【问题标题】:How to get filename from Content-Disposition in headers如何从标头中的 Content-Disposition 获取文件名
【发布时间】:2011-12-23 13:38:18
【问题描述】:

我正在下载一个带有 Mechanize 的文件,并且在响应头中有一个字符串:

Content-Disposition: attachment; filename=myfilename.txt

是否有一种快速的标准方法来获取该文件名值? 我现在想到的是:

filename = f[1]['Content-Disposition'].split('; ')[1].replace('filename=', '')

但它看起来像一个快速的'n'dirty解决方案。

【问题讨论】:

  • 作为一个警告,文件名可以被引用(像大多数邮件标题一样)并且有转义序列。如此快速的字符串破解可能会导致问题。

标签: python mechanize-python


【解决方案1】:

首先使用mechanize获取header的值,然后使用内置的cgi模块解析header。

演示:

>>> import mechanize
>>> browser = mechanize.Browser()
>>> response = browser.open('http://example.com/your/url')
>>> info = response.info()
>>> header = info.getheader('Content-Disposition')
>>> header
'attachment; filename=myfilename.txt'

然后可以解析标题值:

>>> import cgi               
>>> value, params = cgi.parse_header(header)
>>> value
'attachment'
>>> params
{'filename': 'myfilename.txt'}

params 是一个简单的字典,所以 params['filename'] 是你需要的。 文件名是否用引号括起来并不重要。

【讨论】:

  • 并不是说如果您的文件名被编码,这不起作用,在这种情况下,参数将包含“文件名*”而不是“文件名”,您需要取消引用并将文件名解码为unicode 字符串。
【解决方案2】:

这些正则表达式基于 RFC 6266 的语法,但经过修改以接受没有处置类型的标头,例如内容处置:文件名=example.html

即[处置类型“;” ] disposition-parm (";" disposition-parm)* / disposition-type

它将处理带引号和不带引号的文件名参数,并从引号中的值中取消引号对,例如文件名="foo\"bar" -> foo"bar

它将处理文件名* 扩展参数,并且无论它们在标题中出现的顺序如何,都更喜欢文件名* 扩展参数而不是文件名参数

它会删除文件夹名称信息,例如/etc/passwd -> passwd,如果没有文件名参数(或标题,或者如果参数值为空字符串),则默认为 URL 路径中的基本名称

token 和 qdtext 正则表达式基于 RFC 2616 的语法,mimeCharset 和 valueChars 正则表达式基于 RFC 5987 的语法,语言正则表达式基于 RFC 5646 的语法

import re, urllib
from os import path
from urlparse import urlparse

# content-disposition = "Content-Disposition" ":"
#                        disposition-type *( ";" disposition-parm )
# disposition-type    = "inline" | "attachment" | disp-ext-type
#                     ; case-insensitive
# disp-ext-type       = token
# disposition-parm    = filename-parm | disp-ext-parm
# filename-parm       = "filename" "=" value
#                     | "filename*" "=" ext-value
# disp-ext-parm       = token "=" value
#                     | ext-token "=" ext-value
# ext-token           = <the characters in token, followed by "*">

token = '[-!#-\'*+.\dA-Z^-z|~]+'
qdtext='[]-~\t !#-[]'
mimeCharset='[-!#-&+\dA-Z^-z]+'
language='(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}(?:-[A-Za-z]{3}){,2})?|[A-Za-z]{4,8})(?:-[A-Za-z]{4})?(?:-(?:[A-Za-z]{2}|\d{3}))(?:-(?:[\dA-Za-z]{5,8}|\d[\dA-Za-z]{3}))*(?:-[\dA-WY-Za-wy-z](?:-[\dA-Za-z]{2,8})+)*(?:-[Xx](?:-[\dA-Za-z]{1,8})+)?|[Xx](?:-[\dA-Za-z]{1,8})+|[Ee][Nn]-[Gg][Bb]-[Oo][Ee][Dd]|[Ii]-[Aa][Mm][Ii]|[Ii]-[Bb][Nn][Nn]|[Ii]-[Dd][Ee][Ff][Aa][Uu][Ll][Tt]|[Ii]-[Ee][Nn][Oo][Cc][Hh][Ii][Aa][Nn]|[Ii]-[Hh][Aa][Kk]|[Ii]-[Kk][Ll][Ii][Nn][Gg][Oo][Nn]|[Ii]-[Ll][Uu][Xx]|[Ii]-[Mm][Ii][Nn][Gg][Oo]|[Ii]-[Nn][Aa][Vv][Aa][Jj][Oo]|[Ii]-[Pp][Ww][Nn]|[Ii]-[Tt][Aa][Oo]|[Ii]-[Tt][Aa][Yy]|[Ii]-[Tt][Ss][Uu]|[Ss][Gg][Nn]-[Bb][Ee]-[Ff][Rr]|[Ss][Gg][Nn]-[Bb][Ee]-[Nn][Ll]|[Ss][Gg][Nn]-[Cc][Hh]-[Dd][Ee]'
valueChars = '(?:%[\dA-F][\dA-F]|[-!#$&+.\dA-Z^-z|~])*'
dispositionParm = '[Ff][Ii][Ll][Ee][Nn][Aa][Mm][Ee]\s*=\s*(?:({token})|"((?:{qdtext}|\\\\[\t !-~])*)")|[Ff][Ii][Ll][Ee][Nn][Aa][Mm][Ee]\*\s*=\s*({mimeCharset})\'(?:{language})?\'({valueChars})|{token}\s*=\s*(?:{token}|"(?:{qdtext}|\\\\[\t !-~])*")|{token}\*\s*=\s*{mimeCharset}\'(?:{language})?\'{valueChars}'.format(**locals())

try:
  m = re.match('(?:{token}\s*;\s*)?(?:{dispositionParm})(?:\s*;\s*(?:{dispositionParm}))*|{token}'.format(**locals()), result.headers['Content-Disposition'])

except KeyError:
  name = path.basename(urllib.unquote(urlparse(url).path))

else:
  if not m:
    name = path.basename(urllib.unquote(urlparse(url).path))

  # Many user agent implementations predating this specification do not
  # understand the "filename*" parameter.  Therefore, when both "filename"
  # and "filename*" are present in a single header field value, recipients
  # SHOULD pick "filename*" and ignore "filename"

  elif m.group(8) is not None:
    name = urllib.unquote(m.group(8)).decode(m.group(7))

  elif m.group(4) is not None:
    name = urllib.unquote(m.group(4)).decode(m.group(3))

  elif m.group(6) is not None:
    name = re.sub('\\\\(.)', '\1', m.group(6))

  elif m.group(5) is not None:
    name = m.group(5)

  elif m.group(2) is not None:
    name = re.sub('\\\\(.)', '\1', m.group(2))

  else:
    name = m.group(1)

  # Recipients MUST NOT be able to write into any location other than one to
  # which they are specifically entitled

  if name:
    name = path.basename(name)

  else:
    name = path.basename(urllib.unquote(urlparse(url).path))

【讨论】:

  • 另外,正则表达式可以通过不验证语言标签来简化,特别是因为它被忽略了。语言标签可以只包含无限数量的连字符、数字和字母,它是可选的。所以只要接受 [-\dA-Za-z]* dispositionParm = '[Ff][Ii][Ll][Ee][Nn][Aa][Mm][Ee]\s*=\s*(?: ({token})|"​​((?:{qdtext}|\\\[\t !-~])*)")|[Ff][Ii][Ll][Ee][Nn][Aa][ mm][Ee]*\s*=\s*({mimeCharset})\'[-\dA-Za-z]*\'({valueChars})|{token}\s*=\s*(? :{token}|"(?:{qdtext}|\\\[\t !-~])*")|{token}*\s*=\s*{mimeCharset}\'[-\dA-Za -z]*\'{valueChars}'.format(**locals())
【解决方案3】:

我会尝试类似:

import re
filename = re.findall("filename=(\S+)", f[1]['Content-Disposition'])

这会处理文件名上的引号和 URL 转义。

【讨论】:

  • 但这会返回一个列表,而不是一个字符串,所以你可能想要filename[0] 或其他东西。它还将引号作为文件名的一部分返回。所以不是一个真正的工作示例。
猜你喜欢
  • 1970-01-01
  • 2015-05-12
  • 2021-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-06
相关资源
最近更新 更多