diff --git a/router/downloader/downloader.go b/router/downloader/downloader.go index e283d6f..d95af51 100644 --- a/router/downloader/downloader.go +++ b/router/downloader/downloader.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "mime" "net" "net/http" "net/url" @@ -77,10 +78,13 @@ func (c *Counter) Write(p []byte) (int, error) { type DownloadRequest struct { Directory string URL *url.URL + FileName string + UseHeader bool } type Download struct { Identifier string + path string mu sync.RWMutex req DownloadRequest server *server.Server @@ -172,8 +176,28 @@ func (dl *Download) Execute() error { } } - fnameparts := strings.Split(dl.req.URL.Path, "/") - p := filepath.Join(dl.req.Directory, fnameparts[len(fnameparts)-1]) + if dl.req.UseHeader { + if contentDisposition := res.Header.Get("Content-Disposition"); contentDisposition != "" { + _, params, err := mime.ParseMediaType(contentDisposition) + if err != nil { + return errors.WrapIf(err, "downloader: invalid \"Content-Disposition\" header") + } + + if v, ok := params["filename"]; ok { + dl.path = v + } + } + } + if dl.path == "" { + if dl.req.FileName != "" { + dl.path = dl.req.FileName + } else { + parts := strings.Split(dl.req.URL.Path, "/") + dl.path = parts[len(parts)-1] + } + } + + p := dl.Path() dl.server.Log().WithField("path", p).Debug("writing remote file to disk") r := io.TeeReader(res.Body, dl.counter(res.ContentLength)) @@ -205,6 +229,10 @@ func (dl *Download) Progress() float64 { return dl.progress } +func (dl *Download) Path() string { + return filepath.Join(dl.req.Directory, dl.path) +} + // Handles a write event by updating the progress completed percentage and firing off // events to the server websocket as needed. func (dl *Download) counter(contentLength int64) *Counter { diff --git a/router/router_server_files.go b/router/router_server_files.go index 36f7387..ade9f12 100644 --- a/router/router_server_files.go +++ b/router/router_server_files.go @@ -257,9 +257,12 @@ func postServerPullRemoteFile(c *gin.Context) { s := ExtractServer(c) var data struct { // Deprecated - Directory string `binding:"required_without=RootPath,omitempty" json:"directory"` - RootPath string `binding:"required_without=Directory,omitempty" json:"root"` - URL string `binding:"required" json:"url"` + Directory string `binding:"required_without=RootPath,omitempty" json:"directory"` + RootPath string `binding:"required_without=Directory,omitempty" json:"root"` + URL string `binding:"required" json:"url"` + FileName string `json:"file_name"` + UseHeader bool `json:"use_header"` + Foreground bool `json:"foreground"` } if err := c.BindJSON(&data); err != nil { return @@ -297,21 +300,41 @@ func postServerPullRemoteFile(c *gin.Context) { dl := downloader.New(s, downloader.DownloadRequest{ Directory: data.RootPath, URL: u, + FileName: data.FileName, + UseHeader: data.UseHeader, }) - // Execute this pull in a separate thread since it may take a long time to complete. - go func() { + download := func() error { s.Log().WithField("download_id", dl.Identifier).WithField("url", u.String()).Info("starting pull of remote file to disk") if err := dl.Execute(); err != nil { s.Log().WithField("download_id", dl.Identifier).WithField("error", err).Error("failed to pull remote file") + return err } else { s.Log().WithField("download_id", dl.Identifier).Info("completed pull of remote file") } - }() + return nil + } + if !data.Foreground { + go func() { + _ = download() + }() + c.JSON(http.StatusAccepted, gin.H{ + "identifier": dl.Identifier, + }) + return + } - c.JSON(http.StatusAccepted, gin.H{ - "identifier": dl.Identifier, - }) + if err := download(); err != nil { + NewServerError(err, s).Abort(c) + return + } + + st, err := s.Filesystem().Stat(dl.Path()) + if err != nil { + NewServerError(err, s).AbortFilesystemError(c) + return + } + c.JSON(http.StatusOK, &st) } // Stops a remote file download if it exists and belongs to this server.