【问题标题】: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

标签: javascript ajax filenames file-type content-disposition


【解决方案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;
    }
    

    几点说明:

    1. 这将采用 UTF-8 文件名的值(如果已设置),而不是 ascii 名称
    2. 下载时,您的浏览器可能会进一步更改名称以将某些字符(例如 ")替换为 _ (Chrome)
    3. ascii 模式最适合带引号的文件名,但支持不带引号的值。在这种情况下,它会将filename= 之后和下一个; 之前或标题值末尾的所有文本视为文件名。

    MDN Content Disposition Header

    【讨论】:

    • ASCII 文件名不必用单引号或双引号括起来。
    • @MoonStom 说得好,我会更新。
    • 这是一个很好的答案,我只需要进行一些更改并将i 添加到第一个正则表达式的末尾,因为我的标题返回为filename*=utf-8 而不是UTF-8。跨度>
    • dothyphen 在这种情况下不需要在字符集中进行转义。所以使用[.-] 而不是[\.\-]
    • 我不知道,但我也认为特殊字符应该被转义,即使它们在这种情况下不是特殊字符;它使代码更具可读性,即使在 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" 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"))); 
    

    【讨论】:

    • 您应该在删除它们之前检查是否有任何引号。即使您自己的示例处置标题也不使用任何引号。此外,为了向后兼容,处置标头通常包含“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

        【讨论】:

        【解决方案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,因为文件名可能包含附件
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-07-23
        • 2011-06-02
        • 2011-07-29
        • 1970-01-01
        • 2017-01-26
        • 2012-08-27
        • 1970-01-01
        相关资源
        最近更新 更多