Skip to content

Commit fa2d338

Browse files
authored
Do not sanitize http:// attachment URLs (#1122)
1 parent 5fd912f commit fa2d338

File tree

6 files changed

+55
-14
lines changed

6 files changed

+55
-14
lines changed

apprise/attachment/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ def download(self):
315315
"download() is implimented by the child class.")
316316

317317
@staticmethod
318-
def parse_url(url, verify_host=True, mimetype_db=None):
318+
def parse_url(url, verify_host=True, mimetype_db=None, sanitize=True):
319319
"""Parses the URL and returns it broken apart into a dictionary.
320320
321321
This is very specific and customized for Apprise.
@@ -333,7 +333,8 @@ def parse_url(url, verify_host=True, mimetype_db=None):
333333
successful, otherwise None is returned.
334334
"""
335335

336-
results = URLBase.parse_url(url, verify_host=verify_host)
336+
results = URLBase.parse_url(
337+
url, verify_host=verify_host, sanitize=sanitize)
337338

338339
if not results:
339340
# We're done; we failed to parse our url

apprise/attachment/http.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,7 @@ def __del__(self):
296296
"""
297297
Tidy memory if open
298298
"""
299-
with self._lock:
300-
self.invalidate()
299+
self.invalidate()
301300

302301
def url(self, privacy=False, *args, **kwargs):
303302
"""
@@ -363,8 +362,7 @@ def parse_url(url):
363362
us to re-instantiate this object.
364363
365364
"""
366-
results = AttachBase.parse_url(url)
367-
365+
results = AttachBase.parse_url(url, sanitize=False)
368366
if not results:
369367
# We're done early as we couldn't load the results
370368
return results

apprise/url.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ def post_process_parse_url_results(results):
744744

745745
@staticmethod
746746
def parse_url(url, verify_host=True, plus_to_space=False,
747-
strict_port=False):
747+
strict_port=False, sanitize=True):
748748
"""Parses the URL and returns it broken apart into a dictionary.
749749
750750
This is very specific and customized for Apprise.
@@ -765,7 +765,8 @@ def parse_url(url, verify_host=True, plus_to_space=False,
765765

766766
results = parse_url(
767767
url, default_schema='unknown', verify_host=verify_host,
768-
plus_to_space=plus_to_space, strict_port=strict_port)
768+
plus_to_space=plus_to_space, strict_port=strict_port,
769+
sanitize=sanitize)
769770

770771
if not results:
771772
# We're done; we failed to parse our url

apprise/utils.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ def tidy_path(path):
541541
return path
542542

543543

544-
def parse_qsd(qs, simple=False, plus_to_space=False):
544+
def parse_qsd(qs, simple=False, plus_to_space=False, sanitize=True):
545545
"""
546546
Query String Dictionary Builder
547547
@@ -568,6 +568,8 @@ def parse_qsd(qs, simple=False, plus_to_space=False):
568568
per normal URL Encoded defininition. Normal URL parsing applies
569569
this, but `+` is very actively used character with passwords,
570570
api keys, tokens, etc. So Apprise does not do this by default.
571+
572+
if sanitize is set to False, then kwargs are not placed into lowercase
571573
"""
572574

573575
# Our return result set:
@@ -608,7 +610,7 @@ def parse_qsd(qs, simple=False, plus_to_space=False):
608610

609611
# Always Query String Dictionary (qsd) for every entry we have
610612
# content is always made lowercase for easy indexing
611-
result['qsd'][key.lower().strip()] = val
613+
result['qsd'][key.lower().strip() if sanitize else key] = val
612614

613615
if simple:
614616
# move along
@@ -636,7 +638,7 @@ def parse_qsd(qs, simple=False, plus_to_space=False):
636638

637639

638640
def parse_url(url, default_schema='http', verify_host=True, strict_port=False,
639-
simple=False, plus_to_space=False):
641+
simple=False, plus_to_space=False, sanitize=True):
640642
"""A function that greatly simplifies the parsing of a url
641643
specified by the end user.
642644
@@ -691,6 +693,8 @@ def parse_url(url, default_schema='http', verify_host=True, strict_port=False,
691693
692694
If the URL can't be parsed then None is returned
693695
696+
If sanitize is set to False, then kwargs are not placed in lowercase
697+
and wrapping whitespace is not removed
694698
"""
695699

696700
if not isinstance(url, str):
@@ -750,7 +754,8 @@ def parse_url(url, default_schema='http', verify_host=True, strict_port=False,
750754
# while ensuring that all keys are lowercase
751755
if qsdata:
752756
result.update(parse_qsd(
753-
qsdata, simple=simple, plus_to_space=plus_to_space))
757+
qsdata, simple=simple, plus_to_space=plus_to_space,
758+
sanitize=sanitize))
754759

755760
# Now do a proper extraction of data; http:// is just substitued in place
756761
# to allow urlparse() to function as expected, we'll swap this back to the

test/test_apprise_utils.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,38 @@ def test_parse_url_general():
719719
assert result['qsd+'] == {}
720720
assert result['qsd:'] == {}
721721

722+
# Sanitizing
723+
result = utils.parse_url(
724+
'hTTp://hostname/?+KeY=ValueA&-kEy=ValueB&KEY=Value%20+C&:cOlON=YeS',
725+
sanitize=False)
726+
727+
assert len(result['qsd-']) == 1
728+
assert len(result['qsd+']) == 1
729+
assert len(result['qsd']) == 4
730+
assert len(result['qsd:']) == 1
731+
732+
assert result['schema'] == 'http'
733+
assert result['host'] == 'hostname'
734+
assert result['port'] is None
735+
assert result['user'] is None
736+
assert result['password'] is None
737+
assert result['fullpath'] == '/'
738+
assert result['path'] == '/'
739+
assert result['query'] is None
740+
assert result['url'] == 'http://hostname/'
741+
assert '+KeY' in result['qsd']
742+
assert '-kEy' in result['qsd']
743+
assert ':cOlON' in result['qsd']
744+
assert result['qsd:']['cOlON'] == 'YeS'
745+
assert 'key' not in result['qsd']
746+
assert 'KeY' in result['qsd+']
747+
assert result['qsd+']['KeY'] == 'ValueA'
748+
assert 'kEy' in result['qsd-']
749+
assert result['qsd-']['kEy'] == 'ValueB'
750+
assert result['qsd']['KEY'] == 'Value +C'
751+
assert result['qsd']['+KeY'] == result['qsd+']['KeY']
752+
assert result['qsd']['-kEy'] == result['qsd-']['kEy']
753+
722754

723755
def test_parse_url_simple():
724756
"utils: parse_url() testing """

test/test_attach_http.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def __exit__(self, *args, **kwargs):
190190

191191
# Test custom url get parameters
192192
results = AttachHTTP.parse_url(
193-
'http://user:pass@localhost/apprise.gif?dl=1&cache=300')
193+
'http://user:pass@localhost/apprise.gif?DL=1&cache=300')
194194
assert isinstance(results, dict)
195195
attachment = AttachHTTP(**results)
196196
assert isinstance(attachment.url(), str) is True
@@ -200,12 +200,16 @@ def __exit__(self, *args, **kwargs):
200200
assert attachment
201201
assert mock_get.call_count == 1
202202
assert 'params' in mock_get.call_args_list[0][1]
203-
assert 'dl' in mock_get.call_args_list[0][1]['params']
203+
assert 'DL' in mock_get.call_args_list[0][1]['params']
204204

205205
# Verify that arguments that are reserved for apprise are not
206206
# passed along
207207
assert 'cache' not in mock_get.call_args_list[0][1]['params']
208208

209+
with mock.patch('os.unlink', side_effect=OSError()):
210+
# Test invalidation with exception thrown
211+
attachment.invalidate()
212+
209213
results = AttachHTTP.parse_url(
210214
'http://user:pass@localhost/apprise.gif?+key=value&cache=True')
211215
assert isinstance(results, dict)

0 commit comments

Comments
 (0)