github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/services/imageproxy/local.go (about)

     1  // Copyright (c) 2017-present Xenia, 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  	"time"
    14  
    15  	"github.com/xzl8028/xenia-server/mlog"
    16  	"github.com/xzl8028/xenia-server/services/httpservice"
    17  	"willnorris.com/go/imageproxy"
    18  )
    19  
    20  var imageContentTypes = []string{
    21  	"image/bmp", "image/cgm", "image/g3fax", "image/gif", "image/ief", "image/jp2",
    22  	"image/jpeg", "image/jpg", "image/pict", "image/png", "image/prs.btif", "image/svg+xml",
    23  	"image/tiff", "image/vnd.adobe.photoshop", "image/vnd.djvu", "image/vnd.dwg",
    24  	"image/vnd.dxf", "image/vnd.fastbidsheet", "image/vnd.fpx", "image/vnd.fst",
    25  	"image/vnd.fujixerox.edmics-mmr", "image/vnd.fujixerox.edmics-rlc",
    26  	"image/vnd.microsoft.icon", "image/vnd.ms-modi", "image/vnd.net-fpx", "image/vnd.wap.wbmp",
    27  	"image/vnd.xiff", "image/webp", "image/x-cmu-raster", "image/x-cmx", "image/x-icon",
    28  	"image/x-macpaint", "image/x-pcx", "image/x-pict", "image/x-portable-anymap",
    29  	"image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap",
    30  	"image/x-quicktime", "image/x-rgb", "image/x-xbitmap", "image/x-xpixmap", "image/x-xwindowdump",
    31  }
    32  
    33  var ErrLocalRequestFailed = Error{errors.New("imageproxy.LocalBackend: failed to request proxied image")}
    34  
    35  type LocalBackend struct {
    36  	proxy *ImageProxy
    37  
    38  	// The underlying image proxy implementation provided by the third party library
    39  	impl *imageproxy.Proxy
    40  }
    41  
    42  func makeLocalBackend(proxy *ImageProxy) *LocalBackend {
    43  	impl := imageproxy.NewProxy(proxy.HTTPService.MakeTransport(false), nil)
    44  
    45  	if proxy.Logger != nil {
    46  		logger, err := proxy.Logger.StdLogAt(mlog.LevelDebug, mlog.String("image_proxy", "local"))
    47  		if err != nil {
    48  			mlog.Error("Failed to initialize logger for image proxy", mlog.Err(err))
    49  		}
    50  
    51  		impl.Logger = logger
    52  	}
    53  
    54  	baseURL, err := url.Parse(*proxy.ConfigService.Config().ServiceSettings.SiteURL)
    55  	if err != nil {
    56  		mlog.Error("Failed to set base URL for image proxy. Relative image links may not work.", mlog.Err(err))
    57  	} else {
    58  		impl.DefaultBaseURL = baseURL
    59  	}
    60  
    61  	impl.Timeout = time.Duration(httpservice.RequestTimeout)
    62  	impl.ContentTypes = imageContentTypes
    63  
    64  	return &LocalBackend{
    65  		proxy: proxy,
    66  		impl:  impl,
    67  	}
    68  }
    69  
    70  func (backend *LocalBackend) GetImage(w http.ResponseWriter, r *http.Request, imageURL string) {
    71  	// The interface to the proxy only exposes a ServeHTTP method, so fake a request to it
    72  	req, err := http.NewRequest(http.MethodGet, "/"+imageURL, nil)
    73  	if err != nil {
    74  		// http.NewRequest should only return an error on an invalid URL
    75  		mlog.Error("Failed to create request for proxied image", mlog.String("url", imageURL), mlog.Err(err))
    76  
    77  		w.WriteHeader(http.StatusBadRequest)
    78  		w.Write([]byte{})
    79  		return
    80  	}
    81  
    82  	w.Header().Set("X-Frame-Options", "deny")
    83  	w.Header().Set("X-XSS-Protection", "1; mode=block")
    84  	w.Header().Set("X-Content-Type-Options", "text/plain")
    85  	w.Header().Set("Content-Security-Policy", "default-src 'none'; img-src data:; style-src 'unsafe-inline'")
    86  
    87  	backend.impl.ServeHTTP(w, req)
    88  }
    89  
    90  func (backend *LocalBackend) GetImageDirect(imageURL string) (io.ReadCloser, string, error) {
    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  		return nil, "", Error{err}
    95  	}
    96  
    97  	recorder := httptest.NewRecorder()
    98  
    99  	backend.impl.ServeHTTP(recorder, req)
   100  
   101  	if recorder.Code != http.StatusOK {
   102  		return nil, "", ErrLocalRequestFailed
   103  	}
   104  
   105  	return ioutil.NopCloser(recorder.Body), recorder.Header().Get("Content-Type"), nil
   106  }