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