Repository: falconry/falcon
Pull request #1415 (2019-01-19 20:45:08)

fix(static): Disallow \ufffd in the path

codecov[bot]: # [Codecov](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=h1) Report
> Merging [#1415](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=desc) into [master](https://codecov.io/gh/falconry/falcon/commit/e5dd5ea6ed86079f52e5161334be74cbd327dae8?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/falconry/falcon/pull/1415/graphs/tree.svg?width=650&token=zOtkfNQ3h6&height=150&src=pr)](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=tree)

```diff
@@          Coverage Diff           @@
##           master   #1415   +/-   ##
======================================
  Coverage     100%    100%           
======================================
  Files          38      38           
  Lines        2543    2546    +3     
  Branches      394     395    +1     
======================================
+ Hits         2543    2546    +3
```


| [Impacted Files](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [falcon/routing/static.py](https://codecov.io/gh/falconry/falcon/pull/1415/diff?src=pr&el=tree#diff-ZmFsY29uL3JvdXRpbmcvc3RhdGljLnB5) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=footer). Last update [e5dd5ea...02bfa7c](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

codecov[bot]: # [Codecov](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=h1) Report
> Merging [#1415](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=desc) into [master](https://codecov.io/gh/falconry/falcon/commit/e5dd5ea6ed86079f52e5161334be74cbd327dae8?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/falconry/falcon/pull/1415/graphs/tree.svg?width=650&token=zOtkfNQ3h6&height=150&src=pr)](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=tree)

```diff
@@          Coverage Diff           @@
##           master   #1415   +/-   ##
======================================
  Coverage     100%    100%           
======================================
  Files          38      38           
  Lines        2543    2546    +3     
  Branches      394     395    +1     
======================================
+ Hits         2543    2546    +3
```


| [Impacted Files](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [falcon/routing/static.py](https://codecov.io/gh/falconry/falcon/pull/1415/diff?src=pr&el=tree#diff-ZmFsY29uL3JvdXRpbmcvc3RhdGljLnB5) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=footer). Last update [e5dd5ea...02bfa7c](https://codecov.io/gh/falconry/falcon/pull/1415?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/falcon/routing/static.py b/falcon/routing/static.py
index f2bb1c6..d291d73 100644
--- a/falcon/routing/static.py
+++ b/falcon/routing/static.py
@@ -1,6 +1,7 @@
 import io
 import os
 import re
+import sys
 
 import falcon
 
@@ -37,7 +38,10 @@ class StaticRoute(object):
     """
 
     # NOTE(kgriffs): Don't allow control characters and reserved chars
-    _DISALLOWED_CHARS_PATTERN = re.compile('[\x00-\x1f\x80-\x9f~?<>:*|\'"]')
+    if sys.version_info.major == 3:
+        _DISALLOWED_CHARS_PATTERN = re.compile('[\x00-\x1f\x80-\x9f\ufffd~?<>:*|\'"]')
+    else:
+        _DISALLOWED_CHARS_PATTERN = re.compile('[\x00-\x1f\x80-\x9f~?<>:*|\'"]|\xef\xbf\xbd')
 
     # NOTE(kgriffs): If somehow an executable code exploit is triggerable, this
     # minimizes how much can be included in the payload.


Test patch:
diff --git a/tests/test_static.py b/tests/test_static.py
index d04f521..e184e40 100644
--- a/tests/test_static.py
+++ b/tests/test_static.py
@@ -75,6 +75,11 @@ def client():
     # Too long
     '/static/' + ('t' * StaticRoute._MAX_NON_PREFIXED_LEN) + 'x',
 
+    # Invalid unicode character
+    #
+    # NOTE(kgriffs) create_environ() will take care of encoding this path
+    #   to UTF-8 under Python 2.
+    u'/static/\ufffdsomething',
 ])
 def test_bad_path(uri, monkeypatch):
     monkeypatch.setattr(io, 'open', lambda path, mode: path)