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