如何从内容处置中获取文件名
Posted
技术标签:
【中文标题】如何从内容处置中获取文件名【英文标题】:How to get file name from content-disposition 【发布时间】:2017-04-17 18:54:06 【问题描述】:我下载了一个文件作为 ajax 的响应。如何从content-disposition
获取文件名和文件类型并为其显示缩略图。我得到了很多搜索结果,但找不到正确的方法。
$(".download_btn").click(function ()
var uiid = $(this).data("id2");
$.ajax(
url: "http://localhost:8080/prj/" + data + "/" + uiid + "/getfile",
type: "GET",
error: function (jqXHR, textStatus, errorThrown)
console.log(textStatus, errorThrown);
,
success: function (response, status, xhr)
var header = xhr.getResponseHeader('Content-Disposition');
console.log(header);
);
控制台输出:
inline; filename=demo3.png
【问题讨论】:
控制台说什么? 你为什么要设置window.location.href ="http://localhost:8080/prj/" + data + "/" + uiid + "/getfile";
?这将导致浏览器离开页面并仅显示该 URL。如果您离开页面,您希望如何显示图像的缩略图?为什么需要服务器建议您保存文件的文件名才能生成缩略图?
从内容配置中获取文件名是一个问题。您无法从中获取文件类型,至少不可靠,这就是内容类型标头的用途。缩略图显示将来自数据,是一个完全独立的问题。
我需要在缩略图附近显示文件名。
但我可以从文件名本身找到文件类型filename.jpg
【参考方案1】:
下面还考虑了filename
包含 unicode 字符(即-, !, (, )
等)并因此以 filename*=utf-8''Na%C3%AFve%20file.txt
的形式出现(utf-8
编码)的情况有关详细信息,请参阅here)。在这种情况下,decodeURIComponent()
函数用于解码filename
。
const disposition = xhr.getResponseHeader('Content-Disposition');
filename = disposition.split(/;(.+)/)[1].split(/=(.+)/)[1]
if (filename.toLowerCase().startsWith("utf-8''"))
filename = decodeURIComponent(filename.replace("utf-8''", ''))
else
filename = filename.replace(/['"]/g, '')
如果您正在执行cross-domain 请求,请确保将Access-Control-Expose-Headers:Content-Disposition
添加到服务器上的响应标头(以公开Content-Disposition
标头),否则客户端将无法访问filename
.例如:
headers = 'Access-Control-Expose-Headers': 'Content-Disposition'
return FileResponse("Naïve file.txt", filename="Naïve file.txt", headers=headers)
【讨论】:
【参考方案2】:如果您想获取文件名并同时支持那些奇怪的 url 编码的 UTF-8 标头和 ascii 标头,您可以使用类似这样的东西
public getFileName(disposition: string): string
const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-\.]+)(?:; ?|$)/i;
const asciiFilenameRegex = /filename=(["']?)(.*?[^\\])\1(?:; ?|$)/i;
let fileName: string = null;
if (utf8FilenameRegex.test(disposition))
fileName = decodeURIComponent(utf8FilenameRegex.exec(disposition)[1]);
else
const matches = asciiFilenameRegex.exec(disposition);
if (matches != null && matches[2])
fileName = matches[2];
return fileName;
几点说明:
-
这将采用 UTF-8 文件名的值(如果已设置),而不是 ascii 名称
下载时,您的浏览器可能会进一步更改名称以将某些字符(例如
"
)替换为 _
(Chrome)
ascii 模式最适合带引号的文件名,但支持不带引号的值。在这种情况下,它会将filename=
之后和下一个;
之前或标题值末尾的所有文本视为文件名。
MDN Content Disposition Header
【讨论】:
ASCII 文件名不必用单引号或双引号括起来。 @MoonStom 说得好,我会更新。 这是一个很好的答案,我只需要进行一些更改并将i
添加到第一个正则表达式的末尾,因为我的标题返回为filename*=utf-8
而不是UTF-8
。跨度>
dot
和 hyphen
在这种情况下不需要在字符集中进行转义。所以使用[.-]
而不是[\.\-]
我不知道,但我也认为特殊字符应该被转义,即使它们在这种情况下不是特殊字符;它使代码更具可读性,即使在 Stack Overflow 之外我也喜欢它【参考方案3】:
如果您不使用多部分正文,则可以使用此功能。它从 Content-Disposition 标头值中提取文件名(字符串如:inline; filename=demo3.png)并根据需要进行解码。
const getFileNameFromContentDisposition = disposition =>
if (disposition
&& (disposition.startsWith('attachment') || disposition.startsWith('inline'))
)
let filename = disposition.startsWith('attachment')
? disposition.replace("attachment;", "")
: disposition.replace("inline;", ""); //replaces first match only
filename = filename.trim();
if (filename.includes("filename*=") && filename.includes("filename="))
let filenames = filename.split(";"); //we can parse by ";" because all ";"s inside filename are escaped
if (filenames.length > 1) //"filename=" or "filename*=" not inside filename
if (filenames[0].trim().startsWith("filename*=")) //"filename*=" is preferred
filename = filenames[0].trim();
else
filename = filenames[1].trim();
if (filename.startsWith("filename*="))
filename = filename.replace("filename*=", "")
.split("''").slice(1).join("''"); //remove encoding and ''
filename = decodeURIComponent(filename);
else if (filename.startsWith("filename="))
filename = filename.replace("filename=", "")
if (filename.startsWith('"') && filename.endsWith('"'))
filename = filename.slice(1, filename.length - 1); //remove quotes
return filename;
函数的结果可以拆分为名称和扩展名如下:
let name = getFileNameFromContentDisposition("inline; filename=demo.3.png").split(".");
let extension = name[name.length - 1];
name = name.slice(0, name.length - 1).join(".");
console.log(name); // demo.3
console.log(extension); //png
您可以显示缩略图,例如,使用 svg:
let colors = "png": "red", "jpg": "orange";
//this is a simple example, you can make something more beautiful
let createSVGThumbnail = extension => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 20">
<rect x="0" y="0" fill = "#FAFEFF"/>
<rect x="0" y="7" stroke="$colors[extension] || "blue"" fill = "$colors[extension] || "blue""/>
<text stroke = "white" fill = "white" font-size = "6" x = "0" y = "12.5" textLength = "18">$extension.toUpperCase()</text>
</svg>`;
...
//You can use it as html element background-image
let background = "data:image/svg+xml;base64," + btoa(new TextDecoder().decode(createSVGThumbnail("png")));
【讨论】:
您应该在删除它们之前检查是否有任何引号。即使您自己的示例处置标题也不使用任何引号。此外,为了向后兼容,处置标头通常包含“filename=”和“filename=*”。 感谢您的评论!确实,我没有考虑一切。更正答案【参考方案4】:这是对 marjon4 答案的改进。
选择答案的一种非常简化的方法是像这样使用拆分;
var fileName = xhr.getResponseHeader('content-disposition').split('filename=')[1].split(';')[0];
注意:如果您的文件名本身包含分号 (;),此解决方案可能无法正常工作
【讨论】:
如果文件名中的分号会不会失败? 这是一种幼稚的做法,不推荐。如果文件名包含分号,这可能会返回不正确的结果。缺少对filename*=UTF-8''
的支持。
该解决方案适用于filename*=UTF-8' '
格式您可能指的整个格式类似于content-disposition: "attachment; filename=document.pdf; filename*=UTF-8''document.pdf"
。在说它不起作用之前请尝试理解并回答!是的,从技术上讲,如果文件名本身有分号,则此解决方案将不起作用。但在大多数情况下,文件名不包含分号,所以考虑到这个借口对我来说效果很好! (仍在我的回答中为此添加注释。)
当文件名本身包含filename=
时,它也不适用于边缘情况。
我会添加 .replaceAll('"',''),否则它似乎会因空格名称而失败【参考方案5】:
在我的情况下,标题如下所示:
attachment; filename="test-file3.txt"
因此,我可以使用命名组正则表达式轻松提取文件名:
const regExpFilename = /filename="(?<filename>.*)"/;
const filename: string | null = regExpFilename.exec(contentDispositionHeader)?.groups?.filename ?? null;
我知道我在这里有点跑题了,因为 OP 在文件名周围没有引号,但仍然共享以防有人遇到与我刚才所做的相同的模式
【讨论】:
【参考方案6】:或者只是:
var fileName = xhr.getResponseHeader('Content-Disposition').split("filename=")[1];
【讨论】:
不适用于以下格式:content-disposition: "attachment; filename=document.pdf; filename*=UTF-8''document.pdf"
添加了解决@AlexM提到的问题的答案【参考方案7】:
试试这个解决方案:
var contentDisposition = xhr.getResponseHeader('Content-Disposition');
var startIndex = contentDisposition.indexOf("filename=") + 10; // Adjust '+ 10' if filename is not the right one.
var endIndex = contentDisposition.length - 1; //Check if '- 1' is necessary
var filename = contentDisposition.substring(startIndex, endIndex);
console.log("filename: " + filename)
【讨论】:
【参考方案8】:有一个 npm 包可以完成这项工作:content-disposition
【讨论】:
在浏览器上好像不行。 而 [github.com/jshttp/content-disposition/issues/… 的作者似乎并不想删除 nodejs 的 deps。我能找到的几乎所有 npm 包都至少使用了来自 nodejs 的path
包。
还有content-disposition-header 可以同时用于浏览器和Node.js【参考方案9】:
这是我以前使用它的方式。 我假设您将附件作为服务器响应提供。
我从我的 REST 服务 response.setHeader("Content-Disposition", "attachment;filename=XYZ.csv");
中设置了这样的响应标头
function(response, status, xhr)
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1)
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1])
filename = matches[1].replace(/['"]/g, '');
编辑:
编辑答案以适合您的问题 - 使用单词 inline
而不是 attachment
function(response, status, xhr)
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('inline') !== -1)
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1])
filename = matches[1].replace(/['"]/g, '');
More here
【讨论】:
filename = matches[1].replace(/['"]/g, '');
是做什么的?
此解决方案不适用于以下情况:attachment;文件名*=UTF-8''文件名.txt。使用这个正则表达式,文件名将是 UTF-8filename.txt.
我不敢相信 javascript 有时是多么的丑陋……
也可以像这样匹配 utf8 模式:dis = "attachment; filename*=UTF-8''filename.pdf"
try /filename\*?=([^']*'')?([^;]*)/.exec(dis)[2]
我认为最好使用disposition.startsWith("attachment")
而不是disposition.indexOf('attachment') !== -1
,因为文件名可能包含附件以上是关于如何从内容处置中获取文件名的主要内容,如果未能解决你的问题,请参考以下文章
如何使用可可中的 contentsOfDirectoryAtPath 从文件夹中获取内容列表?
JS fetch API:如何使用一个异步函数从多个文件中获取内容?