diff --git a/oslo_utils/strutils.py b/oslo_utils/strutils.py index 7d15a34..d52542b 100644 --- a/oslo_utils/strutils.py +++ b/oslo_utils/strutils.py @@ -22,6 +22,7 @@ import re import unicodedata import six +from six.moves import urllib from oslo_utils._i18n import _ from oslo_utils import encodeutils @@ -412,3 +413,52 @@ def check_string_length(value, name=None, min_length=0, max_length=None): "%(max_length)s.") % {'name': name, 'length': length, 'max_length': max_length} raise ValueError(msg) + + +def split_path(path, minsegs=1, maxsegs=None, rest_with_last=False): + """Validate and split the given HTTP request path. + + **Examples**:: + + ['a'] = _split_path('/a') + ['a', None] = _split_path('/a', 1, 2) + ['a', 'c'] = _split_path('/a/c', 1, 2) + ['a', 'c', 'o/r'] = _split_path('/a/c/o/r', 1, 3, True) + + :param path: HTTP Request path to be split + :param minsegs: Minimum number of segments to be extracted + :param maxsegs: Maximum number of segments to be extracted + :param rest_with_last: If True, trailing data will be returned as part + of last segment. If False, and there is + trailing data, raises ValueError. + :returns: list of segments with a length of maxsegs (non-existent + segments will return as None) + :raises: ValueError if given an invalid path + + .. versionadded:: 3.9 + """ + if not maxsegs: + maxsegs = minsegs + if minsegs > maxsegs: + raise ValueError(_('minsegs > maxsegs: %(min)d > %(max)d)') % + {'min': minsegs, 'max': maxsegs}) + if rest_with_last: + segs = path.split('/', maxsegs) + minsegs += 1 + maxsegs += 1 + count = len(segs) + if (segs[0] or count < minsegs or count > maxsegs or + '' in segs[1:minsegs]): + raise ValueError(_('Invalid path: %s') % urllib.parse.quote(path)) + else: + minsegs += 1 + maxsegs += 1 + segs = path.split('/', maxsegs) + count = len(segs) + if (segs[0] or count < minsegs or count > maxsegs + 1 or + '' in segs[1:minsegs] or + (count == maxsegs + 1 and segs[maxsegs])): + raise ValueError(_('Invalid path: %s') % urllib.parse.quote(path)) + segs = segs[1:maxsegs] + segs.extend([None] * (maxsegs - 1 - len(segs))) + return segs diff --git a/oslo_utils/tests/test_strutils.py b/oslo_utils/tests/test_strutils.py index 51aad0a..da807df 100644 --- a/oslo_utils/tests/test_strutils.py +++ b/oslo_utils/tests/test_strutils.py @@ -694,3 +694,42 @@ class StringLengthTestCase(test_base.BaseTestCase): self.assertRaises(TypeError, strutils.check_string_length, dict(), max_length=255) + + +class SplitPathTestCase(test_base.BaseTestCase): + def test_split_path_failed(self): + self.assertRaises(ValueError, strutils.split_path, '') + self.assertRaises(ValueError, strutils.split_path, '/') + self.assertRaises(ValueError, strutils.split_path, '//') + self.assertRaises(ValueError, strutils.split_path, '//a') + self.assertRaises(ValueError, strutils.split_path, '/a/c') + self.assertRaises(ValueError, strutils.split_path, '//c') + self.assertRaises(ValueError, strutils.split_path, '/a/c/') + self.assertRaises(ValueError, strutils.split_path, '/a//') + self.assertRaises(ValueError, strutils.split_path, '/a', 2) + self.assertRaises(ValueError, strutils.split_path, '/a', 2, 3) + self.assertRaises(ValueError, strutils.split_path, '/a', 2, 3, True) + self.assertRaises(ValueError, strutils.split_path, '/a/c/o/r', 3, 3) + self.assertRaises(ValueError, strutils.split_path, '/a', 5, 4) + + def test_split_path_success(self): + self.assertEqual(strutils.split_path('/a'), ['a']) + self.assertEqual(strutils.split_path('/a/'), ['a']) + self.assertEqual(strutils.split_path('/a/c', 2), ['a', 'c']) + self.assertEqual(strutils.split_path('/a/c/o', 3), ['a', 'c', 'o']) + self.assertEqual(strutils.split_path('/a/c/o/r', 3, 3, True), + ['a', 'c', 'o/r']) + self.assertEqual(strutils.split_path('/a/c', 2, 3, True), + ['a', 'c', None]) + self.assertEqual(strutils.split_path('/a/c/', 2), ['a', 'c']) + self.assertEqual(strutils.split_path('/a/c/', 2, 3), ['a', 'c', '']) + + def test_split_path_invalid_path(self): + try: + strutils.split_path('o\nn e', 2) + except ValueError as err: + self.assertEqual(str(err), 'Invalid path: o%0An%20e') + try: + strutils.split_path('o\nn e', 2, 3, True) + except ValueError as err: + self.assertEqual(str(err), 'Invalid path: o%0An%20e')