-
-
Save dstufft/0dffb12201d235f35bc7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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