Python: как разрешить URL-адреса, содержащие"..'
мне нужно однозначно идентифицировать и хранить несколько адресов. Проблема в том, что иногда они приходят "..- типа http://somedomain.com/foo/bar/../../some/url
который в основном составляет http://somedomain.com/some/url
если я не ошибаюсь.
есть ли функция Python или сложный способ решить эти URL-адреса ?
6 ответов
есть простое решение с помощью urlparse.urljoin:
>>> import urlparse
>>> urlparse.urljoin('http://www.example.com/foo/bar/../../baz/bux/', '.')
'http://www.example.com/baz/bux/'
однако, если нет конечной косой черты (последний компонент является файлом, а не каталогом), последний компонент будет удален.
это исправление использует функцию urlparse для извлечения пути, а затем использовать (версия posixpath)os.путь для нормализации компонентов. Компенсировать загадочная проблема с косыми чертами, затем присоедините URL-адрес обратно вместе. Следующее doctest
уметь:
import urlparse
import posixpath
def resolveComponents(url):
"""
>>> resolveComponents('http://www.example.com/foo/bar/../../baz/bux/')
'http://www.example.com/baz/bux/'
>>> resolveComponents('http://www.example.com/some/path/../file.ext')
'http://www.example.com/some/file.ext'
"""
parsed = urlparse.urlparse(url)
new_path = posixpath.normpath(parsed.path)
if parsed.path.endswith('/'):
# Compensate for issue1707768
new_path += '/'
cleaned = parsed._replace(path=new_path)
return cleaned.geturl()
это пути к файлам. Посмотреть os.путь.normpath:
>>> import os
>>> os.path.normpath('/foo/bar/../../some/url')
'/some/url'
EDIT:
если это в Windows, ваш входной путь будет использовать обратные косые черты вместо косых. В этом случае вам все равно нужно os.path.normpath
, чтобы избавиться от ..
шаблоны (и //
и /./
и все остальное избыточно), затем преобразуйте обратные косые черты в прямые косые черты:
def fix_path_for_URL(path):
result = os.path.normpath(path)
if os.sep == '\':
result = result.replace('\', '/')
return result
EDIT 2:
если вы хотите нормализовать URL-адреса, сделайте это (прежде чем снимать метод и тому подобное) с помощью urlparse модуль, как показано в ответ на этот вопрос.
EDIT 3:
кажется,urljoin
не нормализует базовый путь, который он дал:
>>> import urlparse
>>> urlparse.urljoin('http://somedomain.com/foo/bar/../../some/url', '')
'http://somedomain.com/foo/bar/../../some/url'
normpath
сам по себе тоже не совсем режет:
>>> import os
>>> os.path.normpath('http://somedomain.com/foo/bar/../../some/url')
'http:/somedomain.com/some/url'
обратите внимание, что начальная двойная черта была съедена.
так мы должны заставить их объединить усилия:
def fix_URL(urlstring):
parts = list(urlparse.urlparse(urlstring))
parts[2] = os.path.normpath(parts[2].replace('/', os.sep)).replace(os.sep, '/')
return urlparse.urlunparse(parts)
использование:
>>> fix_URL('http://somedomain.com/foo/bar/../../some/url')
'http://somedomain.com/some/url'
Я хотел прокомментировать resolveComponents
функция в верхнем ответе.
обратите внимание, что если ваш путь составляет /
, код добавит еще один, который может быть проблематичным.
Поэтому я изменил IF
условие:
if parsed.path.endswith( '/' ) and parsed.path != '/':
urljoin
не работает, поскольку он разрешает только точечные сегменты, если второй аргумент не является абсолютным(!?) или пустой. Не только это, он не обрабатывает чрезмерное ..
s правильно в соответствии с RFC 3986 (они должны быть удалены; urljoin
не так). posixpath.normpath
не может быть использован либо (гораздо меньше os.path.normpath)
, поскольку он разрешает несколько косых черт подряд только на один (например,/////
становится /
), что является некорректным поведением URL-адреса.
следующая короткая функция разрешает любую строку пути URL правильно. Это не следует использовать относительный путь, однако, поскольку дополнительные решения о его поведении должны были бы быть сделаны (поднимите ошибку на чрезмерном ..
s? Удалить .
в начале? Оставить их обоих?)- вместо этого присоединитесь к URL-адресам перед разрешением, если вы знаете, что можете обрабатывать относительные пути. Без лишних слов:--19-->
def resolve_url_path(path):
segments = path.split('/')
segments = [segment + '/' for segment in segments[:-1]] + [segments[-1]]
resolved = []
for segment in segments:
if segment in ('../', '..'):
if resolved[1:]:
resolved.pop()
elif segment not in ('./', '.'):
resolved.append(segment)
return ''.join(resolved)
это обрабатывает конечную точку сегменты (то есть без косой черты) и правильно Слэша. Чтобы разрешить весь URL-адрес, вы можете использовать следующую оболочку (или просто встроить в нее функцию разрешения пути).
try:
# Python 3
from urllib.parse import urlsplit, urlunsplit
except ImportError:
# Python 2
from urlparse import urlsplit, urlunsplit
def resolve_url(url):
parts = list(urlsplit(url))
parts[2] = resolve_url_path(parts[2])
return urlunsplit(parts)
вы можете назвать это так:
>>> resolve_url('http://example.com/../thing///wrong/../multiple-slashes-yeah/.')
'http://example.com/thing///multiple-slashes-yeah/'
правильное разрешение URL имеет более чем несколько подводных камней, оказывается!
По данным RFC 3986 это должно произойти как часть процесса "относительного разрешения". Так что ответ может быть urlparse.urljoin(url, '')
. Но из-за ошибки urlparse.urljoin
не удаляет сегменты точек, когда второй аргумент является пустым url. Вы можете использовать yurl - альтернативная библиотека манипуляций url. Он делает это правильно:
>>> import yurl
>>> print yurl.URL('http://somedomain.com/foo/bar/../../some/url') + yurl.URL()
http://somedomain.com/some/url
import urlparse
import posixpath
parsed = list(urlparse.urlparse(url))
parsed[2] = posixpath.normpath(posixpath.join(parsed[2], rel_path))
proper_url = urlparse.urlunparse(parsed)