github.com/fretkak/mattermost-mattermost-server@v5.11.1+incompatible/services/imageproxy/local.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package imageproxy
     5  
     6  import (
     7  	"errors"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"net/url"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/mattermost/mattermost-server/mlog"
    17  	"github.com/mattermost/mattermost-server/services/httpservice"
    18  	"willnorris.com/go/imageproxy"
    19  )
    20  
    21  var imageContentTypes = []string{
    22  	"image/bmp", "image/cgm", "image/g3fax", "image/gif", "image/ief", "image/jp2",
    23  	"image/jpeg", "image/jpg", "image/pict", "image/png", "image/prs.btif", "image/svg+xml",
    24  	"image/tiff", "image/vnd.adobe.photoshop", "image/vnd.djvu", "image/vnd.dwg",
    25  	"image/vnd.dxf", "image/vnd.fastbidsheet", "image/vnd.fpx", "image/vnd.fst",
    26  	"image/vnd.fujixerox.edmics-mmr", "image/vnd.fujixerox.edmics-rlc",
    27  	"image/vnd.microsoft.icon", "image/vnd.ms-modi", "image/vnd.net-fpx", "image/vnd.wap.wbmp",
    28  	"image/vnd.xiff", "image/webp", "image/x-cmu-raster", "image/x-cmx", "image/x-icon",
    29  	"image/x-macpaint", "image/x-pcx", "image/x-pict", "image/x-portable-anymap",
    30  	"image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap",
    31  	"image/x-quicktime", "image/x-rgb", "image/x-xbitmap", "image/x-xpixmap", "image/x-xwindowdump",
    32  }
    33  
    34  var ErrLocalRequestFailed = Error{errors.New("imageproxy.LocalBackend: failed to request proxied image")}
    35  
    36  type LocalBackend struct {
    37  	proxy *ImageProxy
    38  
    39  	// The underlying image proxy implementation provided by the third party library
    40  	impl *imageproxy.Proxy
    41  }
    42  
    43  func makeLocalBackend(proxy *ImageProxy) *LocalBackend {
    44  	impl := imageproxy.NewProxy(proxy.HTTPService.MakeTransport(false), nil)
    45  
    46  	baseURL, err := url.Parse(*proxy.ConfigService.Config().ServiceSettings.SiteURL)
    47  	if err != nil {
    48  		mlog.Error("Failed to set base URL for image proxy. Relative image links may not work.", mlog.Err(err))
    49  	} else {
    50  		impl.DefaultBaseURL = baseURL
    51  	}
    52  
    53  	impl.Timeout = time.Duration(httpservice.RequestTimeout)
    54  	impl.ContentTypes = imageContentTypes
    55  
    56  	return &LocalBackend{
    57  		proxy: proxy,
    58  		impl:  impl,
    59  	}
    60  }
    61  
    62  func (backend *LocalBackend) GetImage(w http.ResponseWriter, r *http.Request, imageURL string) {
    63  	// The interface to the proxy only exposes a ServeHTTP method, so fake a request to it
    64  	req, err := http.NewRequest(http.MethodGet, "/"+imageURL, nil)
    65  	if err != nil {
    66  		// http.NewRequest should only return an error on an invalid URL
    67  		mlog.Error("Failed to create request for proxied image", mlog.String("url", imageURL), mlog.Err(err))
    68  
    69  		w.WriteHeader(http.StatusBadRequest)
    70  		w.Write([]byte{})
    71  		return
    72  	}
    73  
    74  	w.Header().Set("X-Frame-Options", "deny")
    75  	w.Header().Set("X-XSS-Protection", "1; mode=block")
    76  	w.Header().Set("X-Content-Type-Options", "nosniff")
    77  	w.Header().Set("Content-Security-Policy", "default-src 'none'; img-src data:; style-src 'unsafe-inline'")
    78  
    79  	backend.impl.ServeHTTP(w, req)
    80  }
    81  
    82  func (backend *LocalBackend) GetImageDirect(imageURL string) (io.ReadCloser, string, error) {
    83  	// The interface to the proxy only exposes a ServeHTTP method, so fake a request to it
    84  	req, err := http.NewRequest(http.MethodGet, "/"+imageURL, nil)
    85  	if err != nil {
    86  		return nil, "", Error{err}
    87  	}
    88  
    89  	recorder := httptest.NewRecorder()
    90  
    91  	backend.impl.ServeHTTP(recorder, req)
    92  
    93  	if recorder.Code != http.StatusOK {
    94  		return nil, "", ErrLocalRequestFailed
    95  	}
    96  
    97  	return ioutil.NopCloser(recorder.Body), recorder.Header().Get("Content-Type"), nil
    98  }
    99  
   100  func (backend *LocalBackend) GetProxiedImageURL(imageURL string) string {
   101  	siteURL := *backend.proxy.ConfigService.Config().ServiceSettings.SiteURL
   102  
   103  	if imageURL == "" || imageURL[0] == '/' || strings.HasPrefix(imageURL, siteURL) {
   104  		return imageURL
   105  	}
   106  
   107  	return siteURL + "/api/v4/image?url=" + url.QueryEscape(imageURL)
   108  }
   109  
   110  func (backend *LocalBackend) GetUnproxiedImageURL(proxiedURL string) string {
   111  	siteURL := *backend.proxy.ConfigService.Config().ServiceSettings.SiteURL
   112  
   113  	if !strings.HasPrefix(proxiedURL, siteURL+"/api/v4/image?url=") {
   114  		return proxiedURL
   115  	}
   116  
   117  	parsed, err := url.Parse(proxiedURL)
   118  	if err != nil {
   119  		return proxiedURL
   120  	}
   121  
   122  	u := parsed.Query()["url"]
   123  	if len(u) == 0 {
   124  		return proxiedURL
   125  	}
   126  
   127  	return u[0]
   128  }