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)