diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ed3b07 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.test diff --git a/proxy/proxy.go b/proxy/proxy.go new file mode 100644 index 0000000..b97ee7a --- /dev/null +++ b/proxy/proxy.go @@ -0,0 +1,81 @@ +// Package proxy provides the image proxy. +package proxy + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + "strings" +) + +// URLError reports a malformed URL error. +type URLError struct { + Message string + URL *url.URL +} + +func (e URLError) Error() string { + return fmt.Sprintf("malformed URL %q: %s", e.URL, e.Message) +} + +// Request is a request for an image. +type Request struct { + URL *url.URL // URL of the image to proxy + Width int // requested width, in pixels + Height int // requested height, in pixels +} + +// NewRequest parses an http.Request into an image request. +func NewRequest(r *http.Request) (*Request, error) { + path := strings.SplitN(r.URL.Path, "/", 3) + if len(path) != 3 { + return nil, URLError{"too few path segments", r.URL} + } + + var err error + req := new(Request) + + req.URL, err = url.Parse(path[2]) + if err != nil { + return nil, URLError{ + fmt.Sprintf("unable to parse remote URL: %v", err), + r.URL, + } + } + + if !req.URL.IsAbs() { + return nil, URLError{"must provide absolute remote URL", r.URL} + } + + if req.URL.Scheme != "http" && req.URL.Scheme != "https" { + return nil, URLError{"remote URL must have http or https URL", r.URL} + } + + // query string is always part of the remote URL + req.URL.RawQuery = r.URL.RawQuery + + var h, w string + size := strings.SplitN(path[1], "x", 2) + w = size[0] + if len(size) > 1 { + h = size[1] + } else { + h = w + } + + if w != "" { + req.Width, err = strconv.Atoi(w) + if err != nil { + return nil, URLError{"width must be an int", r.URL} + } + } + if h != "" { + req.Height, err = strconv.Atoi(h) + if err != nil { + return nil, URLError{"height must be an int", r.URL} + } + } + + return req, nil +} diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go new file mode 100644 index 0000000..05a8575 --- /dev/null +++ b/proxy/proxy_test.go @@ -0,0 +1,101 @@ +package proxy + +import ( + "net/http" + "testing" +) + +func TestNewRequest(t *testing.T) { + tests := []struct { + URL string + RemoteURL string + Width int + Height int + ExpectError bool + }{ + // invalid URLs + { + "http://localhost/", "", 0, 0, true, + }, + { + "http://localhost/1/", "", 0, 0, true, + }, + { + "http://localhost//example.com/foo", "", 0, 0, true, + }, + { + "http://localhost//ftp://example.com/foo", "", 0, 0, true, + }, + { + "http://localhost/s/http://example.com/", "", 0, 0, true, + }, + { + "http://localhost/1xs/http://example.com/", "", 0, 0, true, + }, + + // valid URLs + { + "http://localhost//http://example.com/foo", + "http://example.com/foo", 0, 0, false, + }, + { + "http://localhost//https://example.com/foo", + "https://example.com/foo", 0, 0, false, + }, + { + "http://localhost//http://example.com/foo?bar", + "http://example.com/foo?bar", 0, 0, false, + }, + + // size variations + { + "http://localhost/x/http://example.com/", + "http://example.com/", 0, 0, false, + }, + { + "http://localhost/0/http://example.com/", + "http://example.com/", 0, 0, false, + }, + { + "http://localhost/1x/http://example.com/", + "http://example.com/", 1, 0, false, + }, + { + "http://localhost/x1/http://example.com/", + "http://example.com/", 0, 1, false, + }, + { + "http://localhost/1x2/http://example.com/", + "http://example.com/", 1, 2, false, + }, + } + + for i, tt := range tests { + req, err := http.NewRequest("GET", tt.URL, nil) + if err != nil { + t.Errorf("%d. Error parsing request: %v", i, err) + continue + } + + r, err := NewRequest(req) + if tt.ExpectError { + if err == nil { + t.Errorf("%d. Expected parsing error", i) + } + continue + } else if err != nil { + t.Errorf("%d. Error parsing request: %v", i, err) + continue + } + + if got := r.URL.String(); tt.RemoteURL != got { + t.Errorf("%d. Request URL = %v, want %v", i, got, tt.RemoteURL) + } + if tt.Height != r.Height { + t.Errorf("%d. Request Height = %v, want %v", i, r.Height, tt.Height) + } + if tt.Width != r.Width { + t.Errorf("%d. Request Width = %v, want %v", i, r.Width, tt.Width) + } + } +}