comparison sat/plugins/plugin_comp_file_sharing.py @ 3296:da443cf946ad

comp file sharing: CORS: - added CORS headers to allow using the HTTP server from an other domain - added `Content-Security-Policy`
author Goffi <goffi@goffi.org>
date Tue, 09 Jun 2020 06:21:23 +0200
parents 449dfbfcdbcc
children 91b5ae058c66
comparison
equal deleted inserted replaced
3295:9bc3fca290ab 3296:da443cf946ad
34 from sat.tools.common import files_utils 34 from sat.tools.common import files_utils
35 from sat.tools.common import tls 35 from sat.tools.common import tls
36 from sat.tools import stream 36 from sat.tools import stream
37 from twisted.internet import defer, reactor 37 from twisted.internet import defer, reactor
38 from twisted.words.protocols.jabber import error 38 from twisted.words.protocols.jabber import error
39 from twisted.web import server, resource, static 39 from twisted.web import server, resource, static, http
40 from wokkel import pubsub 40 from wokkel import pubsub
41 from wokkel import generic 41 from wokkel import generic
42 42
43 43
44 log = getLogger(__name__) 44 log = getLogger(__name__)
83 class HTTPFileServer(resource.Resource): 83 class HTTPFileServer(resource.Resource):
84 isLeaf = True 84 isLeaf = True
85 85
86 def errorPage(self, request, code): 86 def errorPage(self, request, code):
87 request.setResponseCode(code) 87 request.setResponseCode(code)
88 if code == 400: 88 if code == http.BAD_REQUEST:
89 brief = 'Bad Request' 89 brief = 'Bad Request'
90 details = "Your request is invalid" 90 details = "Your request is invalid"
91 elif code == 403: 91 elif code == http.FORBIDDEN:
92 brief = 'Forbidden' 92 brief = 'Forbidden'
93 details = "You're not allowed to use this resource" 93 details = "You're not allowed to use this resource"
94 elif code == 404: 94 elif code == http.NOT_FOUND:
95 brief = 'Not Found' 95 brief = 'Not Found'
96 details = "No resource found at this URL" 96 details = "No resource found at this URL"
97 else: 97 else:
98 brief = 'Error' 98 brief = 'Error'
99 details = "This resource can't be used" 99 details = "This resource can't be used"
111 elif media_type == 'application' and media_subtype == 'pdf': 111 elif media_type == 'application' and media_subtype == 'pdf':
112 return 'inline' 112 return 'inline'
113 else: 113 else:
114 return 'attachment' 114 return 'attachment'
115 115
116 def render(self, request):
117 request.setHeader("Access-Control-Allow-Origin", "*")
118 request.setHeader("Access-Control-Allow-Methods", "OPTIONS, HEAD, GET, PUT")
119 request.setHeader("Access-Control-Allow-Headers", "Content-Type, Xmpp-File-Path, Xmpp-File-No-Http")
120 request.setHeader("Access-Control-Allow-Credentials", "true")
121 return super().render(request)
122
123 def render_OPTIONS(self, request):
124 request.setResponseCode(http.OK)
125 return b""
126
116 def render_GET(self, request): 127 def render_GET(self, request):
117 try: 128 try:
118 request.upload_data 129 request.upload_data
119 except exceptions.DataError: 130 except exceptions.DataError:
120 return self.errorPage(request, 404) 131 return self.errorPage(request, http.NOT_FOUND)
121 132
122 defer.ensureDeferred(self.renderGet(request)) 133 defer.ensureDeferred(self.renderGet(request))
123 return server.NOT_DONE_YET 134 return server.NOT_DONE_YET
124 135
125 async def renderGet(self, request): 136 async def renderGet(self, request):
126 try: 137 try:
127 upload_id, filename = request.upload_data 138 upload_id, filename = request.upload_data
128 except exceptions.DataError: 139 except exceptions.DataError:
129 request.write(self.errorPage(request, 403)) 140 request.write(self.errorPage(request, http.FORBIDDEN))
130 request.finish() 141 request.finish()
131 return 142 return
132 found_files = await request.file_sharing.host.memory.getFiles( 143 found_files = await request.file_sharing.host.memory.getFiles(
133 client=None, peer_jid=None, perms_to_check=None, public_id=upload_id) 144 client=None, peer_jid=None, perms_to_check=None, public_id=upload_id)
134 if not found_files: 145 if not found_files:
135 request.write(self.errorPage(request, 404)) 146 request.write(self.errorPage(request, http.NOT_FOUND))
136 request.finish() 147 request.finish()
137 return 148 return
138 if len(found_files) > 1: 149 if len(found_files) > 1:
139 log.error(f"more that one files found for public id {upload_id!r}") 150 log.error(f"more that one files found for public id {upload_id!r}")
140 151
149 # thus we add a content disposition header 160 # thus we add a content disposition header
150 request.setHeader( 161 request.setHeader(
151 'Content-Disposition', 162 'Content-Disposition',
152 f"{disp_type}; filename*=UTF-8''{quote(found_file['name'])}" 163 f"{disp_type}; filename*=UTF-8''{quote(found_file['name'])}"
153 ) 164 )
165 # cf. https://xmpp.org/extensions/xep-0363.html#server
166 request.setHeader(
167 'Content-Security-Policy',
168 "default-src 'none'; frame-ancestors 'none';"
169 )
154 ret = file_res.render(request) 170 ret = file_res.render(request)
155 if ret != server.NOT_DONE_YET: 171 if ret != server.NOT_DONE_YET:
156 # HEAD returns directly the result (while GET use a produced) 172 # HEAD returns directly the result (while GET use a produced)
157 request.write(ret) 173 request.write(ret)
158 request.finish() 174 request.finish()
164 async def renderPut(self, request): 180 async def renderPut(self, request):
165 try: 181 try:
166 client, upload_request = request.upload_request_data 182 client, upload_request = request.upload_request_data
167 upload_id, filename = request.upload_data 183 upload_id, filename = request.upload_data
168 except AttributeError: 184 except AttributeError:
169 request.write(self.errorPage(request, 400)) 185 request.write(self.errorPage(request, http.BAD_REQUEST))
170 request.finish() 186 request.finish()
171 return 187 return
172 188
173 # at this point request is checked and file is buffered, we can store it 189 # at this point request is checked and file is buffered, we can store it
174 # we close the content here, before registering the file 190 # we close the content here, before registering the file
186 await request.file_sharing.registerReceivedFile( 202 await request.file_sharing.registerReceivedFile(
187 client, upload_request.from_, file_data, tmp_file_path, 203 client, upload_request.from_, file_data, tmp_file_path,
188 public_id=upload_id, 204 public_id=upload_id,
189 ) 205 )
190 206
191 request.setResponseCode(201) 207 request.setResponseCode(http.CREATED)
192 request.finish() 208 request.finish()
193 209
194 210
195 class FileSharingRequest(server.Request): 211 class FileSharingRequest(server.Request):
196 212