Skip to content

Instantly share code, notes, and snippets.

@dstufft
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dstufft/0dffb12201d235f35bc7 to your computer and use it in GitHub Desktop.
Save dstufft/0dffb12201d235f35bc7 to your computer and use it in GitHub Desktop.
import hashlib
import hmac
import json
import os
import klein
import treq
from twisted.web.client import UNKNOWN_LENGTH
# TODO: Move Security and Informational Headers So they apply globally
__version__ = "1.0"
SECRET_KEY = os.environ["SMUGGLER_KEY"]
USER_AGENT = os.environ.get(
"SMUGGLER_USER_AGENT",
"Smuggler/{}".format(__version__),
)
TIMEOUT = int(os.environ.get("SMUGGLER_TIMEOUT", 10))
MAX_IMAGE_SIZE = int(os.environ.get("SMUGGLER_SIZE_LIMIT", 5242880))
with open(
os.environ.get(
"SMUGGLER_MIMETYPES",
os.path.join(os.path.dirname(__file__), "mimetypes.json"),
)) as fp:
MIMETYPES = json.load(fp)
def getHeader(headers, name, default=None):
return headers.getRawHeaders(name, [default])[0]
def processResponse(sourceResponse, request):
# Ensure that we know what size the image is
if sourceResponse.length == UNKNOWN_LENGTH:
request.setResponseCode(413)
return "cannot determine size of source image"
# Ensure that the image isn't too large for us
if sourceResponse.length > MAX_IMAGE_SIZE:
request.setResponseCode(413)
return "source image exceeds max allowed size"
contentType = getHeader(sourceResponse.headers, "Content-Type")
etag = getHeader(sourceResponse.headers, "ETag")
lastModified = getHeader(sourceResponse.headers, "Last-Modified")
expires = getHeader(sourceResponse.headers, "Expires")
cacheControl = getHeader(sourceResponse.headers, "Cache-Control")
# Verify Content Type
contentTypePrefix = contentType.split(";")[0].strip()
if contentTypePrefix not in MIMETYPES:
request.setResponseCode(415)
return "non-Image content-type returned '%s'" % (contentTypePrefix,)
request.setHeader("Content-Type", contentType)
#request.setHeader("Content-Length", sourceResponse.length)
if etag is not None:
request.setETag(etag)
if lastModified is not None:
request.setLastModified(lastModified)
if expires is not None:
request.setHeader("Expires", expires)
if cacheControl is not None:
request.setHeader("Cache-Control", cacheControl)
# Security based headers
request.setHeader("X-Content-Type-Options", "nosniff")
request.setHeader("X-Frame-Options", "deny")
# Set our own headers
request.setHeader("Server", USER_AGENT)
return treq.content(sourceResponse)
@klein.route("/<hmacRequest>/<path:url>")
def proxyImage(request, hmacRequest, url):
hmacDigest = hmac.new(
SECRET_KEY, url,
digestmod=hashlib.sha224,
).hexdigest()
# if hmacDigest != hmacRequest: # TODO: Constant Time
# request.setResponseCode(404)
# return "checksum mismatch"
d = treq.get(
url.encode("utf8"),
headers={
"Accept": "images/*",
"User-Agent": USER_AGENT,
},
persistent=False,
timeout=TIMEOUT,
)
d.addCallback(processResponse, request)
return d
resource = klein.resource
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment