summary refs log tree commit diff
path: root/synapse/http/server.py
diff options
context:
space:
mode:
Diffstat (limited to 'synapse/http/server.py')
-rw-r--r--synapse/http/server.py71
1 files changed, 60 insertions, 11 deletions
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: