diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index d4c79c2e..bfb6508a 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -5,7 +5,10 @@ This is a bug and feature tracker, please only use it to report bugs or request features within OctoPrint (not OctoPi, not any OctoPrint plugins and not unofficial OctoPrint versions). Mark requests with a [Request] prefix in the title please. Fully fill out the bug reporting -template for bug reports. +template for bug reports. Do not delete any lines from the template but +those enclosed in [ and ] - and those please do delete, they are only +provided for your information and removing them makes your ticket more +readable :) Do not seek support here ("I need help with ..."), that belongs on the mailing list or the G+ community (both linked in the "guidelines @@ -17,11 +20,12 @@ Thank you! #### What were you doing? -[Please be as specific as possible here. The maintainers will need to reproduce -your issue in order to fix it and that is not possible if they don't know -what you did to get it to happen in the first place. If you encountered -a problem with specific files of any sorts, make sure to also include a link to a file -with which to reproduce the problem.] +[Please be as specific as possible here. The maintainers will need to +reproduce your issue in order to fix it and that is not possible if they +don't know what you did to get it to happen in the first place. + +If you encountered a problem with specific files of any sorts, make sure +to also include a link to a file with which to reproduce the problem.] #### What did you expect to happen? @@ -29,7 +33,7 @@ with which to reproduce the problem.] #### Branch & Commit or Version of OctoPrint -[Can be found in the lower left corner of the web interface.] +[Can be found in the lower left corner of the web interface. ALWAYS INCLUDE.] #### Printer model & used firmware incl. version @@ -41,12 +45,17 @@ with which to reproduce the problem.] #### Link to octoprint.log -[On gist.github.com or pastebin.com. Always include and never truncate.] +[On gist.github.com or pastebin.com. ALWAYS INCLUDE and never truncate.] #### Link to contents of terminal tab or serial.log [On gist.github.com or pastebin.com. If applicable, always include if unsure or -reporting communication issues. Never truncate.] +reporting communication issues. Never truncate. + +serial.log is usually not written due to performance reasons and must be +enabled explicitly. Provide at the very least the contents of your +terminal tab at the time of the bug occurence, even if you do not have +a serial.log.] #### Link to contents of Javascript console in the browser diff --git a/CHANGELOG.md b/CHANGELOG.md index 200ccfd8..f9a2eb65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,27 @@ * [#1047](https://github.com/foosel/OctoPrint/issues/1047) - Fixed 90 degree webcam rotation for iOS Safari. +## 1.2.16rc1 (2016-09-08) + +### Improvements + + * [#1434](https://github.com/foosel/OctoPrint/issues/1434): Make sure to sanitize any file names in the upload folder that do not match OctoPrint's file name "sanitization standard" automatically when creating a file listing. This should solve issues with UI functionality like selecting a file for printing or deleting a file to not work with files that were uploaded manually to the ``uploads`` folder. As a side note: Please don't do this, use the ``watched`` folder if you want to SCP/FTP/copy files directly to OctoPrint. + * [#1434](https://github.com/foosel/OctoPrint/issues/1434): Allow `[` and `]` in uploaded file names. + * [#1481](https://github.com/foosel/OctoPrint/issues/1481): Bring back non-fuzzy layer time estimates in the GCODE viewer. + * Improved fuzzy print time displays in the frontend. Rounding now takes overall duration into account - durations over a day will be rounded up/down to half days, durations over an hour will be rounded up/down to half hours, durations over 30min will be rounded to 10min segments, durations below 30min will be rounded up or down to the next minute depending on the seconds and finally if we are talking about less than a minute, durations over 30s will return "less than a minute", durations under 30s will return "a couple of seconds". + * Improved intermediary loading page: Don't report server as ready and reload until preliminary caching has been done, IF preliminary caching will be done. + * Added release channels to OctoPrint's bundled Software Update plugin. You will now be able to subscribe to OctoPrint's `maintenance` or `devel` release candidates in addition to stable versions. [Read more about Release Channels on the wiki](https://github.com/foosel/OctoPrint/wiki/Using-Release-Channels). + +### Bug Fixes + + * [#1448](https://github.com/foosel/OctoPrint/issues/1448): Don't "eat" first line of the pause script after a pause triggering `M0` but send it to the printer instead + * [#1477](https://github.com/foosel/OctoPrint/issues/1477): Only report files enqueued for analysis which actually are (as in, don't claim to have queued STL files for GCODE analysis) + * [#1478](https://github.com/foosel/OctoPrint/issues/1478): Don't display inaccurate linear estimate ("6 days remaining") until 30 *minutes* have passed, even if nothing else is available. Potentially related to [#1428](https://github.com/foosel/OctoPrint/issues/1428). + * [#1479](https://github.com/foosel/OctoPrint/issues/1479): Make sure set cookies are post fixed with a port specific suffix and that the path they are set on takes the script root from the request into account. + * [#1483](https://github.com/foosel/OctoPrint/issues/1483): Filenames in file uploads may also now be encoded in ISO-8859-1, as defined in [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4). Solves an issue when sending files with non-ASCII-characters in the file name from Slic3r. + +([Commits](https://github.com/foosel/OctoPrint/compare/1.2.15...1.2.16rc1)) + ## 1.2.15 (2016-07-30) ### Improvements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 733c9241..de99d934 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,17 +123,18 @@ following section *completely*. Thank you! :) ### What should I include in a bug report? -Always use the following template (you can remove what's within `[...]`, that's +Always use the following template (please remove what's within `[...]`, that's only provided here as some additional information for you), **even if only adding a "me too" to an existing ticket**: #### What were you doing? - [Please be as specific as possible here. The maintainers will need to reproduce - your issue in order to fix it and that is not possible if they don't know - what you did to get it to happen in the first place. If you encountered - a problem with specific files of any sorts, make sure to also include a link to a file - with which to reproduce the problem.] + [Please be as specific as possible here. The maintainers will need to + reproduce your issue in order to fix it and that is not possible if they + don't know what you did to get it to happen in the first place. + + If you encountered a problem with specific files of any sorts, make sure + to also include a link to a file with which to reproduce the problem.] #### What did you expect to happen? @@ -141,7 +142,7 @@ only provided here as some additional information for you), **even if only addin #### Branch & Commit or Version of OctoPrint - [Can be found in the lower left corner of the web interface.] + [Can be found in the lower left corner of the web interface. ALWAYS INCLUDE.] #### Printer model & used firmware incl. version @@ -153,12 +154,17 @@ only provided here as some additional information for you), **even if only addin #### Link to octoprint.log - [On gist.github.com or pastebin.com. Always include and never truncate.] + [On gist.github.com or pastebin.com. ALWAYS INCLUDE and never truncate.] #### Link to contents of terminal tab or serial.log [On gist.github.com or pastebin.com. If applicable, always include if unsure or - reporting communication issues. Never truncate.] + reporting communication issues. Never truncate. + + serial.log is usually not written due to performance reasons and must be + enabled explicitly. Provide at the very least the contents of your + terminal tab at the time of the bug occurence, even if you do not have + a serial.log.] #### Link to contents of Javascript console in the browser @@ -376,6 +382,7 @@ the local version identifier to allow for an exact determination of the active c tickets as well, explained issue with "me too" red herrings. * 2016-03-14: Some more requirements for PRs, and a PR template. * 2016-06-08: New `prerelease` and `rc` branches explained. + * 2016-09-09: New `rc/*` branches explained. ## Footnotes * [1] - If you are wondering why, the problem is that anything that you add diff --git a/SUPPORTERS.md b/SUPPORTERS.md index 659d13df..9c51ca64 100644 --- a/SUPPORTERS.md +++ b/SUPPORTERS.md @@ -1,4 +1,4 @@ -# Supporters +# Supporters Development of this version of OctoPrint wouldn't have been possible without [financial support by the community](http://octoprint.org/support-octoprint/) - @@ -12,11 +12,13 @@ thanks to everyone who contributed! * Arnljot Arntsen * Aurelio Bernal Ramírez * Bart Zudell + * Boris Hussein * Brad Jackson * Brad Mooneyham * Brent Fiegle * Brian E. Tyler * Charles Mitchell + * Christopher Day * Christian Petropolis * COLLE+McVOY * CreativeTools @@ -33,6 +35,7 @@ thanks to everyone who contributed! * J. Eckert * Jason Galarneau * Joe Korzeniewski + * Josh Daniels * Joshua Gregory * Kaile Riser * Kale Stedman @@ -45,11 +48,11 @@ thanks to everyone who contributed! * Mark Walker * Masayoshi Mitsui * Michael McDargh - * Miguel Angel Salmeron * Mikey * Miles Flavel * Mohammed Khorakiwala * Noe Ruiz + * Patrick McGinnis * Paul Generes * Peter Grace * Peter Schmehl @@ -64,8 +67,9 @@ thanks to everyone who contributed! * stefi davis * Steven Pearson * Sven Mueller + * Terrance Shaw * Thomas Hatley - * Thomas Sanladerer * Trent Shumay + * Xpandedreality -and 844 more wonderful people pledging on the [Patreon campaign](https://patreon.com/foosel)! \ No newline at end of file +and 905 more wonderful people pledging on the [Patreon campaign](https://patreon.com/foosel)! diff --git a/docs/api/fileops.rst b/docs/api/fileops.rst index 3ac67bfe..c6b6806d 100644 --- a/docs/api/fileops.rst +++ b/docs/api/fileops.rst @@ -168,18 +168,10 @@ Upload file or create folder 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" @@ -195,7 +187,7 @@ Upload file or create folder 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": { @@ -217,6 +209,46 @@ Upload file or create folder } } }, + "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/filemanager/storage.py b/src/octoprint/filemanager/storage.py index 490ced2f..3b186261 100644 --- a/src/octoprint/filemanager/storage.py +++ b/src/octoprint/filemanager/storage.py @@ -496,7 +496,7 @@ class LocalFileStorage(StorageInterface): else: path = self.basefolder base = "" - return self._list_folder(path, base=base, filter=filter, recursive=recursive) + return self._list_folder(path, base=base, entry_filter=filter, recursive=recursive) def add_folder(self, path, ignore_existing=True): path, name = self.sanitize(path) @@ -805,6 +805,30 @@ class LocalFileStorage(StorageInterface): raise ValueError("path not contained in base folder: {path}".format(**locals())) return path + def _sanitize_entry(self, entry, path, entry_path): + sanitized = self.sanitize_name(entry) + if sanitized != entry: + # entry is not sanitized yet, let's take care of that + sanitized_path = os.path.join(path, sanitized) + sanitized_name, sanitized_ext = os.path.splitext(sanitized) + + counter = 1 + while os.path.exists(sanitized_path): + counter += 1 + sanitized = self.sanitize_name("{}_({}){}".format(sanitized_name, counter, sanitized_ext)) + sanitized_path = os.path.join(path, sanitized) + + try: + shutil.move(entry_path, sanitized_path) + + self._logger.info("Sanitized \"{}\" to \"{}\"".format(entry_path, sanitized_path)) + return sanitized, sanitized_path + except: + self._logger.exception("Error while trying to rename \"{}\" to \"{}\", ignoring file".format(entry_path, sanitized_path)) + raise + + return entry, entry_path + def path_in_storage(self, path): if isinstance(path, (tuple, list)): path = self.join_path(*path) @@ -1044,7 +1068,10 @@ class LocalFileStorage(StorageInterface): if metadata_dirty: self._save_metadata(path, metadata) - def _list_folder(self, path, base="", filter=None, recursive=True): + def _list_folder(self, path, base="", entry_filter=None, recursive=True, **kwargs): + if entry_filter is None: + entry_filter = kwargs.get("filter", None) + metadata = self._get_metadata(path) if not metadata: metadata = dict() @@ -1058,27 +1085,11 @@ class LocalFileStorage(StorageInterface): entry_path = os.path.join(path, entry) - sanitized = self.sanitize_name(entry) - if sanitized != entry: - # entry is not sanitized yet, let's take care of that - sanitized_path = os.path.join(path, sanitized) - sanitized_name, sanitized_ext = os.path.splitext(sanitized) - - counter = 1 - while os.path.exists(sanitized_path): - counter += 1 - sanitized = self.sanitize_name("{}_({}){}".format(sanitized_name, counter, sanitized_ext)) - sanitized_path = os.path.join(path, sanitized) - - try: - shutil.move(entry_path, sanitized_path) - - self._logger.info("Sanitized \"{}\" to \"{}\"".format(entry_path, sanitized_path)) - entry = sanitized - entry_path = sanitized_path - except: - self._logger.exception("Error while trying to rename \"{}\" to \"{}\", ignoring file".format(entry_path, sanitized_path)) - continue + try: + entry, entry_path = self._sanitize_entry(entry, path, entry_path) + except: + # error while trying to rename the file, we'll continue here and ignore it + continue path_in_location = entry if not base else base + entry @@ -1099,7 +1110,7 @@ class LocalFileStorage(StorageInterface): # TODO extract model hash from source if possible to recreate link - if not filter or filter(entry, entry_data): + if not entry_filter or entry_filter(entry, entry_data): # only add files passing the optional filter extended_entry_data = dict() extended_entry_data.update(entry_data) @@ -1123,11 +1134,11 @@ class LocalFileStorage(StorageInterface): type_path=["folder"] ) if recursive: - sub_result = self._list_folder(entry_path, base=path_in_location + "/", filter=filter, + sub_result = self._list_folder(entry_path, base=path_in_location + "/", entry_filter=entry_filter, recursive=recursive) entry_data["children"] = sub_result - if not filter or filter(entry, entry_data): + if not entry_filter or entry_filter(entry, entry_data): def get_size(): total_size = 0 for element in entry_data["children"].values(): diff --git a/src/octoprint/plugins/softwareupdate/templates/_snippets/plugins/softwareupdate/releaseChannel.jinja2 b/src/octoprint/plugins/softwareupdate/templates/_snippets/plugins/softwareupdate/releaseChannel.jinja2 index 59c6720e..6133e9b5 100644 --- a/src/octoprint/plugins/softwareupdate/templates/_snippets/plugins/softwareupdate/releaseChannel.jinja2 +++ b/src/octoprint/plugins/softwareupdate/templates/_snippets/plugins/softwareupdate/releaseChannel.jinja2 @@ -2,5 +2,6 @@
+ {{ _('Make sure you have read "Using Release Channels" before switching to a release channel other than "Stable"', url="https://github.com/foosel/OctoPrint/wiki/Using-Release-Channels") }}
diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index a6875504..b36a71ab 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -321,6 +321,14 @@ class ReverseProxiedEnvironment(object): environ["HTTP_HOST"] = host environ["SERVER_NAME"] = server environ["SERVER_PORT"] = port + + elif environ.get("HTTP_HOST", None) is not None: + # if we have a Host header, we use that and make sure our server name and port properties match it + host = environ["HTTP_HOST"] + server, port = host_to_server_and_port(host, url_scheme) + environ["SERVER_NAME"] = server + environ["SERVER_PORT"] = port + else: # else we take a look at the server and port headers and if we have # something there we derive the host from it @@ -335,15 +343,12 @@ class ReverseProxiedEnvironment(object): if port is not None: environ["SERVER_PORT"] = port - # make sure HTTP_HOST matches SERVER_NAME and SERVER_PORT - expected_server, expected_port = host_to_server_and_port(environ.get("HTTP_HOST", None), url_scheme) - if expected_server != environ["SERVER_NAME"] or expected_port != environ["SERVER_PORT"]: - # there's a difference, fix it! - if url_scheme == "http" and environ["SERVER_PORT"] == "80" or url_scheme == "https" and environ["SERVER_PORT"] == "443": - # default port for scheme, can be skipped - environ["HTTP_HOST"] = environ["SERVER_NAME"] - else: - environ["HTTP_HOST"] = environ["SERVER_NAME"] + ":" + environ["SERVER_PORT"] + # reconstruct host header + if url_scheme == "http" and environ["SERVER_PORT"] == "80" or url_scheme == "https" and environ["SERVER_PORT"] == "443": + # default port for scheme, can be skipped + environ["HTTP_HOST"] = environ["SERVER_NAME"] + else: + environ["HTTP_HOST"] = environ["SERVER_NAME"] + ":" + environ["SERVER_PORT"] # call wrapped app with rewritten environment return environ @@ -364,10 +369,16 @@ class OctoPrintFlaskRequest(flask.Request): # strip cookie_suffix from all cookies in the request, return result cookies = flask.Request.cookies.__get__(self) - def cookie_name_converter(key): - return key[:-len(self.cookie_suffix)] if key.endswith(self.cookie_suffix) else key + result = dict() + desuffixed = dict() + for key, value in cookies.items(): + if key.endswith(self.cookie_suffix): + desuffixed[key[:-len(self.cookie_suffix)]] = value + else: + result[key] = value - return dict((cookie_name_converter(key), value) for key, value in cookies.items()) + result.update(desuffixed) + return result @cached_property def server_name(self): diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index 481a6ace..38e0e0d0 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/src/octoprint/translations/de/LC_MESSAGES/messages.mo b/src/octoprint/translations/de/LC_MESSAGES/messages.mo index 0b6c6117..389721a9 100644 Binary files a/src/octoprint/translations/de/LC_MESSAGES/messages.mo and b/src/octoprint/translations/de/LC_MESSAGES/messages.mo differ diff --git a/src/octoprint/translations/de/LC_MESSAGES/messages.po b/src/octoprint/translations/de/LC_MESSAGES/messages.po index a17b2b1e..bd7cd773 100644 --- a/src/octoprint/translations/de/LC_MESSAGES/messages.po +++ b/src/octoprint/translations/de/LC_MESSAGES/messages.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: OctoPrint\n" "Report-Msgid-Bugs-To: i18n@octoprint.org\n" -"POT-Creation-Date: 2016-09-07 12:06+0200\n" -"PO-Revision-Date: 2016-09-07 12:09+0100\n" +"POT-Creation-Date: 2016-09-09 11:35+0200\n" +"PO-Revision-Date: 2016-09-09 11:48+0100\n" "Last-Translator: Gina Häußge \n" "Language: de\n" "Language-Team: German (http://www.transifex.com/projects/p/octoprint/" @@ -190,7 +190,6 @@ msgid "Overwrite existing file" msgstr "Vorhandene Datei überschreiben" #: src/octoprint/plugins/cura/templates/cura_settings.jinja2:113 -#, fuzzy msgid "" "\n" " You can import your existing profile .ini files " @@ -738,7 +737,7 @@ msgstr "Repository-Cache TTL" #: src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2:239 #: src/octoprint/plugins/softwareupdate/templates/softwareupdate.jinja2:26 -#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:107 +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:108 #: src/octoprint/templates/dialogs/confirmation.jinja2:11 #: src/octoprint/templates/dialogs/slicing.jinja2:50 #: src/octoprint/templates/sidebar/state.jinja2:25 @@ -746,7 +745,7 @@ msgid "Cancel" msgstr "Abbruch" #: src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2:240 -#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:108 +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:109 msgid "Save" msgstr "Speichern" @@ -1063,7 +1062,18 @@ msgstr "Versionstracking für OctoPrint" msgid "OctoPrint Release Channel" msgstr "OctoPrint Release Channel" -#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:96 +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:93 +#, python-format +msgid "" +"Make sure you have read \"Using Release Channels\" before switching to a " +"release channel other than \"Stable\"" +msgstr "" +"Bitte lies " +"\"Using Release Channels\" bevor du auf einen anderen Release Channel " +"als \"Stable\" wechselst" + +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:97 msgid "Version cache TTL" msgstr "TTL des Versionscaches" diff --git a/tests/server/util/flask.py b/tests/server/util/flask.py index e18e2dfb..b732416c 100644 --- a/tests/server/util/flask.py +++ b/tests/server/util/flask.py @@ -103,27 +103,29 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): "wsgi.url_scheme": "https" }), - # server and port headers set -> host derived with port + # host set, server and port differ -> updated, standard port ({ - "HTTP_X_FORWARDED_SERVER": "example2.com", - "HTTP_X_FORWARDED_PORT": "444", - "HTTP_X_FORWARDED_PROTO": "https" - }, { - "HTTP_HOST": "example2.com:444", - "SERVER_NAME": "example2.com", - "SERVER_PORT": "444", - "wsgi.url_scheme": "https" - }), + "HTTP_HOST": "example.com", + "wsgi.url_scheme": "https", + "SERVER_NAME": "localhost", + "SERVER_PORT": "80" + }, { + "HTTP_HOST": "example.com", + "SERVER_NAME": "example.com", + "SERVER_PORT": "443", + }), - # server and port headers set, standard port -> host derived, no port + # host set, server and port differ -> updated, non standard port ({ - "HTTP_X_FORWARDED_SERVER": "example.com", - "HTTP_X_FORWARDED_PORT": "80", - }, { - "HTTP_HOST": "example.com", + "HTTP_HOST": "example.com:444", + "wsgi.url_scheme": "https", + "SERVER_NAME": "localhost", + "SERVER_PORT": "80" + }, { + "HTTP_HOST": "example.com:444", "SERVER_NAME": "example.com", - "SERVER_PORT": "80", - }), + "SERVER_PORT": "444", + }), # multiple scheme entries -> only use first one ({ @@ -132,7 +134,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): "wsgi.url_scheme": "https" }), - # host = none -> should never happen but you never know... + # host = none (should never happen but you never know) -> server & port used for reconstruction ({ "HTTP_HOST": None, "HTTP_X_FORWARDED_SERVER": "example.com", @@ -158,6 +160,67 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): self.assertDictEqual(merged_expected, actual) + @data( + # server and port headers set -> host derived with port + ({ + "SERVER_NAME": "example2.com", + "SERVER_PORT": "444", + "HTTP_X_FORWARDED_PROTO": "https" + }, { + "HTTP_HOST": "example2.com:444", + "SERVER_NAME": "example2.com", + "SERVER_PORT": "444", + "wsgi.url_scheme": "https" + }), + + # server and port headers set, standard port -> host derived, no port + ({ + "SERVER_NAME": "example.com", + "SERVER_PORT": "80", + }, { + "HTTP_HOST": "example.com", + "SERVER_NAME": "example.com", + "SERVER_PORT": "80", + }), + + # server and port forwarded headers set -> host derived with port + ({ + "HTTP_X_FORWARDED_SERVER": "example2.com", + "HTTP_X_FORWARDED_PORT": "444", + "HTTP_X_FORWARDED_PROTO": "https" + }, { + "HTTP_HOST": "example2.com:444", + "SERVER_NAME": "example2.com", + "SERVER_PORT": "444", + "wsgi.url_scheme": "https" + }), + + # server and port forwarded headers set, standard port -> host derived, no port + ({ + "HTTP_X_FORWARDED_SERVER": "example.com", + "HTTP_X_FORWARDED_PORT": "80", + }, { + "HTTP_HOST": "example.com", + "SERVER_NAME": "example.com", + "SERVER_PORT": "80", + }), + ) + @unpack + def test_nohost(self, environ, expected): + reverse_proxied = ReverseProxiedEnvironment() + + merged_environ = dict(standard_environ) + merged_environ.update(environ) + del merged_environ["HTTP_HOST"] + + actual = reverse_proxied(merged_environ) + + merged_expected = dict(standard_environ) + merged_expected.update(environ) + merged_expected.update(expected) + + self.assertDictEqual(merged_expected, actual) + @data( # prefix overridden ({ @@ -195,57 +258,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): "SERVER_PORT": "81" }), - # server overridden - ({ - "server": "example.com" - }, { - }, { - "HTTP_HOST": "example.com:5000", - "SERVER_NAME": "example.com", - "SERVER_PORT": "5000" - }), - - # port overridden, standard port - ({ - "port": "80" - }, { - }, { - "HTTP_HOST": "localhost", - "SERVER_PORT": "80" - }), - - # port overridden, non standard port - ({ - "port": "81" - }, { - }, { - "HTTP_HOST": "localhost:81", - "SERVER_PORT": "81" - }), - - # server and port overridden, default port - ({ - "server": "example.com", - "port": "80" - }, { - }, { - "HTTP_HOST": "example.com", - "SERVER_NAME": "example.com", - "SERVER_PORT": "80" - }), - - # server and port overridden, non default port - ({ - "server": "example.com", - "port": "81" - }, { - }, { - "HTTP_HOST": "example.com:81", - "SERVER_NAME": "example.com", - "SERVER_PORT": "81" - }), - - # prefix not really overridden + # prefix not really overridden, forwarded headers take precedence ({ "prefix": "/octoprint" }, { @@ -253,7 +266,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): }, { }), - # scheme not really overridden + # scheme not really overridden, forwarded headers take precedence ({ "scheme": "https" }, { @@ -261,7 +274,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): }, { }), - # scheme 2 not really overridden + # scheme 2 not really overridden, forwarded headers take precedence ({ "scheme": "https" }, { @@ -269,7 +282,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): }, { }), - # host not really overridden + # host not really overridden, forwarded headers take precedence ({ "host": "example.com:444" }, { @@ -277,7 +290,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): }, { }), - # server not really overridden + # server not really overridden, forwarded headers take precedence ({ "server": "example.com" }, { @@ -285,12 +298,20 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): }, { }), - # port not really overridden + # port not really overridden, forwarded headers take precedence ({ "port": "444" }, { "HTTP_X_FORWARDED_PORT": "5000" }, { + }), + + # server and port not really overridden, Host header wins + ({ + "server": "example.com", + "port": "80" + }, { + }, { }) ) @unpack @@ -308,6 +329,74 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): self.assertDictEqual(merged_expected, actual) + @data( + # server overridden + ({ + "server": "example.com" + }, { + }, { + "HTTP_HOST": "example.com:5000", + "SERVER_NAME": "example.com", + "SERVER_PORT": "5000" + }), + + # port overridden, standard port + ({ + "port": "80" + }, { + }, { + "HTTP_HOST": "localhost", + "SERVER_PORT": "80" + }), + + # port overridden, non standard port + ({ + "port": "81" + }, { + }, { + "HTTP_HOST": "localhost:81", + "SERVER_PORT": "81" + }), + + # server and port overridden, default port + ({ + "server": "example.com", + "port": "80" + }, { + }, { + "HTTP_HOST": "example.com", + "SERVER_NAME": "example.com", + "SERVER_PORT": "80" + }), + + # server and port overridden, non default port + ({ + "server": "example.com", + "port": "81" + }, { + }, { + "HTTP_HOST": "example.com:81", + "SERVER_NAME": "example.com", + "SERVER_PORT": "81" + }), + + ) + @unpack + def test_fallbacks_nohost(self, fallbacks, environ, expected): + reverse_proxied = ReverseProxiedEnvironment(**fallbacks) + + merged_environ = dict(standard_environ) + merged_environ.update(environ) + del merged_environ["HTTP_HOST"] + + actual = reverse_proxied(merged_environ) + + merged_expected = dict(standard_environ) + merged_expected.update(environ) + merged_expected.update(expected) + + self.assertDictEqual(merged_expected, actual) + def test_header_config_ok(self): result = ReverseProxiedEnvironment.to_header_candidates(["prefix-header1", "prefix-header2"]) self.assertEquals(result, ["HTTP_PREFIX_HEADER1", "HTTP_PREFIX_HEADER2"]) @@ -344,28 +433,32 @@ class OctoPrintFlaskRequestTest(unittest.TestCase): def test_server_name(self): request = OctoPrintFlaskRequest(standard_environ) - self.assertEquals(request.server_name, "localhost") + self.assertEquals("localhost", request.server_name) def test_server_port(self): request = OctoPrintFlaskRequest(standard_environ) - self.assertEquals(request.server_port, "5000") + self.assertEquals("5000", request.server_port) def test_cookie_suffix(self): request = OctoPrintFlaskRequest(standard_environ) - self.assertEquals(request.cookie_suffix, "_P5000") + self.assertEquals("_P5000", request.cookie_suffix) def test_cookies(self): environ = dict(standard_environ) environ["HTTP_COOKIE"] = "postfixed_P5000=postfixed_value; " \ "postfixed_wrong_P5001=postfixed_wrong_value; " \ - "unpostfixed=unpostfixed_value" + "unpostfixed=unpostfixed_value; " \ + "both_P5000=both_postfixed_value; " \ + "both=both_unpostfixed_value;" request = OctoPrintFlaskRequest(environ) cookies = request.cookies - self.assertDictEqual(cookies, {"postfixed": "postfixed_value", - "postfixed_wrong_P5001": "postfixed_wrong_value", - "unpostfixed": "unpostfixed_value"}) + self.assertDictEqual({"postfixed": u"postfixed_value", + "postfixed_wrong_P5001": u"postfixed_wrong_value", + "unpostfixed": u"unpostfixed_value", + "both": u"both_postfixed_value"}, + cookies) ##~~ 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") ) diff --git a/translations/de/LC_MESSAGES/messages.mo b/translations/de/LC_MESSAGES/messages.mo index 0b6c6117..389721a9 100644 Binary files a/translations/de/LC_MESSAGES/messages.mo and b/translations/de/LC_MESSAGES/messages.mo differ diff --git a/translations/de/LC_MESSAGES/messages.po b/translations/de/LC_MESSAGES/messages.po index a17b2b1e..bd7cd773 100644 --- a/translations/de/LC_MESSAGES/messages.po +++ b/translations/de/LC_MESSAGES/messages.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: OctoPrint\n" "Report-Msgid-Bugs-To: i18n@octoprint.org\n" -"POT-Creation-Date: 2016-09-07 12:06+0200\n" -"PO-Revision-Date: 2016-09-07 12:09+0100\n" +"POT-Creation-Date: 2016-09-09 11:35+0200\n" +"PO-Revision-Date: 2016-09-09 11:48+0100\n" "Last-Translator: Gina Häußge \n" "Language: de\n" "Language-Team: German (http://www.transifex.com/projects/p/octoprint/" @@ -190,7 +190,6 @@ msgid "Overwrite existing file" msgstr "Vorhandene Datei überschreiben" #: src/octoprint/plugins/cura/templates/cura_settings.jinja2:113 -#, fuzzy msgid "" "\n" " You can import your existing profile .ini files " @@ -738,7 +737,7 @@ msgstr "Repository-Cache TTL" #: src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2:239 #: src/octoprint/plugins/softwareupdate/templates/softwareupdate.jinja2:26 -#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:107 +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:108 #: src/octoprint/templates/dialogs/confirmation.jinja2:11 #: src/octoprint/templates/dialogs/slicing.jinja2:50 #: src/octoprint/templates/sidebar/state.jinja2:25 @@ -746,7 +745,7 @@ msgid "Cancel" msgstr "Abbruch" #: src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2:240 -#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:108 +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:109 msgid "Save" msgstr "Speichern" @@ -1063,7 +1062,18 @@ msgstr "Versionstracking für OctoPrint" msgid "OctoPrint Release Channel" msgstr "OctoPrint Release Channel" -#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:96 +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:93 +#, python-format +msgid "" +"Make sure you have read \"Using Release Channels\" before switching to a " +"release channel other than \"Stable\"" +msgstr "" +"Bitte lies " +"\"Using Release Channels\" bevor du auf einen anderen Release Channel " +"als \"Stable\" wechselst" + +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:97 msgid "Version cache TTL" msgstr "TTL des Versionscaches" diff --git a/translations/messages.pot b/translations/messages.pot index c20926a1..66a24c1e 100644 --- a/translations/messages.pot +++ b/translations/messages.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: OctoPrint 1.2.16.dev36+g2256a1e\n" +"Project-Id-Version: OctoPrint 1.2.16.dev44+ge19ecb0\n" "Report-Msgid-Bugs-To: i18n@octoprint.org\n" -"POT-Creation-Date: 2016-09-07 12:06+0200\n" +"POT-Creation-Date: 2016-09-09 11:35+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -655,7 +655,7 @@ msgstr "" #: src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2:239 #: src/octoprint/plugins/softwareupdate/templates/softwareupdate.jinja2:26 -#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:107 +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:108 #: src/octoprint/templates/dialogs/confirmation.jinja2:11 #: src/octoprint/templates/dialogs/slicing.jinja2:50 #: src/octoprint/templates/sidebar/state.jinja2:25 @@ -663,7 +663,7 @@ msgid "Cancel" msgstr "" #: src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2:240 -#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:108 +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:109 msgid "Save" msgstr "" @@ -935,7 +935,15 @@ msgstr "" msgid "OctoPrint Release Channel" msgstr "" -#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:96 +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:93 +#, python-format +msgid "" +"Make sure you have read \"Using Release Channels\" before " +"switching to a release channel other than \"Stable\"" +msgstr "" + +#: src/octoprint/plugins/softwareupdate/templates/softwareupdate_settings.jinja2:97 msgid "Version cache TTL" msgstr ""