github.com/adacta-ru/mattermost-server/v6@v6.0.0/services/imageproxy/local.go (about)

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