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  }