mirror of
https://github.com/willnorris/imageproxy.git
synced 2024-12-16 21:56:43 -05:00
content-type checking
This commit is contained in:
parent
74c16f575e
commit
39a4e1813d
3 changed files with 82 additions and 6 deletions
|
@ -51,6 +51,7 @@ var scaleUp = flag.Bool("scaleUp", false, "allow images to scale beyond their or
|
|||
var timeout = flag.Duration("timeout", 0, "time limit for requests served by this proxy")
|
||||
var verbose = flag.Bool("verbose", false, "print verbose logging messages")
|
||||
var version = flag.Bool("version", false, "Deprecated: this flag does nothing")
|
||||
var contentTypes = flag.String("contentTypes", "", "comma separated list of allowed content types")
|
||||
|
||||
func init() {
|
||||
flag.Var(&cache, "cache", "location to cache images (see https://github.com/willnorris/imageproxy#cache)")
|
||||
|
@ -66,6 +67,9 @@ func main() {
|
|||
if *referrers != "" {
|
||||
p.Referrers = strings.Split(*referrers, ",")
|
||||
}
|
||||
if *contentTypes != "" {
|
||||
p.ContentTypes = strings.Split(*contentTypes, ",")
|
||||
}
|
||||
if *signatureKey != "" {
|
||||
key := []byte(*signatureKey)
|
||||
if strings.HasPrefix(*signatureKey, "@") {
|
||||
|
|
|
@ -26,8 +26,10 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -67,6 +69,10 @@ type Proxy struct {
|
|||
|
||||
// If true, log additional debug messages
|
||||
Verbose bool
|
||||
|
||||
// ContentTypes specifies a list of content types to allow. An empty list means only image types
|
||||
// are allowed.
|
||||
ContentTypes []string
|
||||
}
|
||||
|
||||
// NewProxy constructs a new proxy. The provided http RoundTripper will be
|
||||
|
@ -162,7 +168,15 @@ func (p *Proxy) serveImage(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
copyHeader(w.Header(), resp.Header, "Content-Length", "Content-Type")
|
||||
contentType, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if !validContentType(p.ContentTypes, contentType) {
|
||||
http.Error(w, "forbidden content-type", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
|
||||
copyHeader(w.Header(), resp.Header, "Content-Length")
|
||||
|
||||
//Enable CORS for 3rd party applications
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
@ -211,6 +225,36 @@ func (p *Proxy) allowed(r *Request) error {
|
|||
return fmt.Errorf("request does not contain an allowed host or valid signature: %v", r)
|
||||
}
|
||||
|
||||
// validContentType returns whether contentType matches one of the patterns. If no patterns are
|
||||
// given, validContentType returns true if contentType matches one of the whitelisted image types.
|
||||
func validContentType(patterns []string, contentType string) bool {
|
||||
if len(patterns) == 0 {
|
||||
switch contentType {
|
||||
case "image/bmp", "image/cgm", "image/g3fax", "image/gif", "image/ief", "image/jp2",
|
||||
"image/jpeg", "image/jpg", "image/pict", "image/png", "image/prs.btif", "image/svg+xml",
|
||||
"image/tiff", "image/vnd.adobe.photoshop", "image/vnd.djvu", "image/vnd.dwg",
|
||||
"image/vnd.dxf", "image/vnd.fastbidsheet", "image/vnd.fpx", "image/vnd.fst",
|
||||
"image/vnd.fujixerox.edmics-mmr", "image/vnd.fujixerox.edmics-rlc",
|
||||
"image/vnd.microsoft.icon", "image/vnd.ms-modi", "image/vnd.net-fpx", "image/vnd.wap.wbmp",
|
||||
"image/vnd.xiff", "image/webp", "image/x-cmu-raster", "image/x-cmx", "image/x-icon",
|
||||
"image/x-macpaint", "image/x-pcx", "image/x-pict", "image/x-portable-anymap",
|
||||
"image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap",
|
||||
"image/x-quicktime", "image/x-rgb", "image/x-xbitmap", "image/x-xpixmap",
|
||||
"image/x-xwindowdump":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, pattern := range patterns {
|
||||
if ok, err := filepath.Match(pattern, contentType); ok && err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// validHost returns whether the host in u matches one of hosts.
|
||||
func validHost(hosts []string, u *url.URL) bool {
|
||||
for _, host := range hosts {
|
||||
|
|
|
@ -299,12 +299,12 @@ func (t testTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
var raw string
|
||||
|
||||
switch req.URL.Path {
|
||||
case "/ok":
|
||||
case "/plain":
|
||||
raw = "HTTP/1.1 200 OK\n\n"
|
||||
case "/error":
|
||||
return nil, errors.New("http protocol error")
|
||||
case "/nocontent":
|
||||
raw = "HTTP/1.1 204 No Content\n\n"
|
||||
raw = "HTTP/1.1 204 No Content\nContent-Type: image/png\n\n"
|
||||
case "/etag":
|
||||
raw = "HTTP/1.1 200 OK\nEtag: \"tag\"\n\n"
|
||||
case "/png":
|
||||
|
@ -312,7 +312,7 @@ func (t testTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
img := new(bytes.Buffer)
|
||||
png.Encode(img, m)
|
||||
|
||||
raw = fmt.Sprintf("HTTP/1.1 200 OK\nContent-Length: %d\n\n%s", len(img.Bytes()), img.Bytes())
|
||||
raw = fmt.Sprintf("HTTP/1.1 200 OK\nContent-Length: %d\nContent-Type: image/png\n\n%s", len(img.Bytes()), img.Bytes())
|
||||
default:
|
||||
raw = "HTTP/1.1 404 Not Found\n\n"
|
||||
}
|
||||
|
@ -338,8 +338,8 @@ func TestProxy_ServeHTTP(t *testing.T) {
|
|||
{"/http://bad.test/", http.StatusForbidden}, // Disallowed host
|
||||
{"/http://good.test/error", http.StatusInternalServerError}, // HTTP protocol error
|
||||
{"/http://good.test/nocontent", http.StatusNoContent}, // non-OK response
|
||||
|
||||
{"/100/http://good.test/ok", http.StatusOK},
|
||||
{"/100/http://good.test/png", http.StatusOK},
|
||||
{"/100/http://good.test/plain", http.StatusForbidden}, // non-image response
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -411,3 +411,31 @@ func TestTransformingTransport(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidContentType(t *testing.T) {
|
||||
for contentType, expected := range map[string]bool{
|
||||
"": false,
|
||||
"image/png": true,
|
||||
"text/html": false,
|
||||
} {
|
||||
actual := validContentType(nil, contentType)
|
||||
if actual != expected {
|
||||
t.Errorf("got %v, expected %v for content type: %v", actual, expected, contentType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidContentType_Patterns(t *testing.T) {
|
||||
for contentType, expected := range map[string]bool{
|
||||
"": false,
|
||||
"image/png": false,
|
||||
"foo/asdf": true,
|
||||
"bar/baz": true,
|
||||
"bar/bazz": false,
|
||||
} {
|
||||
actual := validContentType([]string{"foo/*", "bar/baz"}, contentType)
|
||||
if actual != expected {
|
||||
t.Errorf("got %v, expected %v for content type: %v", actual, expected, contentType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue