Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fetch): add support for file urls in fetch #1239

Closed
wants to merge 1 commit into from

Conversation

Linkgoron
Copy link
Member

@Linkgoron Linkgoron commented Feb 17, 2022

This PR adds support for file urls in fetch

refs: nodejs/node#42003

According to the fetch spec, file urls are "left as an exercise for the reader."
https://fetch.spec.whatwg.org/#scheme-fetch

@@ -882,7 +883,7 @@ async function schemeFetch (fetchParams) {
case 'file:': {
// For now, unfortunate as it is, file URLs are left as an exercise for the reader.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comment?

Copy link
Member Author

@Linkgoron Linkgoron Feb 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's from the spec itself, other comments also include the text from the spec itself.

async function fileFetch (fetchParams) {
const { request } = fetchParams
const url = requestCurrentURL(request)
const context = this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is context just not this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's the same style that's used in other methods, so I wanted to keep it the same.

const url = requestCurrentURL(request)
const context = this
if (request.method !== 'GET') {
return makeNetworkError(`Fetching files only supports the GET method. Received ${request.method}`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do browsers do with HEAD?

Copy link
Member Author

@Linkgoron Linkgoron Feb 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at what Deno does, I even kept the message the same. I assume that Chrome behaves in a similar way, but I'll check it out.

response.aborted = true
stream.destroy(new AbortError())
} else {
stream.destroy(new TypeError('terminated'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not specific to these PR but these errors should have codes?

(That seems orthogonal though)

Copy link
Member Author

@Linkgoron Linkgoron Feb 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at what's done in other methods, so changes might need to get made in other places as well.

t.ok(err.cause)
t.equal(err.cause.code, 'ENOENT')
}
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test AbortSignal?

Copy link
Member Author

@Linkgoron Linkgoron Feb 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, tests are the main thing that's missing and why I've put this as a draft (also need to see what to do with headers)

Copy link
Member

@benjamingr benjamingr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good progress

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a way to control the security of this. There are quite a few possible attacks when reading files from disk on servers.

My opinion is that we should aim to a "close by default" approach vs "open by default".

I would recommend we add a "FileSystemEnabledAgent" that can direct requests to disk in case we need it to be. This entity will be responsible to configure what paths could be served.

@Linkgoron
Copy link
Member Author

Linkgoron commented Feb 17, 2022

We need a way to control the security of this. There are quite a few possible attacks when reading files from disk on servers.

My opinion is that we should aim to a "close by default" approach vs "open by default".

I would recommend we add a "FileSystemEnabledAgent" that can direct requests to disk in case we need it to be. This entity will be responsible to configure what paths could be served.

This kind of change would make this less portable (which was one of the reasons why this was requested), as I assume that other implementation don't have such constructions (I'll check them out). Is going out of spec and adding a flag or forcing localURLsOnly when using file urls an option? Or were you thinking of somehow configuring everything before-hand and having some kind of global configuration for fetch (does something like that exist already, I couldn't find any)?

@benjamingr
Copy link
Member

We need a way to control the security of this. There are quite a few possible attacks when reading files from disk on servers.

So your concern is that we get an external URL from the user and they send over a file url and that lets them somehow access/manipulate data on the server?

@mcollina
Copy link
Member

This kind of change would make this less portable (which was one of the reasons why this was requested), as I assume that other implementation don't have such constructions (I'll check them out). Is going out of spec and adding a flag or forcing localURLsOnly when using file urls an option?

That's not correct. In the browser this is managed by CSP and others. Unfortunately Node.js is not a browser and we have to deal with more strict security constraints, so I don't think this can be enabled by default without checks.

@mcollina
Copy link
Member

So your concern is that we get an external URL from the user and they send over a file url and that lets them somehow access/manipulate data on the server?

Exactly that. Moreover I might want to send a URL to read a file that is restricted in a given folder.

@Linkgoron
Copy link
Member Author

Linkgoron commented Feb 17, 2022

This kind of change would make this less portable (which was one of the reasons why this was requested), as I assume that other implementation don't have such constructions (I'll check them out). Is going out of spec and adding a flag or forcing localURLsOnly when using file urls an option?

That's not correct. In the browser this is managed by CSP and others. Unfortunately Node.js is not a browser and we have to deal with more strict security constraints, so I don't think this can be enabled by default without checks.

Yes, of course. What I meant was that I didn't think the fetch method had such parameters. I edited my message after you already quoted me, where I asked if you meant that we should add some kind of global configuration for fetch that would allow such behaviour?

@mcollina
Copy link
Member

We usually include those configuration inside the Dispatcher system. That's the right place where the retrieving of file should happen and where the checks should be.

@benjamingr
Copy link
Member

Asked about this in Deno btw: denoland/deno#11925 (comment)

@KhafraDev
Copy link
Member

I think there should also be some warning that this behavior can change at any times; once (or if - file uris have been left to the implementation for years now) the spec includes steps for handling file:// urls, this will have to be rewritten and it's very likely that the behavior will change.

I believe that only Firefox and Deno handle file urls at the moment as well, which means it's not technically spec compliant and not widely available.

@Jamesernator
Copy link

Jamesernator commented Sep 18, 2022

While it's not a web option, could we just have an option to fetch to allow file urls? Browsers would simply ignore such an option so it would still be possible to write a compatible subset of fetch that works in both for loading package resources:

// file.js
const myResource = new URL("./resource.txt", import.meta.url);
 
const resourceText = await fetch(myResource, {
    // If file.js is loaded in Node.js then myResource will be some file:// url in which
    // case Node.js will see this option and allow it,
    // In browsers myResource will generally be a https:// url and will just ignore this
    // options proceeding per normal
    allowFileURLs: true,
}).then(res => res.text());

By having such an option, other fetch calls could be presumed to be "safe" (to the extent arbitrary fetches are actually safe in node).

@Linkgoron
Copy link
Member Author

Linkgoron commented Sep 18, 2022

While it's not a web option, could we just have an option to fetch to allow file urls? Browsers would simply ignore such an option so it would still be possible to write a compatible subset of fetch that works in both for loading package resources:

// file.js
const myResource = new URL("./resource.txt", import.meta.url);
 
const resourceText = await fetch(myResource, {
    // If file.js is loaded in Node.js then myResource will be some file:// url in which
    // case Node.js will see this option and allow it,
    // In browsers myResource will generally be a https:// url and will just ignore this
    // options proceeding per normal
    allowFileURLs: true,
}).then(res => res.text());

By having such an option, other fetch calls could be presumed to be "safe" (to the extent arbitrary fetches are actually safe in node).

I agree that this feature is useful, and I'd be happy to get this into undici if we can agree on how to make everyone happy with its security.

@mcollina
Copy link
Member

Go ahead and add this option, I think it's a good addition. I would use a more ugly-and-prefixed name for it to avoid any potential conflicts with whatwg. Anyhow, I'd recommend you to open an issue at wintercg/fetch.

@KhafraDev
Copy link
Member

I would prefer to leave this to wintercg/fetch#5 instead of implementing a non-spec compliant solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants