MM-10417 Add local image proxy and enable by default (#9967)
* MM-10417 Add local image proxy and enable by default * Remove unused function * Add dependencies for willnorris/imageproxy * Fixed compilation errors * Lock to the master version of willnorris/imageproxy * Fix atmos/camo proxy when no SiteURL is specified * Re-add default values for deprecated settings * Fix unit tests added by merge * Pass imageproxy to App struct * Remove unneeded locking when creating the image proxy * Remove empty test file
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
e961b4cd0d
Коммит
ba5566d1a0
70
services/imageproxy/atmos_camo.go
Обычный файл
70
services/imageproxy/atmos_camo.go
Обычный файл
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package imageproxy
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AtmosCamoBackend struct {
|
||||
proxy *ImageProxy
|
||||
}
|
||||
|
||||
func makeAtmosCamoBackend(proxy *ImageProxy) *AtmosCamoBackend {
|
||||
return &AtmosCamoBackend{
|
||||
proxy: proxy,
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *AtmosCamoBackend) GetImage(w http.ResponseWriter, r *http.Request, imageURL string) {
|
||||
http.Redirect(w, r, backend.GetProxiedImageURL(imageURL), http.StatusFound)
|
||||
}
|
||||
|
||||
func (backend *AtmosCamoBackend) GetProxiedImageURL(imageURL string) string {
|
||||
cfg := *backend.proxy.ConfigService.Config()
|
||||
siteURL := *cfg.ServiceSettings.SiteURL
|
||||
proxyURL := *cfg.ImageProxySettings.RemoteImageProxyURL
|
||||
options := *cfg.ImageProxySettings.RemoteImageProxyOptions
|
||||
|
||||
return getAtmosCamoImageURL(imageURL, siteURL, proxyURL, options)
|
||||
}
|
||||
|
||||
func getAtmosCamoImageURL(imageURL, siteURL, proxyURL, options string) string {
|
||||
// Don't proxy blank images, relative URLs, absolute URLs on this server, or URLs that are already going through the proxy
|
||||
if imageURL == "" || imageURL[0] == '/' || (siteURL != "" && strings.HasPrefix(imageURL, siteURL)) || strings.HasPrefix(imageURL, proxyURL) {
|
||||
return imageURL
|
||||
}
|
||||
|
||||
mac := hmac.New(sha1.New, []byte(options))
|
||||
mac.Write([]byte(imageURL))
|
||||
digest := hex.EncodeToString(mac.Sum(nil))
|
||||
|
||||
return proxyURL + "/" + digest + "/" + hex.EncodeToString([]byte(imageURL))
|
||||
}
|
||||
|
||||
func (backend *AtmosCamoBackend) GetUnproxiedImageURL(proxiedURL string) string {
|
||||
proxyURL := *backend.proxy.ConfigService.Config().ImageProxySettings.RemoteImageProxyURL + "/"
|
||||
|
||||
if !strings.HasPrefix(proxiedURL, proxyURL) {
|
||||
return proxiedURL
|
||||
}
|
||||
|
||||
path := proxiedURL[len(proxyURL):]
|
||||
|
||||
slash := strings.IndexByte(path, '/')
|
||||
if slash == -1 {
|
||||
return proxiedURL
|
||||
}
|
||||
|
||||
decoded, err := hex.DecodeString(path[slash+1:])
|
||||
if err != nil {
|
||||
return proxiedURL
|
||||
}
|
||||
|
||||
return string(decoded)
|
||||
}
|
||||
159
services/imageproxy/atmos_camo_test.go
Обычный файл
159
services/imageproxy/atmos_camo_test.go
Обычный файл
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package imageproxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func makeTestAtmosCamoProxy() *ImageProxy {
|
||||
configService := &testutils.StaticConfigService{
|
||||
Cfg: &model.Config{
|
||||
ServiceSettings: model.ServiceSettings{
|
||||
SiteURL: model.NewString("https://mattermost.example.com"),
|
||||
},
|
||||
ImageProxySettings: model.ImageProxySettings{
|
||||
Enable: model.NewBool(true),
|
||||
ImageProxyType: model.NewString(model.IMAGE_PROXY_TYPE_ATMOS_CAMO),
|
||||
RemoteImageProxyURL: model.NewString("http://images.example.com"),
|
||||
RemoteImageProxyOptions: model.NewString("7e5f3fab20b94782b43cdb022a66985ef28ba355df2c5d5da3c9a05e4b697bac"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return MakeImageProxy(configService, nil)
|
||||
}
|
||||
|
||||
func TestAtmosCamoBackend_GetImage(t *testing.T) {
|
||||
imageURL := "http://www.mattermost.org/wp-content/uploads/2016/03/logoHorizontalWhite.png"
|
||||
proxiedURL := "http://images.example.com/62183a1cf0a4927c3b56d249366c2745e34ffe63/687474703a2f2f7777772e6d61747465726d6f73742e6f72672f77702d636f6e74656e742f75706c6f6164732f323031362f30332f6c6f676f486f72697a6f6e74616c57686974652e706e67"
|
||||
|
||||
proxy := makeTestAtmosCamoProxy()
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, "", nil)
|
||||
proxy.GetImage(recorder, request, imageURL)
|
||||
resp := recorder.Result()
|
||||
|
||||
assert.Equal(t, http.StatusFound, resp.StatusCode)
|
||||
assert.Equal(t, proxiedURL, resp.Header.Get("Location"))
|
||||
}
|
||||
|
||||
func TestAtmosCamoBackend_GetProxiedImageURL(t *testing.T) {
|
||||
imageURL := "http://www.mattermost.org/wp-content/uploads/2016/03/logoHorizontal.png"
|
||||
proxiedURL := "http://images.example.com/5b6f6661516bc837b89b54566eb619d14a5c3eca/687474703a2f2f7777772e6d61747465726d6f73742e6f72672f77702d636f6e74656e742f75706c6f6164732f323031362f30332f6c6f676f486f72697a6f6e74616c2e706e67"
|
||||
|
||||
proxy := makeTestAtmosCamoProxy()
|
||||
|
||||
// Most of this logic is tested in TestGetAtmosCamoImageURL
|
||||
assert.Equal(t, proxiedURL, proxy.GetProxiedImageURL(imageURL))
|
||||
}
|
||||
|
||||
func TestGetAtmosCamoImageURL(t *testing.T) {
|
||||
imageURL := "http://www.mattermost.org/wp-content/uploads/2016/03/logoHorizontal.png"
|
||||
proxiedURL := "http://images.example.com/5b6f6661516bc837b89b54566eb619d14a5c3eca/687474703a2f2f7777772e6d61747465726d6f73742e6f72672f77702d636f6e74656e742f75706c6f6164732f323031362f30332f6c6f676f486f72697a6f6e74616c2e706e67"
|
||||
|
||||
defaultSiteURL := "https://mattermost.example.com"
|
||||
proxyURL := "http://images.example.com"
|
||||
options := "7e5f3fab20b94782b43cdb022a66985ef28ba355df2c5d5da3c9a05e4b697bac"
|
||||
|
||||
for _, test := range []struct {
|
||||
Name string
|
||||
Input string
|
||||
SiteURL string
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
Name: "should proxy image",
|
||||
Input: imageURL,
|
||||
SiteURL: defaultSiteURL,
|
||||
Expected: proxiedURL,
|
||||
},
|
||||
{
|
||||
Name: "should proxy image when no site URL is set",
|
||||
Input: imageURL,
|
||||
SiteURL: "",
|
||||
Expected: proxiedURL,
|
||||
},
|
||||
{
|
||||
Name: "should proxy image when a site URL with a subpath is set",
|
||||
Input: imageURL,
|
||||
SiteURL: proxyURL + "/subpath",
|
||||
Expected: proxiedURL,
|
||||
},
|
||||
{
|
||||
Name: "should not proxy a relative image",
|
||||
Input: "/static/logo.png",
|
||||
SiteURL: defaultSiteURL,
|
||||
Expected: "/static/logo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not proxy an image on the Mattermost server",
|
||||
Input: "https://mattermost.example.com/static/logo.png",
|
||||
SiteURL: defaultSiteURL,
|
||||
Expected: "https://mattermost.example.com/static/logo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not proxy an image on the Mattermost server when a subpath is set",
|
||||
Input: "https://mattermost.example.com/static/logo.png",
|
||||
SiteURL: defaultSiteURL + "/static",
|
||||
Expected: "https://mattermost.example.com/static/logo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not proxy an image that has already been proxied",
|
||||
Input: proxiedURL,
|
||||
SiteURL: defaultSiteURL,
|
||||
Expected: proxiedURL,
|
||||
},
|
||||
} {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
assert.Equal(t, test.Expected, getAtmosCamoImageURL(test.Input, test.SiteURL, proxyURL, options))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAtmosCamoBackend_GetUnproxiedImageURL(t *testing.T) {
|
||||
imageURL := "http://www.mattermost.org/wp-content/uploads/2016/03/logoHorizontal.png"
|
||||
proxiedURL := "http://images.example.com/5b6f6661516bc837b89b54566eb619d14a5c3eca/687474703a2f2f7777772e6d61747465726d6f73742e6f72672f77702d636f6e74656e742f75706c6f6164732f323031362f30332f6c6f676f486f72697a6f6e74616c2e706e67"
|
||||
|
||||
proxy := makeTestAtmosCamoProxy()
|
||||
|
||||
for _, test := range []struct {
|
||||
Name string
|
||||
Input string
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
Name: "should remove proxy",
|
||||
Input: proxiedURL,
|
||||
Expected: imageURL,
|
||||
},
|
||||
{
|
||||
Name: "should not remove proxy from a relative image",
|
||||
Input: "/static/logo.png",
|
||||
Expected: "/static/logo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not remove proxy from an image on the Mattermost server",
|
||||
Input: "https://mattermost.example.com/static/logo.png",
|
||||
Expected: "https://mattermost.example.com/static/logo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not remove proxy from a non-proxied image",
|
||||
Input: imageURL,
|
||||
Expected: imageURL,
|
||||
},
|
||||
} {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
assert.Equal(t, test.Expected, proxy.GetUnproxiedImageURL(test.Input))
|
||||
})
|
||||
}
|
||||
}
|
||||
123
services/imageproxy/imageproxy.go
Обычный файл
123
services/imageproxy/imageproxy.go
Обычный файл
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package imageproxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/services/configservice"
|
||||
"github.com/mattermost/mattermost-server/services/httpservice"
|
||||
)
|
||||
|
||||
// An ImageProxy is the public interface for Mattermost's image proxy. An instance of ImageProxy should be created
|
||||
// using MakeImageProxy which requires a configService and an HTTPService provided by the server.
|
||||
type ImageProxy struct {
|
||||
ConfigService configservice.ConfigService
|
||||
configListenerId string
|
||||
|
||||
HTTPService httpservice.HTTPService
|
||||
|
||||
lock sync.RWMutex
|
||||
backend ImageProxyBackend
|
||||
}
|
||||
|
||||
// An ImageProxyBackend provides the functionality for different types of image proxies. An ImageProxy will construct
|
||||
// the required backend depending on the ImageProxySettings provided by the ConfigService.
|
||||
type ImageProxyBackend interface {
|
||||
// GetImage provides a proxied image in response to an HTTP request.
|
||||
GetImage(w http.ResponseWriter, r *http.Request, imageURL string)
|
||||
|
||||
// GetProxiedImageURL returns the URL to access a given image through the image proxy, whether the image proxy is
|
||||
// running externally or as part of the Mattermost server itself.
|
||||
GetProxiedImageURL(imageURL string) string
|
||||
|
||||
// GetUnproxiedImageURL returns the original URL of an image from one that has been directed at the image proxy.
|
||||
GetUnproxiedImageURL(proxiedURL string) string
|
||||
}
|
||||
|
||||
func MakeImageProxy(configService configservice.ConfigService, httpService httpservice.HTTPService) *ImageProxy {
|
||||
proxy := &ImageProxy{
|
||||
ConfigService: configService,
|
||||
HTTPService: httpService,
|
||||
}
|
||||
|
||||
proxy.configListenerId = proxy.ConfigService.AddConfigListener(proxy.OnConfigChange)
|
||||
|
||||
config := proxy.ConfigService.Config()
|
||||
proxy.backend = proxy.makeBackend(*config.ImageProxySettings.Enable, *config.ImageProxySettings.ImageProxyType)
|
||||
|
||||
return proxy
|
||||
}
|
||||
|
||||
func (proxy *ImageProxy) makeBackend(enable bool, proxyType string) ImageProxyBackend {
|
||||
if !enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch proxyType {
|
||||
case model.IMAGE_PROXY_TYPE_LOCAL:
|
||||
return makeLocalBackend(proxy)
|
||||
case model.IMAGE_PROXY_TYPE_ATMOS_CAMO:
|
||||
return makeAtmosCamoBackend(proxy)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (proxy *ImageProxy) Close() {
|
||||
proxy.lock.Lock()
|
||||
defer proxy.lock.Unlock()
|
||||
|
||||
proxy.ConfigService.RemoveConfigListener(proxy.configListenerId)
|
||||
}
|
||||
|
||||
func (proxy *ImageProxy) OnConfigChange(oldConfig, newConfig *model.Config) {
|
||||
if *oldConfig.ImageProxySettings.Enable != *newConfig.ImageProxySettings.Enable ||
|
||||
*oldConfig.ImageProxySettings.ImageProxyType != *newConfig.ImageProxySettings.ImageProxyType {
|
||||
proxy.lock.Lock()
|
||||
defer proxy.lock.Unlock()
|
||||
|
||||
proxy.backend = proxy.makeBackend(*newConfig.ImageProxySettings.Enable, *newConfig.ImageProxySettings.ImageProxyType)
|
||||
}
|
||||
}
|
||||
|
||||
// GetImage takes an HTTP request for an image and requests that image using the image proxy.
|
||||
func (proxy *ImageProxy) GetImage(w http.ResponseWriter, r *http.Request, imageURL string) {
|
||||
proxy.lock.RLock()
|
||||
defer proxy.lock.RUnlock()
|
||||
|
||||
if proxy.backend == nil {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
proxy.backend.GetImage(w, r, imageURL)
|
||||
}
|
||||
|
||||
// GetProxiedImageURL takes the URL of an image and returns a URL that can be used to view that image through the
|
||||
// image proxy.
|
||||
func (proxy *ImageProxy) GetProxiedImageURL(imageURL string) string {
|
||||
proxy.lock.RLock()
|
||||
defer proxy.lock.RUnlock()
|
||||
|
||||
if proxy.backend == nil {
|
||||
return imageURL
|
||||
}
|
||||
|
||||
return proxy.backend.GetProxiedImageURL(imageURL)
|
||||
}
|
||||
|
||||
// GetUnproxiedImageURL takes the URL of an image on the image proxy and returns the original URL of the image.
|
||||
func (proxy *ImageProxy) GetUnproxiedImageURL(proxiedURL string) string {
|
||||
proxy.lock.RLock()
|
||||
defer proxy.lock.RUnlock()
|
||||
|
||||
if proxy.backend == nil {
|
||||
return proxiedURL
|
||||
}
|
||||
|
||||
return proxy.backend.GetUnproxiedImageURL(proxiedURL)
|
||||
}
|
||||
99
services/imageproxy/local.go
Обычный файл
99
services/imageproxy/local.go
Обычный файл
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package imageproxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/services/httpservice"
|
||||
"willnorris.com/go/imageproxy"
|
||||
)
|
||||
|
||||
var imageContentTypes = []string{
|
||||
"image/bmp", "image/cgm", "image/g3fax", "image/gif", "image/ief", "image/jp2",
|
||||
"image/jpeg", "image/jpg", "image/pict", "image/png", "image/prs.btif", "image/svg+xml",
|
||||
"image/tiff", "image/vnd.adobe.photoshop", "image/vnd.djvu", "image/vnd.dwg",
|
||||
"image/vnd.dxf", "image/vnd.fastbidsheet", "image/vnd.fpx", "image/vnd.fst",
|
||||
"image/vnd.fujixerox.edmics-mmr", "image/vnd.fujixerox.edmics-rlc",
|
||||
"image/vnd.microsoft.icon", "image/vnd.ms-modi", "image/vnd.net-fpx", "image/vnd.wap.wbmp",
|
||||
"image/vnd.xiff", "image/webp", "image/x-cmu-raster", "image/x-cmx", "image/x-icon",
|
||||
"image/x-macpaint", "image/x-pcx", "image/x-pict", "image/x-portable-anymap",
|
||||
"image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap",
|
||||
"image/x-quicktime", "image/x-rgb", "image/x-xbitmap", "image/x-xpixmap", "image/x-xwindowdump",
|
||||
}
|
||||
|
||||
type LocalBackend struct {
|
||||
proxy *ImageProxy
|
||||
|
||||
// The underlying image proxy implementation provided by the third party library
|
||||
impl *imageproxy.Proxy
|
||||
}
|
||||
|
||||
func makeLocalBackend(proxy *ImageProxy) *LocalBackend {
|
||||
impl := imageproxy.NewProxy(proxy.HTTPService.MakeTransport(false), nil)
|
||||
|
||||
baseURL, err := url.Parse(*proxy.ConfigService.Config().ServiceSettings.SiteURL)
|
||||
if err != nil {
|
||||
mlog.Error("Failed to set base URL for image proxy. Relative image links may not work.", mlog.Err(err))
|
||||
} else {
|
||||
impl.DefaultBaseURL = baseURL
|
||||
}
|
||||
|
||||
impl.Timeout = time.Duration(httpservice.RequestTimeout)
|
||||
impl.ContentTypes = imageContentTypes
|
||||
|
||||
return &LocalBackend{
|
||||
proxy: proxy,
|
||||
impl: impl,
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *LocalBackend) GetImage(w http.ResponseWriter, r *http.Request, imageURL string) {
|
||||
// The interface to the proxy only exposes a ServeHTTP method, so fake a request to it
|
||||
req, err := http.NewRequest(http.MethodGet, "/"+imageURL, nil)
|
||||
if err != nil {
|
||||
// http.NewRequest should only return an error on an invalid URL
|
||||
mlog.Error("Failed to create request for proxied image", mlog.String("url", imageURL), mlog.Err(err))
|
||||
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte{})
|
||||
return
|
||||
}
|
||||
|
||||
backend.impl.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
func (backend *LocalBackend) GetProxiedImageURL(imageURL string) string {
|
||||
siteURL := *backend.proxy.ConfigService.Config().ServiceSettings.SiteURL
|
||||
|
||||
if imageURL == "" || imageURL[0] == '/' || strings.HasPrefix(imageURL, siteURL) {
|
||||
return imageURL
|
||||
}
|
||||
|
||||
return siteURL + "/api/v4/image?url=" + url.QueryEscape(imageURL)
|
||||
}
|
||||
|
||||
func (backend *LocalBackend) GetUnproxiedImageURL(proxiedURL string) string {
|
||||
siteURL := *backend.proxy.ConfigService.Config().ServiceSettings.SiteURL
|
||||
|
||||
if !strings.HasPrefix(proxiedURL, siteURL+"/api/v4/image?url=") {
|
||||
return proxiedURL
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(proxiedURL)
|
||||
if err != nil {
|
||||
return proxiedURL
|
||||
}
|
||||
|
||||
u := parsed.Query()["url"]
|
||||
if len(u) == 0 {
|
||||
return proxiedURL
|
||||
}
|
||||
|
||||
return u[0]
|
||||
}
|
||||
243
services/imageproxy/local_test.go
Обычный файл
243
services/imageproxy/local_test.go
Обычный файл
@@ -0,0 +1,243 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package imageproxy
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/services/httpservice"
|
||||
"github.com/mattermost/mattermost-server/utils/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func makeTestLocalProxy() *ImageProxy {
|
||||
configService := &testutils.StaticConfigService{
|
||||
Cfg: &model.Config{
|
||||
ServiceSettings: model.ServiceSettings{
|
||||
SiteURL: model.NewString("https://mattermost.example.com"),
|
||||
AllowedUntrustedInternalConnections: model.NewString("127.0.0.1"),
|
||||
},
|
||||
ImageProxySettings: model.ImageProxySettings{
|
||||
Enable: model.NewBool(true),
|
||||
ImageProxyType: model.NewString(model.IMAGE_PROXY_TYPE_LOCAL),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return MakeImageProxy(configService, httpservice.MakeHTTPService(configService))
|
||||
}
|
||||
|
||||
func TestLocalBackend_GetImage(t *testing.T) {
|
||||
t.Run("image", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "max-age=2592000, private")
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.Header().Set("Content-Length", "10")
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("1111111111"))
|
||||
})
|
||||
|
||||
mock := httptest.NewServer(handler)
|
||||
defer mock.Close()
|
||||
|
||||
proxy := makeTestLocalProxy()
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, "", nil)
|
||||
proxy.GetImage(recorder, request, mock.URL+"/image.png")
|
||||
resp := recorder.Result()
|
||||
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Equal(t, "max-age=2592000, private", resp.Header.Get("Cache-Control"))
|
||||
assert.Equal(t, "10", resp.Header.Get("Content-Length"))
|
||||
|
||||
respBody, _ := ioutil.ReadAll(resp.Body)
|
||||
assert.Equal(t, []byte("1111111111"), respBody)
|
||||
})
|
||||
|
||||
t.Run("not an image", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotAcceptable)
|
||||
})
|
||||
|
||||
mock := httptest.NewServer(handler)
|
||||
defer mock.Close()
|
||||
|
||||
proxy := makeTestLocalProxy()
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, "", nil)
|
||||
proxy.GetImage(recorder, request, mock.URL+"/file.pdf")
|
||||
resp := recorder.Result()
|
||||
|
||||
require.Equal(t, http.StatusNotAcceptable, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("not an image, but remote server ignores accept header", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "max-age=2592000, private")
|
||||
w.Header().Set("Content-Type", "application/pdf")
|
||||
w.Header().Set("Content-Length", "10")
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("1111111111"))
|
||||
})
|
||||
|
||||
mock := httptest.NewServer(handler)
|
||||
defer mock.Close()
|
||||
|
||||
proxy := makeTestLocalProxy()
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, "", nil)
|
||||
proxy.GetImage(recorder, request, mock.URL+"/file.pdf")
|
||||
resp := recorder.Result()
|
||||
|
||||
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
|
||||
mock := httptest.NewServer(handler)
|
||||
defer mock.Close()
|
||||
|
||||
proxy := makeTestLocalProxy()
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, "", nil)
|
||||
proxy.GetImage(recorder, request, mock.URL+"/image.png")
|
||||
resp := recorder.Result()
|
||||
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("other server error", func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
})
|
||||
|
||||
mock := httptest.NewServer(handler)
|
||||
defer mock.Close()
|
||||
|
||||
proxy := makeTestLocalProxy()
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, "", nil)
|
||||
proxy.GetImage(recorder, request, mock.URL+"/image.png")
|
||||
resp := recorder.Result()
|
||||
|
||||
require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("timeout", func(t *testing.T) {
|
||||
wait := make(chan bool, 1)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
<-wait
|
||||
})
|
||||
|
||||
mock := httptest.NewServer(handler)
|
||||
defer mock.Close()
|
||||
|
||||
proxy := makeTestLocalProxy()
|
||||
|
||||
// Modify the timeout to be much shorter than the default 30 seconds
|
||||
proxy.backend.(*LocalBackend).impl.Timeout = time.Millisecond
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, "", nil)
|
||||
proxy.GetImage(recorder, request, mock.URL+"/image.png")
|
||||
resp := recorder.Result()
|
||||
|
||||
require.Equal(t, http.StatusGatewayTimeout, resp.StatusCode)
|
||||
|
||||
wait <- true
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocalBackend_GetProxiedImageURL(t *testing.T) {
|
||||
imageURL := "http://www.mattermost.org/wp-content/uploads/2016/03/logoHorizontal.png"
|
||||
proxiedURL := "https://mattermost.example.com/api/v4/image?url=http%3A%2F%2Fwww.mattermost.org%2Fwp-content%2Fuploads%2F2016%2F03%2FlogoHorizontal.png"
|
||||
|
||||
proxy := makeTestLocalProxy()
|
||||
|
||||
for _, test := range []struct {
|
||||
Name string
|
||||
Input string
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
Name: "should proxy image",
|
||||
Input: imageURL,
|
||||
Expected: proxiedURL,
|
||||
},
|
||||
{
|
||||
Name: "should not proxy a relative image",
|
||||
Input: "/static/logo.png",
|
||||
Expected: "/static/logo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not proxy an image on the Mattermost server",
|
||||
Input: "https://mattermost.example.com/static/logo.png",
|
||||
Expected: "https://mattermost.example.com/static/logo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not proxy an image that has already been proxied",
|
||||
Input: proxiedURL,
|
||||
Expected: proxiedURL,
|
||||
},
|
||||
} {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
assert.Equal(t, test.Expected, proxy.GetProxiedImageURL(test.Input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalBackend_GetUnproxiedImageURL(t *testing.T) {
|
||||
imageURL := "http://www.mattermost.org/wp-content/uploads/2016/03/logoHorizontal.png"
|
||||
proxiedURL := "https://mattermost.example.com/api/v4/image?url=http%3A%2F%2Fwww.mattermost.org%2Fwp-content%2Fuploads%2F2016%2F03%2FlogoHorizontal.png"
|
||||
|
||||
proxy := makeTestLocalProxy()
|
||||
|
||||
for _, test := range []struct {
|
||||
Name string
|
||||
Input string
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
Name: "should remove proxy",
|
||||
Input: proxiedURL,
|
||||
Expected: imageURL,
|
||||
},
|
||||
{
|
||||
Name: "should not remove proxy from a relative image",
|
||||
Input: "/static/logo.png",
|
||||
Expected: "/static/logo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not remove proxy from an image on the Mattermost server",
|
||||
Input: "https://mattermost.example.com/static/logo.png",
|
||||
Expected: "https://mattermost.example.com/static/logo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not remove proxy from a non-proxied image",
|
||||
Input: imageURL,
|
||||
Expected: imageURL,
|
||||
},
|
||||
} {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
assert.Equal(t, test.Expected, proxy.GetUnproxiedImageURL(test.Input))
|
||||
})
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user