一尘不染

记住并重新填充文件输入

javascript

注意:

我有一个网站,允许用户多次上传文件进行处理。目前,我只有一个文件输入,但是我希望能够记住用户的选择并将其显示在屏幕上。

我想知道的是在用户选择文件后,我会记住他们的选择,并重新显示输入的文件,并在重新加载页面时预先选择该文件。我所需要知道的是如何记住并重新填充文件输入。

我也欢迎不使用文件输入的方法(如果可能的话)。

我正在使用JQuery


阅读 423

收藏
2020-05-01

共1个答案

一尘不染

好的,您想要“记住并重新 填充文件输入 ”,“记住他们的选择并 重新显示 带有 重新加载页面时预先选择 的文件的文件输入_。”。
在对我之前的回答的评论中,您声明您正在没有真正开放的选择:“对不起,但没有Flash和Applet,只是javscript和/或文件输入,可能会拖放。”

但是,固执地someone子的人(一定程度上不一定是一件坏事)可能会认为这些答案是:“不,因为我是这么说的”,这与当时的确有所不同:“不,这是规格禁止”。
因此,这是我第三次也是最后一次尝试回答您的问题(我将您引导到水坑,将您带到河边,现在将您推向水源,但我不能让您喝水)。

编辑3:

您实际上想在RFC1867第3.4节中描述/“建议”您要做的事情:

VALUE属性可能与<INPUTTYPE=file>标记一起用于默认文件名。这种用法可能取决于平台。但是,在一个以上事务的序列中,这可能很有用,例如,避免让用户一遍又一遍地提示相同的文件名。

实际上, _[HTML
4.01规范的第17.4.1节规定:

用户代理可以将value属性的值用作初始文件名。

(通过“用户代理”,它们的意思是“浏览器”)。

鉴于javascript既可以修改和提交表单(包括文件输入),又可以使用CSS隐藏表单/表单元素(例如文件输入)的事实,仅上述语句就可以实现静默上传来自用户计算机的文件,而没有他的意图/知识。
显然这是极其重要的,这是不可能的,因此,RFC1867(在上文中)在 第8节“安全注意事项”中 指出:

用户代理不要发送用户未明确要求发送的任何文件,这一点很重要。因此,HTML解释器应该确认可能会建议使用的任何默认文件名<INPUT TYPE=fileVALUE="yyyy">

但是,唯一实现该功能的浏览器(我知道)是 Opera (某些旧版本): Opera :它接受<input type="file"value="C:\foo\bar.txt>javascript(elm_input_file.value='c:\\foo\\bar.txt';)设置的或值。
如果在提交表单时此文件框未更改,Opera将弹出一个安全窗口,通知用户要将哪个文件上传到哪个位置(URL / Web服务器)。

现在,有人可能会争辩说所有其他浏览器都违反了规范,但这是错误的:由于规范说明:“ may”(未说“must”)“
..使用value属性作为初始文件名”。
而且,如果浏览器不接受设置文件输入值(又名该值只是“只读”),则浏览器也不需要弹出这样的“可怕”和“困难”的安全性-pop-up(如果用户不理解它(和/或被“限制为”始终单击“确定”),这甚至可能无法达到目的)。

然后,让我们快速前进到HTML5。
在这里,所有这些歧义都被清除了(但是仍然有些令人费解):

在 4.10.7.1.18文件上传状态下,我们可以阅读簿记详细信息

  • 值IDL属性处于模式文件名。
  • 元素的value属性必须省略。

因此,必须省略文件输入的value属性,但它也可以在4.10.7.4通用输入元素API中
描述的称为“文件名”的“模式”下运行:

值IDL属性允许脚本操纵输入元素的值。该属性采用以下方式之一,以定义其行为:

跳至此“ 模式文件名 ”:

获取时,它必须返回字符串“C:\fakepath\”,后跟所选文件列表中第一个文件的文件名(如果有),或者返回空字符串(如果列表为空)。设置时,如果新值是空字符串,则必须清空所选文件的列表;否则,它必须抛出InvalidStateError异常。

让我重复一遍:如果试图将文件输入值设置为不为空的字符串,则“它将must引发InvalidStateError异常!!!”
(但是可以通过将输入字段的值设置为空字符串来清除输入字段。)

因此,在当前以及可预见的HTML5未来 (以及Opera以外的过去)中, 只有用户可以填充文件输入
(通过浏览器或操作系统提供的“文件选择器”)。 不能使用javascript或通过设置默认值将文件输入(重新)填充到文件/目录中。

获取文件名/文件路径

现在, 假设 并非没有可能用默认值(重新)填充文件输入,那么显然您需要完整的路径:directory + filename(+扩展名)。

过去,某些浏览器(例如(最著名的)IE6(最高IE8)) 的确 显示了完整路径+文件名作为值:alert( elm_input_file.value );在javascript中只是一个简单的等,浏览器也将此完整路径+文件名(+扩展名)发送给了提交表单时接收服务器。
注意:某些浏览器还具有’file或fileName’属性(通常发送到服务器),但是显然其中不包含路径。

这是现实的安全/隐私风险:恶意网站(所有者/开发者)可能获取到用户主目录的路径(个人资料,帐户,Cookie,注册表的用户部分,历史记录,收藏夹,桌面等在其中)。位于已知的恒定位置),那么典型的非技术Windows用户将从以下位置上传文件:C:\Documentsand Settings\[UserName]\My Documents\My Pictures\kinky_stuff\image.ext
在传输数据(甚至通过https“加密”)或“安全”存储该数据时,我什至没有谈到风险!

因此,越来越多的替代浏览器开始遵循最古老的,经过验证的安全措施之一:在需要了解的基础上共享信息。
而且绝大多数网站 不需要知道 文件路径,因此它们只显示文件名(+扩展名)。

到IE8发布时,MS决定参加比赛,并添加了一个URLAction选项,称为“上传文件时包含本地目录路径”,该选项在一般Internet区域中设置为“禁用”(在Internet区域中设置为“启用”)信任区域)。

这项更改造成了小麻烦(主要是在“针对IE优化”环境中),所有自定义代码和专有“控件”都无法获取已上传文件的文件名:它们被硬编码为期望包含完整路径,并在最后一个反斜杠之后提取部分(如果幸运的话,也可以提取正斜杠)。[1]
HTML5伴随而来,正如您在上面所阅读的,“ mode filename”指定:

获取时,它必须返回字符串“C:\fakepath\”,后跟所选文件列表中第一个文件的文件名(如果有),或者返回空字符串(如果列表为空)。

他们注意到

这种“伪造路径”的要求是历史的不幸

由于历史原因,值IDL属性在文件名前加上字符串“C:\fakepath\”。一些旧版用户代理实际上包括完整路径(这是一个安全漏洞)。结果,以向后兼容的方式从值IDL属性获取文件名是不容易的。以下函数以适当兼容的方式提取文件名:

function extractFilename(path) {
   if (path.substr(0, 12) == "C:\\fakepath\\")
    return path.substr(12); // modern browser
   var x;
   x = path.lastIndexOf('/');>       if (x >= 0) // Unix-based path
     return path.substr(x+1);
   x = path.lastIndexOf('\\');
   if (x >= 0) // Windows-based path
     return path.substr(x+1);
   return path; // just the filename
 }

注意:我认为这个功能很愚蠢:整个问题总是要有一个假的Windows路径来解析。.因此,第一个’if’不仅无用,甚至会引发一个错误:想象一个用户使用一个较旧的浏览器上传来自的文件:(c:\fakepath\Some folder\file.ext因为它将返回:Some folder\file.ext)…
我将简单地使用:

function extractFilename(s){ 
  // returns string containing everything from the end of the string 
  //   that is not a back/forward slash or an empty string on error
  //   so one can check if return_value===''
  return (typeof s==='string' && (s=s.match(/[^\\\/]+$/)) && s[0]) || '';
}

(如HTML5规范明确规定的那样)。

让我们回顾一下(获取路径/文件名):

  • 较旧的浏览器(以及较新的浏览器,可以将其启用为IE> = 8之类的选项)将显示完整的Windows / Unix路径
  • 较旧的浏览器将不会显示任何路径,仅显示文件名(+扩展名)
  • 当前/未来/与HTML5兼容的浏览器将始终在字符串前添加字符串:c:\fakepath\在获取文件输入 时将其添加到文件名。
    最重要的是,它们仅会返回第 一个 文件名(来自“选定文件列表”)。文件输入接受多个文件,并且用户已选择多个文件。

因此,在最近的过去,当前以及可预见的HTML5将来,通常只会获得文件名。

这使我们进入需要检查的最后一件事:此“选定文件列表” /多个文件,这使我们进入了难题的第三部分:

(HTML5)文件API

首先:“文件API”不应与“ 文件系统API ”
混淆,这是文件系统API的摘要:

该规范定义了一个用于导航文件系统层次结构的API,并定义了一种方法,用户代理可以通过该方法将用户本地文件系统的沙盒部分公开给Web应用程序。它建立在[FILE-
WRITER-ED]之上,而[FILE-WRITER-ED]又建立在[FILE-API-ED]之上,每一个都增加了不同的功能。

“用户本地文件系统的沙盒部分”已经明确表明,不能使用它来将用户文件保留在沙箱之外(因此与问题无关,尽管 可以
将用户选择的文件复制到持久性本地存储并使用AJAX等重新上传该 副本
。在失败的上载时作为“重试”很有用。。但这并不是指向可能在此期间发生更改的原始文件的指针)。
更为重要的是,只有webkit(认为chrome的旧版本)实现了此功能,因此该规范很可能无法生存is no more actively maintained, the specification is abandonned for the moment as it didn't get any significant traction

让我们继续“ File API ”, 它抽象地告诉我们:

该规范提供了一个API,用于表示Web应用程序中的文件对象,以及以编程方式选择它们并访问其数据。这包括:

  • FileList接口,它表示从基础系统中单独选择的文件的数组。用于选择的用户界面可以通过调用<input type="file">,即,当输入元素处于文件上载状态[HTML]时。
  • Blob接口,表示不可变的原始二进制数据,并允许将Blob对象中的字节范围作为单独的Blob访问。
  • 文件接口,包括有关文件的只读信息属性,例如文件名和文件的最后修改日期(在磁盘上)。
  • FileReader接口,提供读取文件或Blob的方法,以及事件模型以获取这些读取的结果。
  • 用于与二进制数据(例如文件)一起使用的URL方案,以便可以在Web应用程序中引用它们。

因此,FileList可以由文件模式下的输入字段填充<input type="file">
这意味着上述关于价值属性的所有内容仍然适用!

当输入字段处于文件模式时,它会获得一个只读属性files,该属性类似于数组FileListobject,它引用输入元素的用户选择的文件,并且可以通过进行访问FileListinterface。我是否提到类型的files-attribute FileList只读的(文件API第5.2节)?:

HTMLInputElement接口[HTML]具有FileList …类型的只读属性。

好吧,拖放呢?

从mdn文档-使用拖放选择文件

真正的魔力发生在drop()函数中:

 function drop(e) {
  e.stopPropagation();
   e.preventDefault();

   var dt = e.dataTransfer;
   var files = dt.files;

   handleFiles(files);
 }

在这里,我们从事件中检索dataTransfer字段,然后从中提取文件列表,并将其传递给handleFiles()。从现在开始,无论用户使用输入元素还是拖放操作,文件处理都是相同的。

因此,(就像输入字段type =“file”一样),该事件的dataTransfer属性具有类似数组的属性files,该属性类似于数组,FileListobject并且我们刚刚(如上所述)了解到FileList是 只读的

FileList包含对用户选择(或放置在放置目标上)的文件的引用以及一些属性。从文件API第7.2节“文件属性”中,我们可以阅读:

名称

文件名;获取时,必须以字符串形式返回文件名。不同系统上有许多文件名变体。这只是文件名,没有路径信息。获取时,如果用户代理无法提供此信息,则必须返回空字符串。

lastModifiedDate

文件的最后修改日期。获取时,如果用户代理可以使此信息可用,则必须返回一个新的Date
[HTML]对象,该对象初始化为文件的最后修改日期。如果最后修改日期和时间未知,则该属性必须将当前日期和时间作为Date对象返回。

并且有一个size属性:

F.size与fileBits Blob参数的大小相同,该参数必须是F的不可变原始数据。

同样,没有路径,只有 只读 文件名。

从而:

  • (elm_input||event.dataTransfer).files 给出FileList对象。
  • (elm_input||event.dataTransfer).files.length 给出文件数。
  • (elm_input||event.dataTransfer).files[0] 是选择的第一个文件。
  • (elm_input||event.dataTransfer).files[0].name是选择的第一个文件的文件名
    (这是value从输入type =“ file”返回的文件名)。

那么,“用于二进制数据(例如文件,以便它们可以在Web应用程序中被引用)使用的URL方案”又如何呢?它肯定可以保存用户选择的文件的私有引用吗?

通过File API-Blob和File的URL参考,我们可以了解到:

本规范使用以下URL定义了一种方案:
blob:550e8400-e29b-41d4-a716-446655440000#aboutABBA。

这些都存储在其中URL store(浏览器甚至应该在板上拥有自己的小型HTTP服务器,因此人们可以在CSS,img
src甚至XMLHttpRequest中使用这些url。

可以使用以下方法创建这些Blob URL

  • var myBlobURL=window.URL.createFor(object);会返回一个Blob URL首次使用后自动撤销的。
  • var myBlobURL=window.URL.createObjectURL(object, flag_oneTimeOnly);返回可重用的代码Blob URL(除非flag_oneTImeOnly的计算结果为true),并且可以使用撤销该代码window.URL.revokeObjectURL(myBlobURL)

Bingo,您可能会认为…但是… URL Store仅在会话期间维护(因此,它仍将在同一会话中保留,因此它将在页面刷新后继续存在),并且在卸载文档时丢失。

MDN-使用对象网址

对象URL是标识File对象的字符串。每次调用window.URL.createObjectURL()时,都会创建一个唯一的对象URL,即使您已经为该文件创建了对象URL。这些都必须释放。尽管它们在文档卸载时自动释放,但是如果页面动态使用它们,则应通过调用window.URL.revokeObjectURL()显式释放它们。

这意味着,即使将Blob URL 字符串 存储在cookie或持久性本地存储中,该字符串在新会话中也将毫无用处!

这应该带给我们一个完整的圈和最后的结论:
无法(重新)填充输入字段或用户选择的文件 (该 文件 不在浏览器的“本地存储”区域中)。
(除非您强迫用户使用过时的Opera版本,或强迫用户使用IE和某些activeX编码/模块(实现自定义文件选择器)等)

2020-05-01