如何从内容配置中获取文件名
How to get file name from content-disposition
我下载了一个文件作为 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
这是我以前使用它的方式。
我假设您将附件作为服务器响应提供。
我从我的 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]*=((['"]).*?|[^;\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]*=((['"]).*?|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
}
More here
试试这个解决方案:
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)
有一个 npm 包可以完成这项工作:content-disposition
或者只是:
var fileName = xhr.getResponseHeader('Content-Disposition').split("filename=")[1];
这是对 marjon4 答案的改进。
选择答案的一种更简单的方法是像这样使用拆分;
var fileName = xhr.getResponseHeader('content-disposition').split('filename=')[1].split(';')[0];
注意:如果您的文件名本身包含分号 (;),此解决方案可能无法按预期工作
在我的例子中,header 看起来像这样:
attachment; filename="test-file3.txt"
因此我能够使用命名组正则表达式很容易地提取文件名:
const regExpFilename = /filename="(?<filename>.*)"/;
const filename: string | null = regExpFilename.exec(contentDispositionHeader)?.groups?.filename ?? null;
我知道我在这里有点跑题了,因为 OP 在文件名周围没有引号,但仍然共享以防有人遇到与我刚才相同的模式
如果您不使用多部分body,那么您可以使用此功能。它从 Content-Disposition header 值中提取文件名(字符串如: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" width="18" height="20" viewBox="0 0 18 20">
<rect x="0" y="0" width="18" height="20" fill = "#FAFEFF"/>
<rect x="0" y="7" width="18" height="6" 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")));
如果你想获取文件名并支持那些奇怪的 url 编码的 UTF-8 headers 和 ascii headers,你可以使用这样的东西
public getFileName(disposition: string): string {
const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-\.]+)(?:; ?|$)/i;
const asciiFilenameRegex = /^filename=(["']?)(.*?[^\])(?:; ?|$)/i;
let fileName: string = null;
if (utf8FilenameRegex.test(disposition)) {
fileName = decodeURIComponent(utf8FilenameRegex.exec(disposition)[1]);
} else {
// prevent ReDos attacks by anchoring the ascii regex to string start and
// slicing off everything before 'filename='
const filenameStart = disposition.toLowerCase().indexOf('filename=');
if (filenameStart >= 0) {
const partialDisposition = disposition.slice(filenameStart);
const matches = asciiFilenameRegex.exec(partialDisposition );
if (matches != null && matches[2]) {
fileName = matches[2];
}
}
}
return fileName;
}
一些注意事项:
- 这将采用 UTF-8 文件名的值(如果已设置)覆盖 ascii 名称
- 下载时,您的浏览器可能会进一步更改名称以替换某些字符,例如
"
,使用 _
(Chrome)
- ascii 模式最适合带引号的文件名,但也支持不带引号的值。在这种情况下,它会将
filename=
之后和下一个 ;
或 header 值结尾之前的所有文本视为文件名。
- 这不会清除路径信息。如果您从网站保存文件,那是浏览器的工作,但是如果您在节点应用程序或类似的东西的上下文中使用它,请务必按照 OS 清理路径信息并只留下文件名,或精心制作的文件名可能用于覆盖系统文件(想想像
../../../../../../../path/to/system/files/malicious.dll
) 这样的文件名
下面还考虑了 filename
包含 unicode 字符(即 -, !, (, )
等)的情况,因此以(utf-8
编码)的形式出现,例如,filename*=utf-8''Na%C3%AFve%20file.txt
(参见 here for more details). In such cases, the 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
添加到服务器上的响应 header 中(以公开 Content-Disposition
header), 否则 filename
将无法在客户端访问。例如:
headers = {'Access-Control-Expose-Headers': 'Content-Disposition'}
return FileResponse("Naïve file.txt", filename="Naïve file.txt", headers=headers)
我相信这会有所帮助!
let filename = response.headers['content-disposition'].split('filename=')[1].split('.')[0];
let extension = response.headers['content-disposition'].split('.')[1].split(';')[0];
我下载了一个文件作为 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
这是我以前使用它的方式。 我假设您将附件作为服务器响应提供。
我从我的 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]*=((['"]).*?|[^;\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]*=((['"]).*?|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
}
More here
试试这个解决方案:
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)
有一个 npm 包可以完成这项工作:content-disposition
或者只是:
var fileName = xhr.getResponseHeader('Content-Disposition').split("filename=")[1];
这是对 marjon4 答案的改进。
选择答案的一种更简单的方法是像这样使用拆分;
var fileName = xhr.getResponseHeader('content-disposition').split('filename=')[1].split(';')[0];
注意:如果您的文件名本身包含分号 (;),此解决方案可能无法按预期工作
在我的例子中,header 看起来像这样:
attachment; filename="test-file3.txt"
因此我能够使用命名组正则表达式很容易地提取文件名:
const regExpFilename = /filename="(?<filename>.*)"/;
const filename: string | null = regExpFilename.exec(contentDispositionHeader)?.groups?.filename ?? null;
我知道我在这里有点跑题了,因为 OP 在文件名周围没有引号,但仍然共享以防有人遇到与我刚才相同的模式
如果您不使用多部分body,那么您可以使用此功能。它从 Content-Disposition header 值中提取文件名(字符串如: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" width="18" height="20" viewBox="0 0 18 20">
<rect x="0" y="0" width="18" height="20" fill = "#FAFEFF"/>
<rect x="0" y="7" width="18" height="6" 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")));
如果你想获取文件名并支持那些奇怪的 url 编码的 UTF-8 headers 和 ascii headers,你可以使用这样的东西
public getFileName(disposition: string): string {
const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-\.]+)(?:; ?|$)/i;
const asciiFilenameRegex = /^filename=(["']?)(.*?[^\])(?:; ?|$)/i;
let fileName: string = null;
if (utf8FilenameRegex.test(disposition)) {
fileName = decodeURIComponent(utf8FilenameRegex.exec(disposition)[1]);
} else {
// prevent ReDos attacks by anchoring the ascii regex to string start and
// slicing off everything before 'filename='
const filenameStart = disposition.toLowerCase().indexOf('filename=');
if (filenameStart >= 0) {
const partialDisposition = disposition.slice(filenameStart);
const matches = asciiFilenameRegex.exec(partialDisposition );
if (matches != null && matches[2]) {
fileName = matches[2];
}
}
}
return fileName;
}
一些注意事项:
- 这将采用 UTF-8 文件名的值(如果已设置)覆盖 ascii 名称
- 下载时,您的浏览器可能会进一步更改名称以替换某些字符,例如
"
,使用_
(Chrome) - ascii 模式最适合带引号的文件名,但也支持不带引号的值。在这种情况下,它会将
filename=
之后和下一个;
或 header 值结尾之前的所有文本视为文件名。 - 这不会清除路径信息。如果您从网站保存文件,那是浏览器的工作,但是如果您在节点应用程序或类似的东西的上下文中使用它,请务必按照 OS 清理路径信息并只留下文件名,或精心制作的文件名可能用于覆盖系统文件(想想像
../../../../../../../path/to/system/files/malicious.dll
) 这样的文件名
下面还考虑了 filename
包含 unicode 字符(即 -, !, (, )
等)的情况,因此以(utf-8
编码)的形式出现,例如,filename*=utf-8''Na%C3%AFve%20file.txt
(参见 here for more details). In such cases, the 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
添加到服务器上的响应 header 中(以公开 Content-Disposition
header), 否则 filename
将无法在客户端访问。例如:
headers = {'Access-Control-Expose-Headers': 'Content-Disposition'}
return FileResponse("Naïve file.txt", filename="Naïve file.txt", headers=headers)
我相信这会有所帮助!
let filename = response.headers['content-disposition'].split('filename=')[1].split('.')[0];
let extension = response.headers['content-disposition'].split('.')[1].split(';')[0];