记住并重新填充文件输入

IT技术 javascript html file-upload fileapi
2021-01-21 08:12:29

笔记:

下面的答案反映了 2009 年旧浏览器的状态。 现在您实际上可以在 2017 年通过 JavaScript 设置文件输入元素的值。

有关详细信息以及演示,请参阅此问题中的答案:
如何以编程方式设置文件输入值(即:拖放文件时)?

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

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

我也对不使用文件输入的方法持开放态度(如果可能的话)。

我正在使用 JQuery

4个回答

好的,您想“记住并重新填充文件输入”,“记住他们的选择并使用重新加载页面时预先选择文件重新显示文件输入”..
并且在对我之前的答案的评论中,您声明您是对替代方案并不真正开放:“抱歉,没有 Flash 和小程序,只有 javscript 和/或文件输入,可能会拖放。”

我注意到在浏览(一段)重复的问题123等),几乎所有其他的答案是沿着线:“不,你不能,那将是一个安全问题”,然后任选通过一个简单的概念或代码示例概述安全风险。

然而,像骡子一样固执的人(在一定程度上不一定是坏事)可能会认为这些答案是:“不,因为我这么说”,这确实是不同的:“不,这里是规格禁止它”。
所以这是我第三次也是最后一次回答你的问题(我带你到水坑,我带你到河边,现在我推你到源头,但我不能让你喝)。

编辑3:

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

VALUE 属性可能与<INPUT TYPE=file>默认文件名的标签一起使用这种用途可能与平台有关。然而,在不止一个事务的序列中,这可能是有用的,例如,避免让用户一遍又一遍地提示输入相同的文件名。

事实上,HTML 4.01 规范第 17.4.1 节规定:

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

(“用户代理”是指“浏览器”)。

鉴于javascript可以修改和提交表单(包括文件输入)并且可以使用css隐藏表单/表单元素(如文件输入)的事实,仅上述语句就可以静默上传在用户无意/不知情的情况下从用户的计算机中获取文件。
显然,这是不可能的,这一点非常重要,因此,(以上)RFC1867 在第 8 节安全注意事项中指出

重要的是,用户代理不发送用户未明确要求发送的任何文件。因此,HTML 解释代理应确认任何可能以<INPUT TYPE=file VALUE="yyyy">.

但是,唯一实现此功能的浏览器(我知道)是(一些旧版本)Opera:它接受<input type="file" value="C:\foo\bar.txt>由 javascript ( elm_input_file.value='c:\\foo\\bar.txt';)设置or 值
当这个文件框在表单提交时没有改变时,Opera 会弹出一个安全窗口,通知用户哪些文件将被上传到什么位置(url/网络服务器)。

现在,人们可能会认为,所有其他的浏览器分别对违反规范的,但是这将是错误的:因为该规范指出:“ may”(它并没有说“ must‘)’..使用value属性为初始文件名”。
而且,如果浏览器不接受设置文件输入值(也就是,将该值设为“只读”),那么浏览器也不需要弹出这样一个“可怕”和“困难”的安全性-pop-up(如果用户不理解它(和/或“有条件”总是单击“确定”),它甚至可能无法达到其目的)。

然后让我们快进到 HTML 5..
在这里所有这些歧义都被清除了(但它仍然需要一些令人费解):

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

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

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

value IDL 属性允许脚本操作输入元素的值。该属性处于以下模式之一,用于定义其行为:

跳到这个“模式文件名”:

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

让我再说一遍:如果有人试图将文件输入值设置为非空字符串,“它会must抛出InvalidStateError异常”!!!(但可以通过将其值设置为空字符串来清除输入字段。)

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

获取文件名/文件路径

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

过去,一些浏览器,如(最显着的)IE6(直到 IE8)确实将完整路径+文件名作为值显示:alert( elm_input_file.value );在 javascript 中只是一个简单的等,并且浏览器也将这个完整路径+文件名(+ 扩展名)发送到表单提交时的接收服务器。
注意:一些浏览器也有“文件或文件名”属性(通常发送到服务器)但显然这不会包括路径..

这是一个现实的安全/隐私风险:恶意网站(所有者/利用者)可以获得用户主目录的路径(个人资料、帐户、cookie、注册表的用户部分、历史记录、收藏夹、桌面等是位于已知的固定位置)当典型的非技术 Windows 用户将从以下位置上传他的文件时:C:\Documents and Settings\[UserName]\My Documents\My Pictures\kinky_stuff\image.ext
我什至没有谈论传输数据(甚至通过 https 进行“加密”)或“安全”存储这些数据时的风险!

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

到 IE8 发布时,MS 决定跟上竞争,并添加了一个 URLAction 选项,称为“上传文件时包括本地目录路径”,对于一般 Internet 区域设置为“禁用”(在一般 Internet 区域中设置为“启用”)。受信任区域)默认情况下。

此更改造成了一个小破坏(主要是在“为 IE 优化”环境中),其中各种自定义代码和专有“控件”都无法获取上传文件的文件名:它们被硬编码为期望包含一个完整的路径并提取最后一个反斜杠之后的部分(如果幸运的话,或者正斜杠......)。1 , 2

随之而来的是 HTML5,
正如您在上面阅读的那样,“模式文件名”指定:

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

他们注意到

这种“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-path 来解析..所以第一个 '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\当获取文件输入的文件名值,
    最重要的是,他们将只返回第一个(从“选定的文件列表”),文件名应的file-input 接受多个文件并且用户选择了多个文件。

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

这将我们带到了我们需要检查的最后一件事:这个“选定文件列表”/多个文件,这将我们带到了谜题的第三部分:

(HTML5) 文件 API

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

该规范定义了一个用于导航文件系统层次结构的 API,并定义了一种方法,通过该方法用户代理可以将用户本地文件系统的沙盒部分公开给 Web 应用程序。它建立在 [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

让我们继续“文件 API ”,
它的抽象告诉我们:

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

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

因此,FileList可以通过输入字段在文件模式来填充:<input type="file">
这意味着以上所有关于 value-attribute 的内容仍然适用!

当输入字段处于文件模式时,它会获得一个只读属性files,该属性类似于数组FileList object,引用输入元素的用户选择的文件,并且FileList interface.
我是否提到该类型的 -files属性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,它是一个类似数组的属性FileList object我们刚刚(上面)了解到 FileList 是只读的..

FileList 包含对用户选择(或放置在放置目标上)的文件的引用和一些属性。File API Section 7.2 File Attributes我们可以读到:

名称

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

最后修改日期

文件的最后修改日期。在获取时,如果用户代理可以使此信息可用,则必须返回一个新的 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从输入类型=“文件”返回的)。

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

文件 API - Blob 和文件参考的 URL,我们可以了解到:

该规范定义了一个包含以下 URL 的方案:
blob:550e8400-e29b-41d4-a716-446655440000#aboutABBA。

这些都存储在一个URL store(浏览器甚至应该有自己的迷你 HTTP 服务器,这样人们就可以在 css、img src 甚至 XMLHttpRequest 中使用这些 url。

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

  • var myBlobURL=window.URL.createFor(object);返回Blob URL在第一次使用后自动撤销的a
  • var myBlobURL=window.URL.createObjectURL(object, flag_oneTimeOnly);返回一个可重用的Blob URL(除非 flag_oneTImeOnly 评估为真)并且可以被撤销window.URL.revokeObjectURL(myBlobURL)

宾果游戏你可能会想……然而……URL Store它只在会话期间维护(所以它会在页面刷新时继续存在,因为它仍然是同一个会话)并且在文档被卸载时丢失。

来自MDN - 使用对象 URL

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

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

这应该让我们回到一个完整的循环和最终结论:
不可能(重新)填充输入字段或用户选择的文件(不在浏览器沙盒“本地存储”区域中)。
(除非你强迫你的用户使用过时的 Opera 版本,或者强迫你的用户使用 IE 和一些 activeX 编码/module(实现自定义文件选择器)等)

进一步阅读:
http : //www.cs.tut.fi/~jkorpela/forms/file.html
https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications
http://www.html5rocks.com /en/tutorials/file/filesystem/
http://www.html5rocks.com/en/tutorials/file/dndfiles/
http://caniuse.com/filereader
JavaScript:权威指南 - David Flanagan,第 22 章: filesystem api
如何保存 window.URL.createObjectURL() 结果以备将来使用?
Blob 持续多久?
如何解决 C:\fakepath?

感谢您接触 HTML5 及其新的可能性(例如文件 API)。还不知道这些。
2021-03-12 08:12:29
“所以,你告诉我还有机会!” forums.crackberry.com/attachments/android-f169/...
2021-03-17 08:12:29
不错的文章。我有一些评论/想法。如果我想input从拖放事件文件列表中填充的文件列表怎么办?对于不支持将文件拖放到input元素的浏览器中的用户体验一致性我不认为它会带来任何安全风险(我不知道文件位置,它是用户启动的操作,等等),但我看不到实现它的方法。
2021-03-18 08:12:29
嗨 GitaarLAB,感谢您的回答。我明白您在说什么,但您假设目录信息将存储为纯文本。如果我在 Java 中实现文件输入,我会将目录加密。然后我可以序列化和反序列化对象,从而记住并重新填充信息。我也无法更改该数据(如上面的示例所示)。我理解对安全性的需求,并且我认为这不会是纯文本。抱歉,没有 Flash 和小程序,只有 javscript 和/或文件输入,可能拖放。谢谢
2021-03-29 08:12:29
我从未谈论(或“假设”)您处理数据的安全性或不安全性,因此您不明白我之前的答案(或评论)您不能这样做。另外,为了确定,我想指出javascript 与 java 无关!!! 所以我稍微更新了我的答案(我谦虚地要求你不要删除你的问题,从而删除我在这个原始答案中所做的所有工作。同样的谦虚请求也适用于 mods)。
2021-04-07 08:12:29

在您的表单上创建一个输入字段。当用户选择一个文件时,将结果复制到此字段,例如:

jQuery('#inFile').change(
 function(){ jQuery('#inCopy').val( jQuery('#inFile').val() ); }
);

实际上,结果并没有完全复制,而是复制了“C:/fakepath/SELECTED_FILE_NAME”。虽然您不能设置文件输入的值,但您可以设置文本输入字段的值,而无需“C:/fakepath/”,因为服务器准备表单。

现在,当服务器取回表单时,检查文本输入字段。如果它以“C:/fakepath/”开头,则用户必须选择了一个新文件,因此请上传他们的新选择。如果没有,那么用户选择了之前的选择,这应该不是问题,因为根据原始问题,之前的选择已经上传并且应该(至少通过适当的编程,它可以)仍然是在服务器上。

我不明白为什么不。例如,mySQL 不应受到任何影响。
2021-03-15 08:12:29
这仍然不允许我能够保存有关文件的信息并在以后检索它。
2021-04-10 08:12:29

冒着踩到 GitaarLAB 提供的大量信息的脚趾的风险,我可能会建议 DaveWalley 非常接近为该问题提供实用的解决方案。你们两个都帮了我很大的忙,谢谢。

我带回家的信息是,

  1. 文件输入提供了一条单向街道。它坐在那里等待处理您的下一次上传。这就是它所做的,几乎。接收.
  2. 文件输入标签附近的 div 或只读文本表单输入可以提供服务即它可用于告诉用户,“这是当前上传的内容:” - 需要从服务器端逻辑填充要显示的文件名。

所以一个基本的用例是:

  • 用户通过网页上的表单输入上传文件
  • 服务器端逻辑存储文件
  • 在 Ajax 循环或网页重新加载中,服务器端代码标识要写入 div 的 fileName 字符串,“这是当前上传的内容”
  • 文件输入也可以重新呈现,以便用户也可以上传另一个文件/代替。

您可能需要做更多的工作来处理“* required”验证,但那是另一回事了。

如果您真正想要的是文件数据的临时持久性,例如如果页面上有错误或者如果您想在将数据提交到数据库之前有一个确认页面,那么这通常是通过放置临时文件并将数据保存在隐藏字段中。

这不会重新填充文件输入字段。但是您也可以只取输入文件的名称并将其放在文件输入框旁边。

像这样:

<input type=hidden name="filename" value="<?php echo $filename; ?>" />
<input type="file" name="uploadfile" size="50" />

<?php if (!empty($filename)) echo $filename; ?>