Implemented GETs for the ContentRepoResource. It all actually appears to be working.

This commit is contained in:
Kegan Dougal 2014-08-18 17:18:39 +01:00
parent 590ab24c85
commit 58548ab557
3 changed files with 65 additions and 15 deletions

View file

@ -24,7 +24,7 @@ from twisted.python.log import PythonLoggingObserver
from twisted.web.resource import Resource from twisted.web.resource import Resource
from twisted.web.static import File from twisted.web.static import File
from twisted.web.server import Site 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.http.client import TwistedHttpClient
from synapse.api.urls import ( from synapse.api.urls import (
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX
@ -56,7 +56,7 @@ class SynapseHomeServer(HomeServer):
return File("webclient") # TODO configurable? return File("webclient") # TODO configurable?
def build_resource_for_content_repo(self): def build_resource_for_content_repo(self):
return FileUploadResource("uploads", self.auth) return ContentRepoResource("uploads", self.auth)
def build_db_pool(self): def build_db_pool(self):
""" Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we """ Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we

View file

@ -17,10 +17,13 @@
from syutil.jsonutil import ( from syutil.jsonutil import (
encode_canonical_json, encode_pretty_printed_json 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 synapse.util.stringutils import random_string
from twisted.internet import defer, reactor from twisted.internet import defer, reactor
from twisted.protocols.basic import FileSender
from twisted.web import server, resource from twisted.web import server, resource
from twisted.web.server import NOT_DONE_YET from twisted.web.server import NOT_DONE_YET
from twisted.web.util import redirectTo from twisted.web.util import redirectTo
@ -30,7 +33,7 @@ import collections
import json import json
import logging import logging
import os import os
import re
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -180,16 +183,25 @@ class RootRedirect(resource.Resource):
return resource.Resource.getChild(self, name, request) 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 isLeaf = True
def __init__(self, directory, auth, file_map_func=None): def __init__(self, directory, auth):
resource.Resource.__init__(self) resource.Resource.__init__(self)
self.directory = directory self.directory = directory
self.auth = auth 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): if not os.path.isdir(self.directory):
os.mkdir(self.directory) os.mkdir(self.directory)
@ -210,14 +222,19 @@ class FileUploadResource(resource.Resource):
main_part = random_string(24) main_part = random_string(24)
# suffix with a file extension if we can make one. This is nice to # 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 = "" suffix = ""
if request.requestHeaders.hasHeader("Content-Type"): if request.requestHeaders.hasHeader("Content-Type"):
content_type = request.requestHeaders.getRawHeaders( content_type = request.requestHeaders.getRawHeaders(
"Content-Type")[0] "Content-Type")[0]
suffix = "." + base64.urlsafe_b64encode(content_type)
if (content_type.split("/")[0].lower() in if (content_type.split("/")[0].lower() in
["image", "video", "audio"]): ["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) file_path = os.path.join(self.directory, prefix + main_part + suffix)
logger.info("User %s is uploading a file to path %s", logger.info("User %s is uploading a file to path %s",
@ -236,6 +253,37 @@ class FileUploadResource(resource.Resource):
defer.returnValue(file_path) 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): def render_POST(self, request):
self._async_render(request) self._async_render(request)
return server.NOT_DONE_YET return server.NOT_DONE_YET
@ -243,13 +291,14 @@ class FileUploadResource(resource.Resource):
@defer.inlineCallbacks @defer.inlineCallbacks
def _async_render(self, request): def _async_render(self, request):
try: 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: with open(fname, "wb") as f:
f.write(request.content.read()) f.write(request.content.read())
respond_with_json_bytes(request, 200, respond_with_json_bytes(request, 200,
json.dumps({"path": fname}), json.dumps({"content_token": fname}),
send_cors=True) send_cors=True)
except CodeMessageException as e: except CodeMessageException as e:

View file

@ -32,8 +32,9 @@ angular.module('mFileUpload', [])
console.log("Uploading " + file.name + "... to /matrix/content"); console.log("Uploading " + file.name + "... to /matrix/content");
matrixService.uploadContent(file).then( matrixService.uploadContent(file).then(
function(response) { function(response) {
console.log(" -> Successfully uploaded! Available at " + location.origin + response.data.url); var content_url = location.origin + "/matrix/content/" + response.data.content_token;
deferred.resolve(location.origin + response.data.url); console.log(" -> Successfully uploaded! Available at " + content_url);
deferred.resolve(content_url);
}, },
function(error) { function(error) {
console.log(" -> Failed to upload " + file.name); console.log(" -> Failed to upload " + file.name);