diff --git a/docs/api/fileops.rst b/docs/api/fileops.rst index 7cadb8c4..759dc69d 100644 --- a/docs/api/fileops.rst +++ b/docs/api/fileops.rst @@ -161,18 +161,10 @@ Upload file Content-Disposition: form-data; name="file"; filename="whistle_v2.gcode" Content-Type: application/octet-stream - ;Generated with Cura_SteamEngine 13.11.2 M109 T0 S220.000000 T0 - ;Sliced at: Wed 11-12-2013 16:53:12 - ;Basic settings: Layer height: 0.2 Walls: 0.8 Fill: 20 - ;Print time: #P_TIME# - ;Filament used: #F_AMNT#m #F_WGHT#g - ;Filament cost: #F_COST# - ;M190 S70 ;Uncomment to add your own bed temperature line - ;M109 S220 ;Uncomment to add your own temperature line - G21 ;metric values - G90 ;absolute positioning + G21 + G90 ... ------WebKitFormBoundaryDeC2E3iWbTv1PwMC Content-Disposition: form-data; name="select" @@ -188,7 +180,7 @@ Upload file HTTP/1.1 200 OK Content-Type: application/json - Location: http://example.com/api/files/sdcard/whistle_.gcode + Location: http://example.com/api/files/sdcard/whistle_v2.gcode { "files": { @@ -208,6 +200,46 @@ Upload file } } }, + "done": false + } + + **Example with UTF-8 encoded filename following RFC 5987** + + .. sourcecode:: http + + POST /api/files/local HTTP/1.1 + Host: example.com + X-Api-Key: abcdef... + Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDeC2E3iWbTv1PwMC + + ------WebKitFormBoundaryDeC2E3iWbTv1PwMC + Content-Disposition: form-data; name="file"; filename*=utf-8''20mm-%C3%BCml%C3%A4ut-b%C3%B6x.gcode + Content-Type: application/octet-stream + + M109 T0 S220.000000 + T0 + G21 + G90 + ... + ------WebKitFormBoundaryDeC2E3iWbTv1PwMC-- + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + Location: http://example.com/api/files/local/20mm-umlaut-box.gcode + + { + "files": { + "local": { + "name": "20mm-umlaut-box", + "origin": "local", + "refs": { + "resource": "http://example.com/api/files/local/whistle_v2.gcode", + "download": "http://example.com/downloads/files/local/whistle_v2.gcode" + } + } + }, "done": true } diff --git a/docs/api/general.rst b/docs/api/general.rst index 9e6a67fc..91e67357 100644 --- a/docs/api/general.rst +++ b/docs/api/general.rst @@ -74,7 +74,19 @@ Encoding OctoPrint uses UTF-8 as charset. -.. _sec-api-cross-origin: +That also includes headers in ``multipart/form-data`` requests, in order to allow the full UTF-8 range of characters +for uploaded filenames. If a ``multipart/form-data`` sub header cannot be decoded as UTF-8, OctoPrint will also attempt +to decode it as ISO-8859-1. + +Additionally, OctoPrint supports replacing the ``filename`` field in the ``Content-Disposition`` header of a +multipart field with a ``filename*`` field following `RFC 5987, Section 3.2 `_, +which allows defining the charset used for encoding the filename. If both ``filename`` and ``filename*`` fields are +present, following the recommendation of the RFC ``filename*`` will be used. + +For an example on how to send a request utilizing RFC 5987 for the ``filename*`` attribute, see the second example +in :ref:`Upload file `. + +.. _sec-api-general-crossorigin: Cross-origin requests ===================== diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index 3638ac45..ed9aca2d 100644 --- a/src/octoprint/server/util/tornado.py +++ b/src/octoprint/server/util/tornado.py @@ -303,7 +303,7 @@ class UploadStorageFallbackHandler(tornado.web.RequestHandler): filename = _extended_header_value(filename) except: # parse error, this is not RFC 5987 compliant after all - self._logger.warn("extended filename* value {!r} is not RFC 5987 compliant") + self._logger.warn("extended filename* value {!r} is not RFC 5987 compliant".format(filename)) self.send_error(400) return else: @@ -494,11 +494,11 @@ def _extended_header_value(value): # RFC 5987 section 3.2 from urllib import unquote encoding, _, value = value.split("'", 2) - return unquote(value).decode(encoding) + return unquote(octoprint.util.to_str(value, encoding="iso-8859-1")).decode(encoding) else: # no encoding provided, strip potentially present quotes and call it a day - return _strip_value_quotes(value) + return octoprint.util.to_unicode(_strip_value_quotes(value), encoding="utf-8") class WsgiInputContainer(object): diff --git a/tests/server/util/tornado.py b/tests/server/util/tornado.py index e6ab47a0..a765d66d 100644 --- a/tests/server/util/tornado.py +++ b/tests/server/util/tornado.py @@ -76,9 +76,14 @@ class StripValueQuotesTest(unittest.TestCase): class ExtendedHeaderValueTest(unittest.TestCase): @data( - ("", u""), + (u"", u""), (None, None), + (u'"quoted-string"', u"quoted-string"), + (u'"qüöted-string"', u"qüöted-string"), + (u"iso-8859-1'en'%A3%20rates", u"£ rates"), + (u"UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", u"£ and € rates"), ('"quoted-string"', u"quoted-string"), + ('"qüöted-string"', u"qüöted-string"), ("iso-8859-1'en'%A3%20rates", u"£ rates"), ("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", u"£ and € rates") )