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 }