// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package imageproxy import ( "io" "net/http" "net/http/httptest" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/httpservice" "github.com/mattermost/mattermost/server/v8/channels/utils/testutils" ) func makeTestAtmosCamoProxy() *ImageProxy { configService := &testutils.StaticConfigService{ Cfg: &model.Config{ ServiceSettings: model.ServiceSettings{ SiteURL: model.NewPointer("https://mattermost.example.com"), AllowedUntrustedInternalConnections: model.NewPointer("127.0.0.1"), }, ImageProxySettings: model.ImageProxySettings{ Enable: model.NewPointer(true), ImageProxyType: model.NewPointer(model.ImageProxyTypeAtmosCamo), RemoteImageProxyURL: model.NewPointer("http://images.example.com"), RemoteImageProxyOptions: model.NewPointer("7e5f3fab20b94782b43cdb022a66985ef28ba355df2c5d5da3c9a05e4b697bac"), }, }, } return MakeImageProxy(configService, httpservice.MakeHTTPService(configService), nil) } func TestAtmosCamoBackend_GetImage(t *testing.T) { imageURL := "https://www.mattermost.com/wp-content/uploads/2022/02/logoHorizontalWhite.png" proxiedURL := "http://images.example.com/b569ce17f1be4550cffa8d8dd3a9e80e6d209584/68747470733a2f2f7777772e6d61747465726d6f73742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323032322f30322f6c6f676f486f72697a6f6e74616c57686974652e706e67" 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_GetImageDirect(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 := makeTestAtmosCamoProxy() parsedURL, err := url.Parse("https://mattermost.example.com") require.NoError(t, err) remoteURL, err := url.Parse(mock.URL) require.NoError(t, err) backend := &AtmosCamoBackend{ siteURL: parsedURL, remoteURL: remoteURL, client: proxy.HTTPService.MakeClient(false), } body, contentType, err := backend.GetImageDirect("https://example.com/image.png") assert.NoError(t, err) assert.Equal(t, "image/png", contentType) require.NotNil(t, body) respBody, _ := io.ReadAll(body) assert.Equal(t, []byte("1111111111"), respBody) } func TestGetAtmosCamoImageURL(t *testing.T) { imageURL := "https://mattermost.com/wp-content/uploads/2022/02/logoHorizontal.png" proxiedURL := "http://images.example.com/03b122734ae088d10cb46ea05512ec7dc852299e/68747470733a2f2f6d61747465726d6f73742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323032322f30322f6c6f676f486f72697a6f6e74616c2e706e67" defaultSiteURL := "https://mattermost.example.com" proxyURL := "http://images.example.com" 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: "https://mattermost.example.com/static/logo.png", }, { Name: "should bypass opaque URLs", Input: "http:xyz123?query", SiteURL: defaultSiteURL, Expected: defaultSiteURL, }, { 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, }, { Name: "should not bypass protocol relative URLs", Input: "https://mattermost.com/wp-content/uploads/2022/02/logoHorizontal.png", SiteURL: "http://mattermost.example.com", Expected: proxiedURL, }, { Name: "should not bypass if the host prefix is same", Input: "https://mattermost.com/wp-content/uploads/2022/02/logoHorizontal.png", SiteURL: defaultSiteURL, Expected: "http://images.example.com/03b122734ae088d10cb46ea05512ec7dc852299e/68747470733a2f2f6d61747465726d6f73742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323032322f30322f6c6f676f486f72697a6f6e74616c2e706e67", }, { Name: "should not bypass for user auth URLs", Input: "https://mattermost.com/wp-content/uploads/2022/02/logoHorizontal.png", SiteURL: defaultSiteURL, Expected: "http://images.example.com/03b122734ae088d10cb46ea05512ec7dc852299e/68747470733a2f2f6d61747465726d6f73742e636f6d2f77702d636f6e74656e742f75706c6f6164732f323032322f30322f6c6f676f486f72697a6f6e74616c2e706e67", }, } { t.Run(test.Name, func(t *testing.T) { parsedURL, err := url.Parse(test.SiteURL) require.NoError(t, err) remoteURL, err := url.Parse(proxyURL) require.NoError(t, err) backend := &AtmosCamoBackend{ siteURL: parsedURL, remoteURL: remoteURL, remoteOptions: *makeTestAtmosCamoProxy().ConfigService.Config().ImageProxySettings.RemoteImageProxyOptions, } assert.Equal(t, test.Expected, backend.getAtmosCamoImageURL(test.Input)) }) } }