diff --git a/synapse/http/server.py b/synapse/http/server.py
index 8502416fca..9b6ae993ab 100644
--- a/synapse/http/server.py
+++ b/synapse/http/server.py
@@ -17,10 +17,13 @@
from syutil.jsonutil import (
encode_canonical_json, encode_pretty_printed_json
)
-from synapse.api.errors import cs_exception, SynapseError, CodeMessageException
+from synapse.api.errors import (
+ cs_exception, SynapseError, CodeMessageException, Codes, cs_error
+)
from synapse.util.stringutils import random_string
from twisted.internet import defer, reactor
+from twisted.protocols.basic import FileSender
from twisted.web import server, resource
from twisted.web.server import NOT_DONE_YET
from twisted.web.util import redirectTo
@@ -30,7 +33,7 @@ import collections
import json
import logging
import os
-
+import re
logger = logging.getLogger(__name__)
@@ -180,16 +183,25 @@ class RootRedirect(resource.Resource):
return resource.Resource.getChild(self, name, request)
-class FileUploadResource(resource.Resource):
+class ContentRepoResource(resource.Resource):
+ """Provides file uploading and downloading.
+
+ Uploads are POSTed to wherever this Resource is linked to. This resource
+ returns a "content token" which can be used to GET this content again. The
+ token is typically a path, but it may not be.
+
+ In this case, the token contains 3 sections:
+ - User ID base64d (for namespacing content to each user)
+ - random string
+ - Content type base64d (so we can return it when clients GET it)
+
+ """
isLeaf = True
- def __init__(self, directory, auth, file_map_func=None):
+ def __init__(self, directory, auth):
resource.Resource.__init__(self)
self.directory = directory
self.auth = auth
- if not file_map_func:
- file_map_func = self.map_request_to_name
- self.get_name_for_request = file_map_func
if not os.path.isdir(self.directory):
os.mkdir(self.directory)
@@ -210,14 +222,19 @@ class FileUploadResource(resource.Resource):
main_part = random_string(24)
# suffix with a file extension if we can make one. This is nice to
- # provide a hint to clients on the file information.
+ # provide a hint to clients on the file information. We will also reuse
+ # this info to spit back the content type to the client.
suffix = ""
if request.requestHeaders.hasHeader("Content-Type"):
content_type = request.requestHeaders.getRawHeaders(
"Content-Type")[0]
+ suffix = "." + base64.urlsafe_b64encode(content_type)
if (content_type.split("/")[0].lower() in
["image", "video", "audio"]):
- suffix = "." + content_type.split("/")[-1]
+ file_ext = content_type.split("/")[-1]
+ # be a little paranoid and only allow a-z
+ file_ext = re.sub("[^a-z]", "", file_ext)
+ suffix += "." + file_ext
file_path = os.path.join(self.directory, prefix + main_part + suffix)
logger.info("User %s is uploading a file to path %s",
@@ -236,6 +253,37 @@ class FileUploadResource(resource.Resource):
defer.returnValue(file_path)
+ def render_GET(self, request):
+ # no auth here on purpose, to allow anyone to view, even across home
+ # servers.
+
+ # TODO: A little crude here, we could do this better.
+ filename = request.path.split(self.directory + "/")[1]
+ # be paranoid
+ filename = re.sub("[^0-9A-z.-_]", "", filename)
+
+ file_path = self.directory + "/" + filename
+ if os.path.isfile(file_path):
+ # filename has the content type
+ base64_contentype = filename.split(".")[1]
+ content_type = base64.urlsafe_b64decode(base64_contentype)
+ logger.info("Sending file %s", file_path)
+ f = open(file_path, 'rb')
+ request.setHeader('Content-Type', content_type)
+ d = FileSender().beginFileTransfer(f, request)
+ def cbFinished(ignored):
+ f.close()
+ request.finish()
+ d.addCallback(cbFinished)
+ else:
+ respond_with_json_bytes(
+ request,
+ 404,
+ json.dumps(cs_error("Not found", code=Codes.NOT_FOUND)),
+ send_cors=True)
+
+ return server.NOT_DONE_YET
+
def render_POST(self, request):
self._async_render(request)
return server.NOT_DONE_YET
@@ -243,13 +291,14 @@ class FileUploadResource(resource.Resource):
@defer.inlineCallbacks
def _async_render(self, request):
try:
- fname = yield self.get_name_for_request(request)
+ fname = yield self.map_request_to_name(request)
+ # TODO I have a suspcious feeling this is just going to block
with open(fname, "wb") as f:
f.write(request.content.read())
respond_with_json_bytes(request, 200,
- json.dumps({"path": fname}),
+ json.dumps({"content_token": fname}),
send_cors=True)
except CodeMessageException as e:
|