44package imageproxy
55
66import (
7+ "bytes"
8+ "encoding/base64"
79 "fmt"
810 "net/http"
911 "net/url"
1012 "regexp"
1113 "sort"
1214 "strconv"
1315 "strings"
16+ "unicode"
1417)
1518
1619const (
@@ -325,8 +328,10 @@ func (r Request) String() string {
325328// NewRequest parses an http.Request into an imageproxy Request. Options and
326329// the remote image URL are specified in the request path, formatted as:
327330// /{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.
331+ // simply contain /{remote_url}. The remote URL must either be:
332+ //
333+ // - an absolute "http" or "https" URL, not be URL encoded, with optional query string, or
334+ // - base64 encoded (URL safe, no padding).
330335//
331336// Assuming an imageproxy server running on localhost, the following are all
332337// valid imageproxy requests:
@@ -335,12 +340,14 @@ func (r Request) String() string {
335340// http://localhost/100x200,r90/http://example.com/image.jpg?foo=bar
336341// http://localhost//http://example.com/image.jpg
337342// http://localhost/http://example.com/image.jpg
343+ // http://localhost/100x200/aHR0cDovL2V4YW1wbGUuY29tL2ltYWdlLmpwZw
338344func NewRequest (r * http.Request , baseURL * url.URL ) (* Request , error ) {
339345 var err error
340346 req := & Request {Original : r }
347+ var enc bool // whether the remote URL was base64 encoded
341348
342349 path := r .URL .EscapedPath ()[1 :] // strip leading slash
343- req .URL , err = parseURL (path )
350+ req .URL , enc , err = parseURL (path , baseURL )
344351 if err != nil || ! req .URL .IsAbs () {
345352 // first segment should be options
346353 parts := strings .SplitN (path , "/" , 2 )
@@ -349,7 +356,7 @@ func NewRequest(r *http.Request, baseURL *url.URL) (*Request, error) {
349356 }
350357
351358 var err error
352- req .URL , err = parseURL (parts [1 ])
359+ req .URL , enc , err = parseURL (parts [1 ], baseURL )
353360 if err != nil {
354361 return nil , URLError {fmt .Sprintf ("unable to parse remote URL: %v" , err ), r .URL }
355362 }
@@ -369,16 +376,37 @@ func NewRequest(r *http.Request, baseURL *url.URL) (*Request, error) {
369376 return nil , URLError {"remote URL must have http or https scheme" , r .URL }
370377 }
371378
372- // query string is always part of the remote URL
373- req .URL .RawQuery = r .URL .RawQuery
379+ if ! enc {
380+ // if the remote URL was not base64-encoded,
381+ // then the query string is part of the remote URL
382+ req .URL .RawQuery = r .URL .RawQuery
383+ }
374384 return req , nil
375385}
376386
377387var reCleanedURL = regexp .MustCompile (`^(https?):/+([^/])` )
378388
379389// parseURL parses s as a URL, handling URLs that have been munged by
380390// path.Clean or a webserver that collapses multiple slashes.
381- func parseURL (s string ) (* url.URL , error ) {
391+ // The returned enc bool indicates whether the remote URL was encoded.
392+ func parseURL (s string , baseURL * url.URL ) (_ * url.URL , enc bool , _ error ) {
393+ // Try to base64 decode the string. If it is not base64 encoded,
394+ // this will fail quickly on the first invalid character like ":", ".", or "/".
395+ // Accept the decoded string if it looks like an absolute HTTP URL,
396+ // or if we have a baseURL and the decoded string did not contain invalid code points.
397+ // This allows for values like "/path", which do successfully base64 decode,
398+ // but not to valid code points, to be treated as an unencoded string.
399+ if b , err := base64 .RawURLEncoding .DecodeString (s ); err == nil {
400+ if strings .HasPrefix (string (b ), "http://" ) || strings .HasPrefix (string (b ), "https://" ) {
401+ enc = true
402+ s = string (b )
403+ } else if baseURL != nil && ! bytes .ContainsRune (b , unicode .ReplacementChar ) {
404+ enc = true
405+ s = string (b )
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