如何从内容处置中获取文件名

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。跨度> 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"   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,因为文件名可能包含附件

以上是关于如何从内容处置中获取文件名的主要内容,如果未能解决你的问题,请参考以下文章

如何从此 URL 从文件中获取内容?

如何使用可可中的 contentsOfDirectoryAtPath 从文件夹中获取内容列表?

j2me - 内容处置附件;文件名 - 如何获得它?

JS fetch API:如何使用一个异步函数从多个文件中获取内容?

如何使用 Python 从电子邮件内容中获取附加的 eml 文件?

如何使用 lambda 函数从 AWS s3 获取文本文件的内容?