Как определить имя файла, загруженного с помощью HTTP в Python?

Я загружаю файл, используя функцию get библиотеки Python requests. Для хранения файла я бы хотел определить имя файла, которое бы имело веб-браузер для своего диалога "сохранить" или "сохранить как...".

Легко, правда? Я могу просто получить его из HTTP-заголовка Content-Disposition, доступного для объекта ответа:

import re
d = r.headers['content-disposition']
fname = re.findall("filename=(.+)", d)

Но если присмотреться к этой теме более внимательно, это не так просто:

В соответствии с RFC 6266, раздел 4.3, и грамматикой в , раздел 4.1,, значение может быть токеном без кавычек (например, the_report.pdf) или строкой в кавычках, которая также может содержать пробел ( например, "the report.pdf") и escape-последовательности. Кроме того,

когда и "имя файла", и "имя файла *" присутствуют в одном значении поля заголовка, [мы] ДОЛЖНЫ выбрать "имя файла *" и игнорировать "имя файла".

Однако значение filename* все же немного сложнее, чем значение filename.

Кроме того, RFC, кажется, допускает дополнительные пробелы вокруг =.

Таким образом, для примеров, перечисленных в RFC, я бы хотел получить следующие результаты:

  • Content-Disposition: Attachment; filename=example.html
    
    имя файла: example.html
  • Content-Disposition: INLINE; FILENAME= "an example.html"
    
    имя файла: an example.html
  • Content-Disposition: attachment;
                         filename*= UTF-8''%e2%82%ac%20rates
    
    имя файла: € rates
  • Content-Disposition: attachment;
                         filename="EURO rates";
                         filename*=utf-8''%e2%82%ac%20rates
    
    имя файла: здесь € rates тоже (не EURO rates, так как filename* имеет преимущество)

Теперь я мог бы легко адаптировать регулярное выражение для учета переменных пробелов вокруг =, но иметь его для обработки всех других вариантов тоже было бы довольно громоздко. (С учетом цитирования и экранирования я даже не уверен, что RegEx может охватить все случаи. Может быть, они могут, так как здесь нет вложенных скобок.)

Итак, я должен реализовать полноценный синтаксический анализатор или я могу определить имя файла в соответствии с RFC 6266 с помощью нескольких вызовов HTTP-библиотеки (может быть, самого TG420)? Поскольку RFC 6266 является частью стандарта HTTP, я могу представить, что некоторые библиотеки, специализирующиеся на HTTP, уже охватывают это. (Поэтому я также спрашивал о Рекомендациях по программному обеспечению SE.)

Ответ 1

Библиотека rfc6266, как представляется, делает именно то, что вам нужно. Он может анализировать исходные заголовки, ответы requests и ответы urllib2. Это на PyPI.

Некоторые примеры:

>>> import rfc6266, requests
>>> rfc6266.parse_headers('''Attachment; filename=example.html''').filename_unsafe
'example.html'
>>> rfc6266.parse_headers('''INLINE; FILENAME= "an example.html"''').filename_unsafe
'an example.html'
>>> rfc6266.parse_headers(
    '''attachment; '''
    '''filename*= UTF-8''%e2%82%ac%20rates''').filename_unsafe
'€ rates'
>>> rfc6266.parse_headers(
    '''attachment; '''
    '''filename="EURO rates"; '''
    '''filename*=utf-8''%e2%82%ac%20rates''').filename_unsafe
'€ rates'
>>> r = requests.get('http://example.com/€ rates')
>>> rfc6266.parse_requests_response(r).filename_unsafe
'€ rates'

В качестве примечания: эта библиотека не любит нестандартные пробелы в заголовке.

Ответ 2

если вам не нужен результат в utf-8

def getFilename(s):
  fname = re.findall("filename\*?=([^;]+)", s, flags=re.IGNORECASE)
  print fname[0].strip().strip('"')

но если UTF-8 является обязательным

def getFilename(s):
    fname = re.findall("filename\*=([^;]+)", s, flags=re.IGNORECASE)
    if not fname:
        fname = re.findall("filename=([^;]+)", s, flags=re.IGNORECASE)
    if "utf-8''" in fname[0].lower():
        fname = re.sub("utf-8''", '', fname[0], flags=re.IGNORECASE)
        fname = urllib.unquote(fname).decode('utf8')
    else:
        fname = fname[0]
    # clean space and double quotes
    print fname.strip().strip('"')

# example
getFilename('Attachment; filename=example.html')
getFilename('INLINE; FILENAME= "an example.html"')

getFilename("attachment;filename*= UTF-8''%e2%82%ac%20rates")
getFilename("attachment; filename=\"EURO rates\";filename*=utf-8''%e2%82%ac%20rates")

getFilename("attachment;filename=\"_____ _____ ___ __ ____ _____ Hekayt Bent.2017.mp3\";filename*=UTF-8''%D8%A7%D8%BA%D9%86%D9%8A%D9%87%20%D8%AD%D9%83%D8%A7%D9%8A%D8%A9%20%D8%A8%D9%86%D8%AA%20%D9%84%D9%80%20%D9%85%D8%AD%D9%85%D8%AF%20%D8%B4%D8%AD%D8%A7%D8%AA%D8%A9%20Hekayt%20Bent.2017.mp3")

результат

example.html
an example.html
€ rates
€ rates
اغنيه حكاية بنت لـ محمد شحاتة Hekayt Bent.2017.mp3