<h2 dir="auto">Context</h2>
<p dir="auto">Currently, <code class="notranslate">fetch()</code> does not allow <code class="notranslate">file://</code> schemes to be fetched. The inability to do this becomes a significant usability issue, making code less portable. It has become quite common in Deno to write isomorphic code that uses <code class="notranslate">import.meta.url</code> as a base for accessing a resource when writing server code. Currently a user would have to code path to determine if the resource they are trying to access is local or on the network at a code level.</p>
<p dir="auto">It was requested in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="435246962" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/2150" data-hovercard-type="issue" data-hovercard-url="/denoland/deno/issues/2150/hovercard" href="https://github.com/denoland/deno/issues/2150">#2150</a> is the 10th most 👍  open issue at the time of this writing.</p>
<p dir="auto">That being said, the web platform does not support it, because of the security considerations and undefined behavior. Specifically the <a href="https://fetch.spec.whatwg.org/#scheme-fetch" rel="nofollow">Fetch Living Standard</a> says:</p>
<blockquote>
<p dir="auto">For now, unfortunate as it is, file URLs are left as an exercise for the<br>
reader.</p>
<p dir="auto">When in doubt, return a network error.</p>
</blockquote>
<p dir="auto">The specification does not prohibit <code class="notranslate">file</code> protocols, but the entire behavior is undefined.</p>
<p dir="auto">And Node.js libraries providing the <code class="notranslate">fetch()</code> API have decided not to implement local file URLs, partly because of the undefined behavior and the security concerns that come with Node.js having a trust-by-default model.</p>
<h3 dir="auto">Firefox</h3>
<p dir="auto">Firefox is the only mainstream browser to support local "file://" URLs well and would be the only one considered as setting precedence with local file <code class="notranslate">fetch()</code>.</p>
<p dir="auto">Since Firefox 67, by default, local files create their own unique opaque origin, instead of sharing an origin (<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSRequestNotHttp" rel="nofollow">ref</a>). This means the only file that can be fetched is the file itself.  For example scripts that come from <code class="notranslate">file:///example/test.html</code> can only do <code class="notranslate">fetch("file:///example/test.html")</code> or <code class="notranslate">fetch("./test.html")</code>.  Every other one will display <code class="notranslate">Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at</code> in the console and throw a network error.</p>
<p dir="auto">If you change the config option <code class="notranslate">privacy.file_unique_origin</code> to <code class="notranslate">false</code>, then you can fetch local files from pages that have a local origin.  There are the observed behaviours of fetching local files this way:</p>
<ul dir="auto">
<li>URLs are relative to the source file/<code class="notranslate">window.location</code></li>
<li>The <code class="notranslate">window.location.origin</code> is opaque, irrespective of it being unique per file or not.</li>
<li>If the file is present on the file system, the fetch is resolved as a 200.</li>
<li>Irrespective of the method, the response is the same.  This means <code class="notranslate">HEAD</code> returns with a body.</li>
<li>There are no headers set on the response (including no content-size).</li>
<li>If the file is not present on the file system, the <code class="notranslate">Cross-Origin Request Blocked</code> error is logged to the console, and a network error is thrown. (I suspect the behaviour around the <code class="notranslate">Cross-Origin Request Blocked</code> is part of the security mitigation to limit the ability of scripts to try to detect if the <code class="notranslate">privacy.file_unique_origin</code> is <code class="notranslate">true</code> or <code class="notranslate">false</code>).</li>
</ul>
<p dir="auto">Since you have to do advanced configuration to be able to enable this, it feels like its behaviours shouldn't overly influence, as most code in the wild wouldn't expect to be able to fetch local files.</p>
<h2 dir="auto">Solution</h2>
<h3 dir="auto">Scheme Fetch</h3>
<p dir="auto">Building upon <a href="https://fetch.spec.whatwg.org/#scheme-fetch" rel="nofollow">4.2. Scheme fetch</a> and switch on the current URL's scheme, and running the associated steps:</p>
<p dir="auto">↪ <code class="notranslate">"file"</code></p>
<ol dir="auto">
<li>Run these steps, but <a href="https://infra.spec.whatwg.org/#abort-when" rel="nofollow">abort when</a> the ongoing fetch is <a href="https://fetch.spec.whatwg.org/#concept-fetch-terminate" rel="nofollow">terminated</a>:
<ol dir="auto">
<li>If <em>request</em>'s <a href="https://fetch.spec.whatwg.org/#concept-request-method" rel="nofollow">method</a> is not <code class="notranslate">GET</code>, then return a <a href="https://fetch.spec.whatwg.org/#concept-network-error" rel="nofollow">network error</a>.</li>
<li>Set <em>result</em> to the result of the resolution of <code class="notranslate">op_open_async</code> with an options argument of <em>path</em> set to the result of <code class="notranslate">pathFromURL()</code> for the <em>url</em> and <em>options</em> set to <em>read</em> to <em>true</em>.</li>
<li>If the <em>result</em> is an <em>error</em>, return a <a href="https://fetch.spec.whatwg.org/#concept-network-error" rel="nofollow">network error</a>.</li>
<li>Otherwise, set <em>file</em> to the an instance of <a href="#filestream"><code class="notranslate">FileStream</code></a> constructed with the <code class="notranslate">rid</code> from <em>result</em>.</li>
<li>Let <em>response</em> be a new <a href="https://fetch.spec.whatwg.org/#concept-response" rel="nofollow">response</a> whose <a href="https://fetch.spec.whatwg.org/#concept-response-status-message" rel="nofollow">status message</a> is <code class="notranslate">OK</code>.</li>
<li>Set <em>response</em>'s <a href="https://fetch.spec.whatwg.org/#concept-response-body" rel="nofollow">body</a>'s <a href="https://fetch.spec.whatwg.org/#concept-body-stream" rel="nofollow">stream</a> to the <em>file</em>'s <code class="notranslate">stream</code>.</li>
<li>Return <em>response</em>.</li>
</ol>
</li>
<li><a href="https://infra.spec.whatwg.org/#if-aborted" rel="nofollow">If aborted</a>, then:
<ol dir="auto">
<li>Let <em>aborted</em> be the termination's aborted flag.</li>
<li>If <em>aborted</em> is set, then return an <a href="https://fetch.spec.whatwg.org/#concept-aborted-network-error" rel="nofollow">aborted network error</a>.</li>
<li>Return a <a href="https://fetch.spec.whatwg.org/#concept-network-error" rel="nofollow">network error</a>.</li>
</ol>
</li>
</ol>
<h3 dir="auto">Body used</h3>
<p dir="auto"><em>TBC</em> steps to close the <code class="notranslate">FileStream</code>.</p>
<h3 dir="auto"><code class="notranslate">FileStream</code></h3>
<p dir="auto">An internal class which encapsulates a success result of <code class="notranslate">op_open_async</code> and performs <code class="notranslate">op_read_async</code> to read and enqueue chunks.</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class FileStream {
  constructor(rid: number);

  readonly closed: boolean;
  readonly rid: number;
  readonly readable: ReadableStream&lt;Uint8Array&gt;;

  close(): Promise&lt;void&gt;;
}"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-smi">FileStream</span> <span class="pl-kos">{</span>
  <span class="pl-c1">constructor</span><span class="pl-kos">(</span><span class="pl-s1">rid</span>: <span class="pl-smi">number</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

  <span class="pl-k">readonly</span> <span class="pl-c1">closed</span>: <span class="pl-smi">boolean</span><span class="pl-kos">;</span>
  <span class="pl-k">readonly</span> <span class="pl-c1">rid</span>: <span class="pl-smi">number</span><span class="pl-kos">;</span>
  <span class="pl-k">readonly</span> <span class="pl-c1">readable</span>: <span class="pl-smi">ReadableStream</span><span class="pl-kos">&lt;</span><span class="pl-smi">Uint8Array</span><span class="pl-kos">&gt;</span><span class="pl-kos">;</span>

  <span class="pl-c1">close</span><span class="pl-kos">(</span><span class="pl-kos">)</span>: <span class="pl-smi">Promise</span><span class="pl-kos">&lt;</span><span class="pl-smi"><span class="pl-k">void</span></span><span class="pl-kos">&gt;</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">The <code class="notranslate">readable</code> should have a similar chunk queuing strategy to reading the body of a network request, in that it uses <code class="notranslate">"byte"</code> type and the <code class="notranslate">pull()</code> algorithm which provides back pressure on the stream.</p>
<h3 dir="auto">Considerations</h3>
<ul dir="auto">
<li>Security considerations should be returned as thrown errors form the <code class="notranslate">op_open_async</code> and be surfaced in a meaningful way. Because this builds upon the Fetch specification, any error condition, including security or non-found (for like a blob URL) return network errors.</li>
<li>Relative URLs are not supported. Determining a base is complex and is prone to a lot of issues. If we have an established standard around the <em>root</em> of a program, related to auto-discovery of a project configuration and have an established pattern around an implicit <code class="notranslate">window.location</code> and worker <code class="notranslate">.location</code>, then we could consider it, but using <code class="notranslate">import.meta.url</code> is the only viable near-term solution.</li>
<li>Additional features, like range requests, last modified headers, supporting <code class="notranslate">HEAD</code> requests, etc. could be added in the future. It feels appropriate to simply focus on a minimum-viable solution.</li>
<li>It is best to leave content-type up to the consumer, as it becomes an very opinionated thing to determine content-types based on extensions in the file system.</li>
<li>Unlike blobs, the <code class="notranslate">content-length</code> header is not set, as the implementation reads the file in a streaming fashion, meaning that the length of the content can change as the content is being read. Therefore consumers who need to "forward" the content length will need to calculate the length through some other mechanism. (Also, note that this header is not set on Firefox when fetching local files).</li>
</ul>
<p dir="auto">The global object currently surfaces the following:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="&gt; this
{ Deno, denoMain, window, atob, btoa, fetch, clearTimeout, clearInterval,
setTimeout, setInterval, location, Blob, File, CustomEventInit, CustomEvent,
EventInit, Event, EventTarget, URL, URLSearchParams, Headers, FormData,
TextEncoder, TextDecoder, performance, onmessage, workerMain,
workerClose, postMessage, Worker }"><pre class="notranslate"><code class="notranslate">&gt; this
{ Deno, denoMain, window, atob, btoa, fetch, clearTimeout, clearInterval,
setTimeout, setInterval, location, Blob, File, CustomEventInit, CustomEvent,
EventInit, Event, EventTarget, URL, URLSearchParams, Headers, FormData,
TextEncoder, TextDecoder, performance, onmessage, workerMain,
workerClose, postMessage, Worker }
</code></pre></div>
<p dir="auto">Since <code class="notranslate">fetch</code> uses the <code class="notranslate">Request</code> and <code class="notranslate">Response</code> interfaces, and those interfaces are often used along with fetch, those too should be surfaced (they are in Chrome, for example). The absence of these interfaces causes <a href="https://github.com/mhart/aws4fetch">aws4fetch</a> to fail with an <code class="notranslate">Uncaught ReferenceError: Request is not defined</code> error.</p>