func referrerMatches(hosts []string, r *http.Request) bool {
	u, err := url.Parse(r.Header.Get("Referer"))
	if err != nil {  
		return false
	}
	return hostMatches(hosts, u)
}
returns whether the referrer from the request is in the host list.
package imageproxy  
import (
	"bufio"
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime"
	"net"
	"net/http"
	"net/url"
	"path"
	"strings"
	"time"
	"github.com/fcjr/aia-transport-go"
	"github.com/gregjones/httpcache"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	tphttp "willnorris.com/go/imageproxy/third_party/http"
)
type Proxy struct {
	Client *http.Client  
	Cache  Cache         
	AllowHosts []string
	DenyHosts []string
	Referrers []string
	IncludeReferer bool
	FollowRedirects bool
	DefaultBaseURL *url.URL
	Logger *log.Logger
	SignatureKeys [][]byte
	ScaleUp bool
	Timeout time.Duration
	Verbose bool
	ContentTypes []string
	UserAgent string
}
func NewProxy(transport http.RoundTripper, cache Cache) *Proxy {
	if transport == nil {
		transport, _ = aia.NewTransport()
	}
	if cache == nil {
		cache = NopCache
	}
	proxy := &Proxy{
		Cache: cache,
	}
	client := new(http.Client)
	client.Transport = &httpcache.Transport{
		Transport: &TransformingTransport{
			Transport:     transport,
			CachingClient: client,
			log: func(format string, v ...interface{}) {
				if proxy.Verbose {
					proxy.logf(format, v...)
				}
			},
		},
		Cache:               cache,
		MarkCachedResponses: true,
	}
	proxy.Client = client
	return proxy
}
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/favicon.ico" {
		return  
	}
	if r.URL.Path == "/" || r.URL.Path == "/health-check" {
		fmt.Fprint(w, "OK")
		return
	}
	if r.URL.Path == "/metrics" {
		var h http.Handler = promhttp.Handler()
		h.ServeHTTP(w, r)
		return
	}
	var h http.Handler = http.HandlerFunc(p.serveImage)
	if p.Timeout > 0 {
		h = tphttp.TimeoutHandler(h, p.Timeout, "Gateway timeout waiting for remote resource.")
	}
	timer := prometheus.NewTimer(metricRequestDuration)
	defer timer.ObserveDuration()
	h.ServeHTTP(w, r)
}
func (p *Proxy) serveImage(w http.ResponseWriter, r *http.Request) {
	req, err := NewRequest(r, p.DefaultBaseURL)
	if err != nil {
		msg := fmt.Sprintf("invalid request URL: %v", err)
		p.log(msg)
		http.Error(w, msg, http.StatusBadRequest)
		return
	}
	if err := p.allowed(req); err != nil {
		p.logf("%s: %v", err, req)
		http.Error(w, msgNotAllowed, http.StatusForbidden)
		return
	}
	req.Options.ScaleUp = p.ScaleUp
	actualReq, _ := http.NewRequest("GET", req.String(), nil)
	if p.UserAgent != "" {
		actualReq.Header.Set("User-Agent", p.UserAgent)
	}
	if len(p.ContentTypes) != 0 {
		actualReq.Header.Set("Accept", strings.Join(p.ContentTypes, ", "))
	}
	if p.IncludeReferer {
		copyHeader(actualReq.Header, r.Header, "referer")
	}
	if p.FollowRedirects {
		p.Client.CheckRedirect = func(newreq *http.Request, via []*http.Request) error {
			if hostMatches(p.DenyHosts, newreq.URL) || (len(p.AllowHosts) > 0 && !hostMatches(p.AllowHosts, newreq.URL)) {
				http.Error(w, msgNotAllowedInRedirect, http.StatusForbidden)
				return errNotAllowed
			}
			return nil
		}
	} else {
		p.Client.CheckRedirect = func(newreq *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		}
	}
	resp, err := p.Client.Do(actualReq)
	if err != nil {
		msg := fmt.Sprintf("error fetching remote image: %v", err)
		p.log(msg)
		http.Error(w, msg, http.StatusInternalServerError)
		metricRemoteErrors.Inc()
		return
	}
	defer resp.Body.Close()
	cached := resp.Header.Get(httpcache.XFromCache) == "1"
	if p.Verbose {
		p.logf("request: %+v (served from cache: %t)", *actualReq, cached)
	}
	if cached {
		metricServedFromCache.Inc()
	}
	copyHeader(w.Header(), resp.Header, "Cache-Control", "Last-Modified", "Expires", "Etag", "Link")
	if should304(r, resp) {
		w.WriteHeader(http.StatusNotModified)
		return
	}
	contentType, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
	if contentType == "" || contentType == "application/octet-stream" || contentType == "binary/octet-stream" {
		b := bufio.NewReader(resp.Body)
		resp.Body = ioutil.NopCloser(b)
		contentType = peekContentType(b)
	}
	if resp.ContentLength != 0 && !contentTypeMatches(p.ContentTypes, contentType) {
		p.logf("content-type not allowed: %q", contentType)
		http.Error(w, msgNotAllowed, http.StatusForbidden)
		return
	}
	w.Header().Set("Content-Type", contentType)
	copyHeader(w.Header(), resp.Header, "Content-Length")
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Content-Security-Policy", "script-src 'none'")
	w.Header().Set("X-Content-Type-Options", "nosniff")
	w.Header().Set("X-XSS-Protection", "1; mode=block")
	w.WriteHeader(resp.StatusCode)
	if _, err := io.Copy(w, resp.Body); err != nil {
		p.logf("error copying response: %v", err)
	}
}
func peekContentType(p *bufio.Reader) string {
	byt, err := p.Peek(512)
	if err != nil && err != bufio.ErrBufferFull && err != io.EOF {
		return ""
	}
	return http.DetectContentType(byt)
}
func copyHeader(dst, src http.Header, keys ...string) {
	if len(keys) == 0 {
		for k := range src {
			keys = append(keys, k)
		}
	}
	for _, key := range keys {
		k := http.CanonicalHeaderKey(key)
		for _, v := range src[k] {
			dst.Add(k, v)
		}
	}
}
var (
	errReferrer   = errors.New("request does not contain an allowed referrer")
	errDeniedHost = errors.New("request contains a denied host")
	errNotAllowed = errors.New("request does not contain an allowed host or valid signature")
	msgNotAllowed           = "requested URL is not allowed"
	msgNotAllowedInRedirect = "requested URL in redirect is not allowed"
)
func (p *Proxy) allowed(r *Request) error {
	if len(p.Referrers) > 0 && !referrerMatches(p.Referrers, r.Original) {
		return errReferrer
	}
	if hostMatches(p.DenyHosts, r.URL) {
		return errDeniedHost
	}
	if len(p.AllowHosts) == 0 && len(p.SignatureKeys) == 0 {
		return nil  
	}
	if len(p.AllowHosts) > 0 && hostMatches(p.AllowHosts, r.URL) {
		return nil
	}
	for _, signatureKey := range p.SignatureKeys {
		if len(signatureKey) > 0 && validSignature(signatureKey, r) {
			return nil
		}
	}
	return errNotAllowed
}
func contentTypeMatches(patterns []string, contentType string) bool {
	if len(patterns) == 0 {
		return true
	}
	for _, pattern := range patterns {
		if ok, err := path.Match(pattern, contentType); ok && err == nil {
			return true
		}
	}
	return false
}
func hostMatches(hosts []string, u *url.URL) bool {
	for _, host := range hosts {
		if u.Hostname() == host {
			return true
		}
		if strings.HasPrefix(host, "*.") && strings.HasSuffix(u.Hostname(), host[2:]) {
			return true
		}
		if ip := net.ParseIP(u.Hostname()); ip != nil {
			if _, ipnet, err := net.ParseCIDR(host); err == nil {
				if ipnet.Contains(ip) {
					return true
				}
			}
		}
	}
	return false
}