diff --git a/internal/server/source_url_internal_test.go b/internal/server/source_url_internal_test.go index 06858b1..beaf1d5 100644 --- a/internal/server/source_url_internal_test.go +++ b/internal/server/source_url_internal_test.go @@ -157,10 +157,11 @@ func postGenerate( } // TestImageURLFetchesItsSourcePathAndQuery requests image URLs whose source -// query holds a space and whose source path holds a "%", each written -// percent-encoded, and checks the one request the upstream receives: the -// source path percent-encoded again, and the source query with the space -// written %20. +// query holds a space, written percent-encoded, or a "'", and whose source +// path holds a "%", written percent-encoded, and checks the one request the +// upstream receives: the source path percent-encoded again, and the source +// query with the space written %20 and the "'" written %27, as a browser +// writes them in a query. func TestImageURLFetchesItsSourcePathAndQuery(t *testing.T) { t.Parallel() @@ -174,6 +175,8 @@ func TestImageURLFetchesItsSourcePathAndQuery(t *testing.T) { }{ {"space in the query", "/photo.png%3Fname=my%20cat", "/photo.png?name=my%20cat"}, + {"apostrophe in the query", "/photo.png%3Fname=it's", + "/photo.png?name=it%27s"}, {"percent sign in the path", percentSignPath, percentSignPath}, } @@ -305,30 +308,64 @@ func TestGeneratedURLFetchesTheSourceURLAsEntered(t *testing.T) { } } -// TestGeneratorRefusesSourceURLItWouldFetchAnotherWay submits a source URL -// whose path holds %2F. pixa keeps the path decoded and would fetch it with a -// "/" there, which is another URL, so the generator page answers 400, shows -// the URL pixa would fetch, and makes no encrypted URL. -func TestGeneratorRefusesSourceURLItWouldFetchAnotherWay(t *testing.T) { +// TestGeneratorRefusesSourceURLItCannotFetchAsWritten submits source URLs the +// generator page must make no encrypted URL for, and checks that it answers +// 400 and shows why. pixa keeps the source path decoded and fetches it +// percent-encoded again, so a path holding %2F would be fetched with a "/" +// there, whatever else the path holds. pixa sends no user name or password, +// and no "?" with no query after it. For these the page shows the URL pixa +// would fetch. A source path holding a ".." segment is refused, as +// /v1/image/ refuses it. +func TestGeneratorRefusesSourceURLItCannotFetchAsWritten(t *testing.T) { t.Parallel() - s, _, _ := startImageProxy(t, startRecordingUpstream(t).Server) - - rec := postGenerate(t, s, "https://"+upstreamHost+"/a%2Fb.png") - body := rec.Body.String() - - if rec.Code != http.StatusBadRequest { - t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest) - } - - if strings.Contains(body, "/v1/e/") { - t.Error("the page shows an encrypted URL") - } - // The test config sets allow_http, so pixa would fetch over plain HTTP - fetchURL := "http://" + upstreamHost + "/a/b.png" - if !strings.Contains(body, fetchURL) { - t.Errorf("the page does not show the URL pixa would fetch, %s; body %s", - fetchURL, body) + const fetchedFrom = "http://" + upstreamHost + + tests := []struct { + name string + // sourceURL is the source URL after "https://" + sourceURL string + // wantShown is what the page must show: the URL pixa would fetch, or + // why it makes no encrypted URL + wantShown string + }{ + {"encoded slash", upstreamHost + "/a%2Fb.png", + fetchedFrom + "/a/b.png"}, + {"encoded slash and a space", upstreamHost + "/a%2Fb c.png", + fetchedFrom + "/a/b%20c.png"}, + {"encoded slash and a bar", upstreamHost + "/a%2Fb|c.png", + fetchedFrom + "/a/b%7Cc.png"}, + {"encoded slash and a letter outside ASCII", upstreamHost + "/café%2Fx.png", + fetchedFrom + "/caf%C3%A9/x.png"}, + {"user name and password", "user:pass@" + upstreamHost + "/a.png", + fetchedFrom + "/a.png"}, + {"question mark with no query", upstreamHost + "/a.png?", + fetchedFrom + "/a.png"}, + {"two-dot segment", upstreamHost + "/a/../b.png", + "cannot hold a .. segment"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + s, _, _ := startImageProxy(t, startRecordingUpstream(t).Server) + + rec := postGenerate(t, s, "https://"+tt.sourceURL) + body := rec.Body.String() + + if rec.Code != http.StatusBadRequest { + t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest) + } + + if strings.Contains(body, "/v1/e/") { + t.Error("the page shows an encrypted URL") + } + + if !strings.Contains(body, tt.wantShown) { + t.Errorf("the page does not show %s; body %s", tt.wantShown, body) + } + }) } }