1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2// See LICENSE.txt for license information. 3 4package app 5 6import ( 7 "io" 8 "io/ioutil" 9 "net/http" 10 "net/url" 11 "time" 12 13 "github.com/pkg/errors" 14 15 "github.com/mattermost/mattermost-server/v6/model" 16 "github.com/mattermost/mattermost-server/v6/utils" 17) 18 19const ( 20 // HTTPRequestTimeout defines a high timeout for downloading large files 21 // from an external URL to avoid slow connections from failing to install. 22 HTTPRequestTimeout = 1 * time.Hour 23) 24 25func (a *App) DownloadFromURL(downloadURL string) ([]byte, error) { 26 return a.Srv().downloadFromURL(downloadURL) 27} 28 29func (s *Server) downloadFromURL(downloadURL string) ([]byte, error) { 30 if !model.IsValidHTTPURL(downloadURL) { 31 return nil, errors.Errorf("invalid url %s", downloadURL) 32 } 33 34 u, err := url.ParseRequestURI(downloadURL) 35 if err != nil { 36 return nil, errors.Errorf("failed to parse url %s", downloadURL) 37 } 38 if !*s.Config().PluginSettings.AllowInsecureDownloadURL && u.Scheme != "https" { 39 return nil, errors.Errorf("insecure url not allowed %s", downloadURL) 40 } 41 42 client := s.HTTPService().MakeClient(true) 43 client.Timeout = HTTPRequestTimeout 44 45 var resp *http.Response 46 err = utils.ProgressiveRetry(func() error { 47 resp, err = client.Get(downloadURL) 48 49 if err != nil { 50 return errors.Wrapf(err, "failed to fetch from %s", downloadURL) 51 } 52 53 if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { 54 _, _ = io.Copy(ioutil.Discard, resp.Body) 55 _ = resp.Body.Close() 56 return errors.Errorf("failed to fetch from %s", downloadURL) 57 } 58 59 return nil 60 }) 61 if err != nil { 62 return nil, errors.Wrap(err, "download failed after multiple retries.") 63 } 64 65 defer resp.Body.Close() 66 67 return ioutil.ReadAll(resp.Body) 68} 69