44package imageproxy
55
66import (
7+ "encoding/base64"
78 "fmt"
89 "net/http"
910 "net/url"
1011 "regexp"
1112 "sort"
1213 "strconv"
1314 "strings"
15+ "unicode"
1416)
1517
1618const (
@@ -325,8 +327,10 @@ func (r Request) String() string {
325327// NewRequest parses an http.Request into an imageproxy Request. Options and
326328// the remote image URL are specified in the request path, formatted as:
327329// /{options}/{remote_url}. Options may be omitted, so a request path may
328- // simply contain /{remote_url}. The remote URL must be an absolute "http" or
329- // "https" URL, should not be URL encoded, and may contain a query string.
330+ // simply contain /{remote_url}. The remote URL must either be:
331+ //
332+ // - an absolute "http" or "https" URL, not be URL encoded, with optional query string, or
333+ // - base64 encoded (URL safe, no padding).
330334//
331335// Assuming an imageproxy server running on localhost, the following are all
332336// valid imageproxy requests:
@@ -335,12 +339,14 @@ func (r Request) String() string {
335339// http://localhost/100x200,r90/http://example.com/image.jpg?foo=bar
336340// http://localhost//http://example.com/image.jpg
337341// http://localhost/http://example.com/image.jpg
342+ // http://localhost/100x200/aHR0cDovL2V4YW1wbGUuY29tL2ltYWdlLmpwZw
338343func NewRequest (r * http.Request , baseURL * url.URL ) (* Request , error ) {
339344 var err error
340345 req := & Request {Original : r }
346+ var enc bool // whether the remote URL was base64 encoded
341347
342348 path := r .URL .EscapedPath ()[1 :] // strip leading slash
343- req .URL , err = parseURL (path )
349+ req .URL , enc , err = parseURL (path , baseURL )
344350 if err != nil || ! req .URL .IsAbs () {
345351 // first segment should be options
346352 parts := strings .SplitN (path , "/" , 2 )
@@ -349,7 +355,7 @@ func NewRequest(r *http.Request, baseURL *url.URL) (*Request, error) {
349355 }
350356
351357 var err error
352- req .URL , err = parseURL (parts [1 ])
358+ req .URL , enc , err = parseURL (parts [1 ], baseURL )
353359 if err != nil {
354360 return nil , URLError {fmt .Sprintf ("unable to parse remote URL: %v" , err ), r .URL }
355361 }
@@ -369,16 +375,38 @@ func NewRequest(r *http.Request, baseURL *url.URL) (*Request, error) {
369375 return nil , URLError {"remote URL must have http or https scheme" , r .URL }
370376 }
371377
372- // query string is always part of the remote URL
373- req .URL .RawQuery = r .URL .RawQuery
378+ if ! enc {
379+ // if the remote URL was not base64-encoded,
380+ // then the query string is part of the remote URL
381+ req .URL .RawQuery = r .URL .RawQuery
382+ }
374383 return req , nil
375384}
376385
377386var reCleanedURL = regexp .MustCompile (`^(https?):/+([^/])` )
378387
379388// parseURL parses s as a URL, handling URLs that have been munged by
380389// path.Clean or a webserver that collapses multiple slashes.
381- func parseURL (s string ) (* url.URL , error ) {
390+ // The returned enc bool indicates whether the remote URL was encoded.
391+ func parseURL (s string , baseURL * url.URL ) (_ * url.URL , enc bool , _ error ) {
392+ // Try to base64 decode the string. If it is not base64 encoded,
393+ // this will fail quickly on the first invalid character like ":", ".", or "/".
394+ // Accept the decoded string if it looks like an absolute HTTP URL,
395+ // or if we have a baseURL and the decoded string did not contain invalid code points.
396+ // This allows for values like "/path", which do successfully base64 decode,
397+ // but not to valid code points, to be treated as an unencoded string.
398+ if b , err := base64 .RawURLEncoding .DecodeString (s ); err == nil {
399+ d := string (b )
400+ if strings .HasPrefix (d , "http://" ) || strings .HasPrefix (d , "https://" ) {
401+ enc = true
402+ s = d
403+ } else if baseURL != nil && ! strings .ContainsRune (d , unicode .ReplacementChar ) {
404+ enc = true
405+ s = d
406+ }
407+ }
408+
382409 s = reCleanedURL .ReplaceAllString (s , "$1://$2" )
383- return url .Parse (s )
410+ u , err := url .Parse (s )
411+ return u , enc , err
384412}
0 commit comments