如何从content-disposition中获取文件名

问题描述 投票:0回答:11

我下载了一个文件作为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
javascript ajax filenames file-type content-disposition
11个回答
147
投票

这是我以前使用它的方法。 我假设您提供附件作为服务器响应。

我从 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, '');
        }
    }
}

更多这里


46
投票

这是对 marjon4 答案的改进。

选择答案的一种更简单的方法是像这样使用 split ;

var fileName = xhr.getResponseHeader('content-disposition').split('filename=')[1].split(';')[0];

注意:如果您的文件名本身包含分号 (;),则此解决方案可能无法按预期工作


36
投票

如果你想获取文件名并支持那些奇怪的 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 {
      // 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;
}

一些注意事项:

  1. 这将采用 UTF-8 文件名的值(如果设置)而不是 ascii 名称
  2. 下载时,您的浏览器可能会进一步更改名称以替换某些字符,例如将
    "
    替换为
    _
    (Chrome)
  3. ascii 模式最适合带引号的文件名,但支持不带引号的值。在这种情况下,它将
    filename=
    之后和下一个
    ;
    或标题值末尾之前的所有文本视为文件名。
  4. 这不会清除路径信息。如果您要从网站保存文件,则这是浏览器的工作,但如果您在节点应用程序或类似内容的上下文中使用此文件,请务必清理每个操作系统的路径信息并仅保留文件名或精心设计的文件名可能用于覆盖系统文件(想象一下像
    ../../../../../../../path/to/system/files/malicious.dll
    这样的文件名)

MDN 内容处置标头


19
投票

在我的例子中,标题看起来像这样:

attachment; filename="test-file3.txt"

因此,我可以使用命名组正则表达式轻松提取文件名:

const regExpFilename = /filename="(?<filename>.*)"/;

const filename: string | null = regExpFilename.exec(contentDispositionHeader)?.groups?.filename ?? null;

我知道我在这里有点偏离主题,因为OP在文件名周围没有引号,但仍然分享,以防有人遇到与我刚才相同的模式


14
投票

或者只是:

var fileName = xhr.getResponseHeader('Content-Disposition').split("filename=")[1];

3
投票

尝试这个解决方案:

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)

2
投票

还有库content-disposition-attachment,可以在浏览器中使用:

npm i -D content-disposition-attachment
import { AxiosResponse } from "axios";
import { parse } from "content-disposition-attachment";

const getFilenameFromHeaders = ({ headers }: AxiosResponse<Blob>) => {
  const defaultName = "untitled";
  try {
    const { attachment, filename } = parse(headers["content-disposition"]);
    return attachment ? filename : defaultName;
  } catch (e) {
    console.error(e);
    return defaultName;
  }
};

1
投票

我相信这会有所帮助!

let filename = response.headers['content-disposition'].split('filename=')[1].split('.')[0];
let extension = response.headers['content-disposition'].split('.')[1].split(';')[0];

1
投票

下面还考虑了

filename
包含unicode字符(即
-, !, (, )
等)的情况,因此,以例如
utf-8
的形式出现(
filename*=utf-8''Na%C3%AFve%20file.txt
编码)(参见)点击此处了解更多详情)。在这种情况下,
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-origin 请求,请确保将

Access-Control-Expose-Headers: Content-Disposition
添加到服务器端的响应标头(请参阅
Access-Control-Expose-Headers
),以便公开
Content-Disposition
标头;否则,客户端将无法通过 JavaScript 访问
filename
。例如:

headers = {'Access-Control-Expose-Headers': 'Content-Disposition'}
return FileResponse("Naïve file.txt", filename="Naïve file.txt", headers=headers)

0
投票

TypeScript 解决方案应该涵盖大多数边缘情况:

    /**
     * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
     */
    parseFilenameFromContentDispositionHeader(contentDisposition: string): string {
        const filenameField = contentDisposition
            .split(';')
            .map((x) => x.trim())
            .filter((x) => x.startsWith('filename'))
            .sort()?.[0];
        let rightPart = filenameField?.split('=')?.[1].replaceAll('"', '');
        if (filenameField?.startsWith('filename=')) {
            return rightPart;
        } else if (filenameField?.startsWith('filename*=')) {
            return decodeURIComponent(rightPart?.split("'")?.at(-1));
        }
        return null;
    }

-1
投票

如果您不使用多部分主体,那么您可以使用此功能。它从 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" 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"))); 
© www.soinside.com 2019 - 2024. All rights reserved.