新的 API 可让网页直接读写硬盘上的文件?

从前,网页中想要实现文件的读写是比较麻烦的,为了读取一个文件,咱们须要建立一个 type="file"input 标签让用户选择一个文件,而为了保存一些内容到本地硬盘,咱们则须要使用 a 标签的 download 属性去触发文件保存。javascript

而连续的文件保存则没法实现了,由于向本地硬盘写入文件是必定要触发“保存”弹框的。要遍历一个本地的目录也是没法实现的。java

在最新版的 Chrome 78 中,Google 开启了一项实验性的特性:Native File System,这是一个全新的本地文件读写 API,当前还未开发完成,所以默认并无开启这项功能。git

若是想要体验这个 API 的话,能够在 chrome://flags 这个地址中搜索 Native File System API 进行启用。github

关于这个 API 的简介,能够参考:github.com/WICG/native…chrome

这是一个全新的文件读写 API,它带来了更加方便的本地文件读写方式,可是也带来了很大的安全风险,所以这个 API 只能在安全的页面中(好比部署了有效的 https 的页面) 调用,而且必须是经过用户的选择进行操做(也就是不能彻底后台自动执行)。数据库

打开并读取文件内容

为了读取本地文件,咱们须要调用 window.chooseFileSystemEntries(option) 接口,它接受一个 option 做为参数。数组

option 是一个对象,包含 4 个能够配置的配置项:浏览器

配置项 取值 解释
type 'openFile'/'saveFile'/'openDirectory' 操做类型,是打开文件,保存文件仍是打开文件夹,默认是 'open-file'
multiple boolean 容许用户选择单个文件仍是选择多个文件,默认为 false,即只容许选择单个文件
accepts object[] 指定弹出的文件选择框中容许选择的文件类型
accepts.description string 描述
accepts.mimeTypes string[] 容许选择的文件的 MIME 类型
accepts.extensions string[] 容许选择的文件的扩展名
excludeAcceptAllOption boolean 是否容许选择不在上面 accepts 列表中容许的文件类型,默认为 false,即容许选择任意文件(*.*

window.chooseFileSystemEntries(option) 返回一个 FileSystemHandle 对象(若 optionmultiple 被指定为 true,则返回一个 FileSystemHandle 数组)。安全

FileSystemHandle 是一个基类,实际上 chooseFileSystemEntries 根据 option 中指定的 type,返回的是它的一个子类 FileSystemFileHandleFileSystemDirectoryHandle异步

若指定的 option 是打开一个文件夹,则返回 FileSystemDirectoryHandle,能够进行目录的遍历,也能够打开一个指定的文件,打开文件将返回一个 FileSystemFileHandle。若指定的 option 是打开/保存一个文件,则直接返回 FileSystemFileHandle

经过 FileSystemFileHandle 上的 getFile() 函数,就能够获得一个 File 对象了。

这里获得 File 对象与 type="file"input 返回的 File 同样,你能够经过经过经过 File 对象的 API 获取文件的具体内容,好比对于文本文件,能够直接调用 .text() 获得字符串,对于二进制文件,能够调用 .arrayBuffer() 获得 ArrayBuffer 对象,也能够调用 .stream() 获得一个 ReadableStream

const handler = await window.chooseFileSystemEntries({
  type: 'openFile',
  accepts: [
    { description: '文本文件', extensions: ['txt'] },
  ],
});

const file = await handler.getFile();
const text = await file.text();
console.log(text);
复制代码

保存文件到本地硬盘

为了将文件保存到硬盘上,咱们须要调用 FileSystemFileHandle 对象上的 createWriter() 函数。

保存文件一般有两种模式,一种是“保存”,即覆盖原始文件,另外一种是“另存为”,即建立一个新的文件。

“另存为”的形式,咱们只须要从新调用 window.chooseFileSystemEntries() 提供保存文件的选项便可。而“保存”文件,咱们直接使用以前打开的文件的 FileSystemFileHandle 对象便可。

调用 createWriter() 函数后会返回一个 FileSystemWriter 对象,它包含三个函数:write(position, data)truncate(size)close()

write 函数的第一个参数 position 为一个数字,指示开始写出的位置,第二个参数为要写出的数据,能够是 ArrayBuffer 对象、ArrayBufferView 对象(Uint8ArrayDataView之类的)、Blob 对象或者直接提供一个要写出的字符串。

truncate 函数会将文件截断为指定长度。一般发生在要保存的文件比原始文件小,若是直接调用 write 函数,文件将只有前面一部分被改写,然后面的原始文件内容仍是会残留下来。

在调用 createWriter() 函数的时候,浏览器会先检查用户是否已经受权了文件写出权限,若是以前是以“打开文件”或是“打开文件夹”的形式打开的文件,则默认是没有文件写出权限的,则此时浏览器会提示用户以申请文件写出权限。若是用户拒绝了申请的权限,则将会抛出一个异常。

const handler = await window.chooseFileSystemEntries({
  type: 'saveFile',
  accepts: [
    { description: '文本文件', extensions: ['txt'] },
  ],
});

const writer = await handler.createWriter();
await writer.truncate(0);  // 清空文件
await writer.write(0, 'Hello World!');  // 在文件头写入字符串
await writer.close();

const file = await handler.getFile();
const text = await file.text();
console.log(text);
复制代码

遍历本地目录

在前面提到的 window.chooseFileSystemEntries() 函数中,option 能够指定打开一个文件夹,此时浏览器会向用户申请目录遍历权限,得到用户赞成以后,就能够获得一个 FileSystemDirectoryHandle 对象。

FileSystemDirectoryHandle 对象上拥有一个 getEntries() 函数,它返回一个异步迭代器,能够经过 for await ... of ... 循环对目录进行遍历。每次遍历获得的都是一个 FileSystemHandle 对象,能够根据 .isFile 属性与 .isDirectory 属性判断具体是 FileSystemFileHandle 对象仍是 FileSystemDirectoryHandle 对象。

除了使用 getEntries() 进行遍历,还能够经过 FileSystemDirectoryHandle 对象上的 getDirectory 函数打开一个指定的子目录,子目录一样是一个 FileSystemDirectoryHandle 对象;也能够经过 getFile 函数打开一个指定的文件,获得 FileSystemFileHandle 对象。

const handler = await window.chooseFileSystemEntries({
  type: 'openDirectory',
});

const ls = async (path, handler) => {
  const result = [];
  for await (const h of await handler.getEntries()) {
    if (h.isFile) {
      result.push(`${path}/${h.name}`);
    } else if (h.isDirectory) {
      const subDirectory = await ls(`${path}/${h.name}`, h);
      result.push(...subDirectory);
    }
  }
  return result;
};

const files = await ls(handler.name, handler);
console.log(files);
复制代码

安全性与权限

这个新的 Native File System API 的全部 API 均以 Promise 的形式进行返回,这使得浏览器能够在函数返回以前进行一切权限的申请与检查而不会阻塞其余代码的执行。在权限不足的时候,浏览器能够在 Promise 等待的阶段向用户申请权限。

为了不恶意网站滥用这个 API 向用户电脑写入恶意文件,window.chooseFileSystemEntries() 函数的调用必须是在安全的页面中,由用户主动触发的,也就是必须放在用户交互的回调中(好比按钮的点击事件),这与 type="file"input 相似。

浏览器可能会处于保护的目的对一些与操做系统相关的文件夹进行限制访问。而且在网站尝试向本地文件夹写入文件的时候,浏览器会给出通知。

为了不频繁地弹出权限警告,浏览器将会保存用户授予的权限,一旦用户授予网页访问某一文件,网页在被关闭以前都将拥有这个权限而不会过时。

一旦网页被关闭,全部的权限都将被回收,用户在下一次打开相同网站时,网站须要从新申请权限。

将来,网页能够将受权信息存入网页内置的数据库(IndexedDB)中,以持久化权限请求。

总结

到目前为止,这个 API 尚未开发完成,标准任然可能被修改或删除。可是这个 API 的出现无疑为 WebIDE 的开发提供了可能,将来可能能够再也不须要安装本地 IDE,直接在网页中打开本地的工程目录,对代码进行编辑、保存,并直接使用线上虚拟化环境进行代码的执行测试。

可是,这个 API 的出现是否会带来严重的安全问题,如今还不得而知,毕竟对于广大小白用户来讲,即使网站给出了安全警告,他们依旧会直接忽略。不是他们不重视,是他们根本看不懂。

好比即使是网站 HTTPS 配置错误,展现出了可能存在风险的警告,用户仍是会习惯性的选择“高级”-“继续进入”;因此出现了 HSTS,在网站出现 HTTPS 错误时,禁止用户继续使用网站。

如此可见,即使是浏览器明确提示你存在安全问题,用户仍是选择性忽略,因此对于 Native File System API 给出的权限申请,展现的只是个“提示框”,对于广大普通用户来讲根本“不会放在眼里”,直接无脑肯定就是了。

若用户访问到恶意网站,一个权限申请,一旦点击了“肯定”,整个硬盘的数据都被加密……

安全,与便捷,仍是须要一个平衡点!

emmmm……是否是跑题了。。。

附:标准文档:wicg.github.io/native-file…

一个 Chrome 实验室提供的纯文本编辑器 Demo:googlechromelabs.github.io/text-editor…

相关文章
相关标签/搜索