@@ -354,7 +354,70 @@ def poll_room_info(room, info_updated):
354
354
@rooms .post ("/room/<Room:room>/file" )
355
355
@auth .user_required
356
356
def upload_file (room ):
357
- """ upload a file to a room """
357
+ """
358
+ Uploads a file to a room.
359
+
360
+ Takes the request as binary in the body and takes other properties (specifically the suggested
361
+ filename) via submitted headers.
362
+
363
+ The user must have upload and posting permissions for the room. The file will have a default
364
+ lifetime of 1 hour, which is extended to 15 days (by default) when a post referencing the
365
+ uploaded file is posted or edited.
366
+
367
+ # URL Parameters
368
+
369
+ # Body
370
+
371
+ The body of the request is the raw bytes that make up the attachment body.
372
+
373
+ # Header parameters
374
+
375
+ ## Content-Type
376
+
377
+ This should be set to application/octet-stream. If the client has a strong reason to use
378
+ another content type then it may do so, but it is acceptable to always use
379
+ `application/octet-stream`.
380
+
381
+ ## Content-Disposition
382
+
383
+ The attachment filename should be provided via the `Content-Disposition` header of the request,
384
+ encoded as URI-encoded UTF-8 as per RFC 5987. Specifically, it should be formatted as:
385
+
386
+ Content-Disposition: attachment; filename*=UTF-8''filename.txt
387
+
388
+ where `filename.txt` is a utf-8 byte sequence with any bytes not in the following list encoded
389
+ using %xx URI-style encoding.
390
+
391
+ Non-encoded ascii characters: A-Z, a-z, 0-9, !, #, $, &, +, -, ., ^, _, `, |, ~. All other
392
+ characters shall be represented as their utf-8 byte sequence.
393
+
394
+ For instance, a file named `my 🎂.txt` (🎂 = U+1F382, with utf-8 representation 0xF0 0x9F 0x8E
395
+ 0x82) should specify the filename in the header as:
396
+
397
+ Content-Disposition: attachment; filename*=UTF-8''my%20%f0%9f%8e%82.txt
398
+
399
+ Filenames are not required as they are not always available (such as when uploading a pasted
400
+ image) but should be used when possible.
401
+
402
+ The filename, if provided, will be provided in the same format in the download header for the
403
+ file.
404
+
405
+ # Error status codes
406
+
407
+ - 403 Forbidden — Returned if the current user does not have permission to post messages or
408
+ upload files to the room.
409
+
410
+ - 404 Not Found — Returned if the room does not exist, or is configured as inaccessible (and
411
+ this user doesn't have access).
412
+
413
+ # Return value
414
+
415
+ On successful upload this endpoint returns a 201 (Created) status code (*not* 200), with a JSON
416
+ body containing an object with key:
417
+
418
+ - `id` — the numeric id of the upload. If the id is not referenced via a subsequent new post,
419
+ post edit, or room image request within one hour then the attachment will be deleted.
420
+ """
358
421
359
422
if not room .check_upload (g .user ):
360
423
abort (http .FORBIDDEN )
@@ -375,11 +438,58 @@ def upload_file(room):
375
438
376
439
377
440
@rooms .get ("/room/<Room:room>/file/<int:fileId>" )
378
- @rooms .get ("/room/<Room:room>/file/<int:fileId>/<filename>" )
379
441
@auth .read_required
380
- def serve_file (room , fileId , filename = None ):
442
+ def serve_file (room , fileId ):
381
443
"""
382
- serves a file uploaded to a room
444
+ Retrieves a file uploaded to the room.
445
+
446
+ Retrieves a file via its numeric id from the room, returning the file content directly as the
447
+ binary response body. The file's suggested filename (as provided by the uploader) is provided
448
+ in the Content-Disposition header, if available.
449
+
450
+ # URL Parameters
451
+
452
+ - `fileId` — The id of the attachment to download.
453
+
454
+ # Return value
455
+
456
+ On success the file content is returned as bytes in the response body. Additional information
457
+ is provided via response headers:
458
+
459
+ ## Content-Length
460
+
461
+ The size (in bytes) of the attachment.
462
+
463
+ ## Content-Type
464
+
465
+ Always `application-octet-stream` (even if the uploader specified something else).
466
+
467
+ ## Content-Disposition
468
+
469
+ This specifies the suggested filename as provided by the uploader, if present. The filename is
470
+ encoded using standard RFC 5987 encoding, for example:
471
+
472
+ Content-Disposition: attachment; filename*=UTF-8''filename.txt
473
+
474
+ See [the upload endpoint](#post-roomroomfile) for filename encoding details. If the attachment
475
+ was uploaded without a filename then this header will not include the filename component, i.e.:
476
+
477
+ Content-Disposition: attachment
478
+
479
+ ## Date
480
+
481
+ The timestamp at which this file was uploaded, as a standard HTTP date.
482
+
483
+ ## Expires
484
+
485
+ The timestamp at which this file is currently scheduled to expire, as a standard HTTP date.
486
+
487
+ # Error status codes
488
+
489
+ - 403 Forbidden — Returned if the current user does not have permission to read messages in the
490
+ room, e.g. because they are banned or the room permissions otherwise restrict access.
491
+
492
+ - 404 Not Found — Returned if the attachment does not exist in this room (or has expired).
383
493
"""
384
494
room_file = room .get_file (fileId )
385
495
if not room_file :
@@ -402,3 +512,26 @@ def serve_file(room, fileId, filename=None):
402
512
return Response (
403
513
response = f , status = 200 , content_type = 'application/octet-stream' , headers = headers
404
514
)
515
+
516
+
517
+ @rooms .get ("/room/<Room:room>/file/<int:fileId>/<filename>" )
518
+ @auth .read_required
519
+ def serve_file_with_ignored_filename (room , fileId , filename ):
520
+ """
521
+ Convenience endpoint for downloading file with a filename appended to the URL.
522
+
523
+ This endpoint is exactly identical to the version of the endpoint without a filename: the
524
+ suffixed filename in the request is simply ignored. This alias is provided only to make it
525
+ slightly more convenient to construct a URL containing a known filename, such as when using
526
+ command-line tools for debugging.
527
+
528
+ Most clients should simply use the non-suffixed endpoint instead.
529
+
530
+ # URL Parameters
531
+
532
+ - `fileId` — The id of the attachment to download.
533
+
534
+ - `filename` — Arbitrary filename of the attachment; this value is entirely ignored by SOGS.
535
+
536
+ """
537
+ return serve_file (room = room , fileId = fileId )
0 commit comments