diff --git a/internal/handlers/image_errors_internal_test.go b/internal/handlers/image_errors_internal_test.go index eac170f..25fe061 100644 --- a/internal/handlers/image_errors_internal_test.go +++ b/internal/handlers/image_errors_internal_test.go @@ -340,3 +340,51 @@ func TestHandleImage_HostForms_Served(t *testing.T) { }) } } + +// TestHandleImage_HashInSourcePathOrQuery_Returns400 sends image URLs whose +// source path or source query holds a "#", written as %23, and checks that +// each is refused with 400, the one signed for that source path included. The +// source URL would otherwise end at the "#" when it is fetched, so each text +// after it would fetch the image at photoPath and cache it under keys of its +// own. +func TestHandleImage_HashInSourcePathOrQuery_Returns400(t *testing.T) { + t.Parallel() + + photos := newPhotoFetcher(t, allowlistedHost, signedHost) + exp := time.Now().Add(time.Hour) + + sig := signature.New(testSigningKey).Sign(&signature.Request{ + SourceHost: signedHost, + SourcePath: photoPath + "#x", + Width: 50, + Height: 50, + Format: string(imgcache.FormatJPEG), + Quality: 85, + FitMode: string(imgcache.FitCover), + Expires: exp, + }) + + tests := []struct { + name string + target string + }{ + {"# in the source path", + "/v1/image/" + allowlistedHost + photoPath + "%23x/50x50.jpeg"}, + {"# in the source query", + "/v1/image/" + allowlistedHost + photoPath + "%3Fv=1%23x/50x50.jpeg"}, + {"# in the source path, signed", + fmt.Sprintf("/v1/image/%s%s%%23x/50x50.jpeg?sig=%s&exp=%d", + signedHost, photoPath, sig, exp.Unix())}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + rec := sendGet(t, newImageRoute(t, photos), tt.target) + checkErrorBody(t, rec, http.StatusBadRequest, + `invalid image URL: invalid image path: `+ + `the source path or query holds a "#"`) + }) + } +}