summary refs log tree commit diff
diff options
context:
space:
mode:
authorKegan Dougal <kegan@matrix.org>2014-08-18 17:18:39 +0100
committerKegan Dougal <kegan@matrix.org>2014-08-18 17:18:54 +0100
commit58548ab557bbbdd95644997e00777b0aec8532bc (patch)
treeee70e07d5ef30fd651637a1d41ec856f1cdb28ba
parenths: Make the uploads directory if it doesn't exist. Namespace uploads by the ... (diff)
downloadsynapse-58548ab557bbbdd95644997e00777b0aec8532bc.tar.xz
Implemented GETs for the ContentRepoResource. It all actually appears to be working.
-rwxr-xr-xsynapse/app/homeserver.py4
-rw-r--r--synapse/http/server.py71
-rw-r--r--webclient/components/fileUpload/file-upload-service.js5
3 files changed, 65 insertions, 15 deletions
diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py
index f7c1da9201..ca102236cf 100755
--- a/synapse/app/homeserver.py
+++ b/synapse/app/homeserver.py
@@ -24,7 +24,7 @@ from twisted.python.log import PythonLoggingObserver
 from twisted.web.resource import Resource
 from twisted.web.static import File
 from twisted.web.server import Site
-from synapse.http.server import JsonResource, RootRedirect, FileUploadResource
+from synapse.http.server import JsonResource, RootRedirect, ContentRepoResource
 from synapse.http.client import TwistedHttpClient
 from synapse.api.urls import (
     CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX
@@ -56,7 +56,7 @@ class SynapseHomeServer(HomeServer):
         return File("webclient")  # TODO configurable?
 
     def build_resource_for_content_repo(self):
-        return FileUploadResource("uploads", self.auth)
+        return ContentRepoResource("uploads", self.auth)
 
     def build_db_pool(self):
         """ Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we
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:
diff --git a/webclient/components/fileUpload/file-upload-service.js b/webclient/components/fileUpload/file-upload-service.js
index 0826666fe4..d620e6a4d0 100644
--- a/webclient/components/fileUpload/file-upload-service.js
+++ b/webclient/components/fileUpload/file-upload-service.js
@@ -32,8 +32,9 @@ angular.module('mFileUpload', [])
         console.log("Uploading " + file.name + "... to /matrix/content");
         matrixService.uploadContent(file).then(
             function(response) {
-                console.log("   -> Successfully uploaded! Available at " + location.origin + response.data.url);
-                deferred.resolve(location.origin + response.data.url);
+                var content_url = location.origin + "/matrix/content/" + response.data.content_token;
+                console.log("   -> Successfully uploaded! Available at " + content_url);
+                deferred.resolve(content_url);
             },
             function(error) {
                 console.log("   -> Failed to upload "  + file.name);