Open Ademan opened 4 months ago
Of course, after finishing the PR I had a good idea for a search term, and discovered that Chrome has at least experimental support for streaming the request body: https://developer.chrome.com/docs/capabilities/web-apis/fetch-streaming-requests
If/when this becomes widely available, implementing NIP-98 as-is, without restrictions on file size, should be possible in the browser.
Significant implementation effort remains, however, and it's uncertain if it will ever be supported outside of chrome (at the time of writing, Firefox 127.0.2 (64-bit) does not support this feature).
I would say for Why not strike the conflicting language from NIP-96 and implement NIP-98 as-is?
to assume a second API would allow large uploads and this would be specific as small POST based?
I am using TUS mainly for large data and will aim to use that with nostr next year hopefully.
So any decision I think should be made assuming a limit on the filesize, and possibly even a means to know what that limit actually is via an API?
Thoughts?
On NIP-96 it should be written hex-encoded instead of base64... it was probably a copy/paste mistake when extracting things from NIP-98.
I like the payload_multipart addition to make it follow NIP-98 correctly.
payload_multipart
probably needs a bit of security review, due to its flexibility. For instance, I have an (unsubstantiated) suspicion there might be a length extension attack possible under certain circumstances (I suspect it could only be when there are two or more fields named in the payload_multipart
hash).
I believe it would be eliminated completely if the payload_multipart
hash is calculated as sha256( sha256(field-0) || sha256(field-1) || ...)
instead.
After sleeping on it for a few days (and being swamped with work, sorry for letting this stagnate). I do think that sha256( sha256(field-0) || sha256(field-1) || ...)
is probably the right approach, but it does mean that for ["payload_multipart", "<hash>", "file"]
in NIP-96, the <hash>
will be sha256(sha256(file-field-contents))
Otherwise, for a payload_multipart like ["payload_multipart", "<hash>", "foo", "bar"]
an attacker could move data from the end of field "foo" to the beginning of field "bar" without invalidating the hash. I'm really struggling to think of a way that could be exploited, but I think it makes sense just to close that hole now, otherwise it will remain a (potentially) significant security footgun for protocol developers that want to use this.
Co-author of NIP-98 here. Please don’t change it as proposed; NIP-98 is fine as is, already in use, and needs to be stable.
NIP-98 was designed to be simple and efficient with minimal text. For edge cases, you can improvise (this is nostr). Introducing this change would double the text and goes against the simplicity we aimed for. NIPs should offer flexibility, not strict rules. You can adapt it to your use case like with Blossom, but please avoid these changes.
The MUSTs in the NIPs can be taken with a pinch of salt; they are really just strong SHOULDs.
NIP-96 is the one I believe has more potential for evolution.
I respect your concern about introducing complexity to NIP-98, but what applications would be adversely affected by this PR? It is strictly additive and optional, it does not invalidate any existing compliant implementations of NIP-98 (stability is unaffected). Consider that (if NIP numbers weren't scarce) this PR could simply add a new NIP instead of adding to NIP-98.
No matter what, we need to either change NIP-98, or NIP-96, or both so if NIP-96 is the most widespread usage of NIP-98, we are in a very tough spot for keeping "stability" for existing implementations.
Here are two alternative approaches though:
payload
tag may have application specific meaning, and then have a list of ways to calculate payload hash, or direct implementers to their relevant NIPs.applications may define additional data commitments in place of the "payload" tag, or in addition to it
and then amend NIP-96 to use payload_file
or something like that instead of payload
.Note that whether we leave NIP-98 as-is, or amend NIP-98 according to either of my 2 new suggestions, this means checking the payload is not generic nor interoperable across applications and can't be shared between use cases. Part of my motivation for this PR was to ensure NIP-98 implementations were interoperable and generic. At least with suggestion 2, NIP-98 implementers can differentiate the payload type from the tag name, so if we reject the payload_multipart
concept, I suggest we amend NIP-98 with suggestion 2 approximately as written here
TL;DR
Approach | Does not invalidate existing NIP-96 implementations | Does not invalidate existing NIP-98 implementations | Does not require changing NIP-98 | Generic, Reusable Implementation | Simple |
---|---|---|---|---|---|
This PR | ❌ | ✅ | ❌ | ✅ | ❌ (it's really not very complicated) |
Alternative 1 | ✅ | ✅ | ❌ | ❌ | ✅ |
Alternative 2 | ❌ | ✅ | ✅ (But a small addition explaining this should be added to NIP-98) | ❌ | ✅ |
@Ademan
Consider that (if NIP numbers weren't scarce) this PR could simply add a new NIP instead of adding to NIP-98.
Sorry for a probably stupid question, but why/how are proposal numbers scarce?
Thanks.
No, please leave NIP-98 as it is. It wasn’t designed to accommodate other NIPs that came later with different design trade-offs. Change NIP-96, but not NIP-98. NIP-98 was meant to be a stable system and should remain unchanged.
NIP-98 is effective, scalable, and already in use. It was designed to be stable, self-consistent, and not need further changes. NIP-96 was a different design with a different group from the start. Nostr NIPs are not a full web standard or a straightjacket; most of the time, the sha256 requirement can be taken as a strong SHOULD.
Please let NIP-98 be NIP-98 and do not merge it with NIP-96, which has a different use case and slightly incompatible design. I didn’t oppose NIP-96 because it addresses a need and offers an alternative approach. Let’s keep the strengths of each NIP intact.
For context, I’ve worked on the Solid Project with Tim Berners-Lee and chaired the Read-Write Web Community Group at the W3C for over a decade. This background informs my perspective on the importance of maintaining NIP-98’s stability. A decade of knowledge and standardization through trial and error can’t be fully captured in a GitHub issue. I know it works and will scale to a large audience.
I put a lot of effort into explaining my position, the problem, and laid out alternatives to try to address your concerns, please at least address what I said.
I appreciate the effort you put into explaining your position and laying out alternatives. However, I still believe we should keep NIP-98 as it is.
Simplicity is key to adoption. Adding complexity, even if it's strictly additive, can lead to a situation where NIP-98 becomes harder to understand and implement. We pride ourselves as "simplicity maximalists" on nostr :) Added implementation complexity can hinder its adoption and use, requiring more effort from those who are coding something from scratch. We should strive to maintain a clear and simple standard (or "possibility") that developers can easily follow.
Edge cases can always be handled through implementation notes outside the NIP, allowing for flexibility without altering the core standard. This approach keeps NIP-98 stable, scalable, and easy to implement. In reality, we don't always check sha256 on the web. The sha256 was put there for things like Banking APIs. But it's included in the "possibility" now, and adding multi-part just complicates things significantly.
Let’s keep NIP-98 focused on its original design goals and handle any specific needs through complementary documentation or alternative approaches. This will help us maintain the clarity and simplicity that drive adoption and ensure the longevity, simplicity and adoption of the NIP.
I hope we can agree to preserve NIP-98’s integrity and continue to find ways to address edge cases without complicating the core NIP.
There are three concrete proposals now, two of which I made specifically to address your concerns. Do you have any specific feedback to any of them?
@Ademan
Consider that (if NIP numbers weren't scarce) this PR could simply add a new NIP instead of adding to NIP-98.
Sorry for a probably stupid question, but why/how are proposal numbers scarce?
Thanks.
There are intended to be only 99 NIPs. It probably will change if it becomes too much of a problem, but for now numbers 1-99 are all that's available.
There are three concrete proposals now, two of which I made specifically to address your concerns. Do you have any specific feedback to any of them?
NACK to payload_multipart tag or changing NIP-98.
Adding some text to NIP-96 to guide implementers I have no objection to, and seems like the best path.
There are intended to be only 99 NIPs. It probably will change if it becomes too much of a problem, but for now numbers 1-99 are all that's available.
I didnt realize this. Why not have a NIP that goes over 100?
@Ademan
Consider that (if NIP numbers weren't scarce) this PR could simply add a new NIP instead of adding to NIP-98.
Sorry for a probably stupid question, but why/how are proposal numbers scarce? Thanks.
There are intended to be only 99 NIPs. It probably will change if it becomes too much of a problem, but for now numbers 1-99 are all that's available.
... who exactly decided that/why? im mean governance shouldn't have artificial scarcity :rofl:
NACK to payload_multipart tag or changing NIP-98.
ok my concern here is, as far as i can tell NIP-96 is the biggest user of NIP-98 by multiple orders of magnitude in terms of both implementations and active users, so staunchly refusing to modify NIP-98 at the expense of changing every NIP-96 implementation is not actually the "stable" choice.
Adding some text to NIP-96 to guide implementers I have no objection to, and seems like the best path.
It sounds like you're referring to Alternative 2, it's my second favorite option, but it requires changing the way NIP-96 works. NIP-98 should also add some clarifying language like i proposed. I'm open to revisions of it of course.
NIP-98 should also add some clarifying language like i proposed ,,, NIP-98 should also add some clarifying language like i proposed
The NACK above is to payload_multipart tag or changing NIP-98 (at all).
Adding some text to NIP-96 to guide implementers I have no objection to, and seems like the best path.
I honestly have no opinion on NIP-96. I dont want it to look like I have a view there. You'll have to talk to NIP-96. Honestly I think this would be better as two separate PRs.
who exactly decided that/why? im mean governance shouldn't have artificial scarcity
We should look at this closely because there's almost 100 NIPs. Is the NIP scarcity one of your motivations? It might be possible to lift some of the scarcity constraint, in that case.
Do you have a pointer to where it says there can only be 100 NIPs?
NIP-98 should also add some clarifying language like i proposed ,,, NIP-98 should also add some clarifying language like i proposed
The NACK above is to payload_multipart tag or changing NIP-98 (at all).
this goes beyond stability then, what is your motivation here? I can't think of any valid reason to refuse any change to the text at all (not the meaning!).
Adding some text to NIP-96 to guide implementers I have no objection to, and seems like the best path.
this option means changing every NIP-96 implementation, which is again, as far as I'm aware, the vast majority of NIP-98 usage. it's more than just guidance. I'm not staunchly against this but i want to make sure we do it with good cause. Like i said it's my second favorite alternative.
I honestly have no opinion on NIP-96. I dont want it to look like I have a view there. You'll have to talk to NIP-96. Honestly I think this would be better as two separate PRs.
This entire issue stems from a contradiction between the two, they need to be discussed simultaneously.
who exactly decided that/why? im mean governance shouldn't have artificial scarcity
fiatjaf as far as I know. I'm speculating, but I think the idea was to force people to come to consensus instead of just creating a new NIP so that there's one or very few standardized ways to do something (harms interoperability which is the purpose of this repo).
We should look at this closely because there's almost 100 NIPs. Is the NIP scarcity one of your motivations? It might be possible to lift some of the scarcity constraint, in that case.
If NIPs were less scarce I could propose payload_multipart
as a separate NIP and use it in NIP-96, but I would still want to reference the new NIP in NIP-98 "here is one standardized extension in NIP-XYZ which is used in NIP-96". I do want to reiterate that since this PR's proposed change to NIP-98 is optional and additive, putting it in a separate NIP is functionally equivalent in its significance to implementers.
Do you have a pointer to where it says there an only be 100 NIPs?
I don't. I'm taking hodlbod's word for it (I can point you to where hodlbod said it if you want), but i vaguely remember fiatjaf saying the same on a podcast (bitcoin.review?)
https://github.com/nostr-protocol/nips/pull/377#issuecomment-1519086914
If this is still fiatjaf's position, it is probably more logistical than anything.
I just think it should be a NIP under the number 100 (yes, I am hoping we will have just 100 NIPs and then we can move to some decentralized NIP registry with no inherent sequence).
The bolded part implies it's not about creating scarcity at all, so my speculation was off ;-)
EDIT: bah, github recorded a reference between the two PRs for that...
If this is still fiatjaf's position, it is probably more logistical than anything.
I just think it should be a NIP under the number 100 (yes, I am hoping we will have just 100 NIPs and then we can move to some decentralized NIP registry with no inherent sequence).
The bolded part implies it's not about creating scarcity at all, so my speculation was off ;-)
EDIT: bah, github recorded a reference between the two PRs for that...
Well im not sure I really agree that even making sense. That feels like bureaucracy to do things that don't make sense because of an arbitrary limitation.. even if it was made with good intentions.
Based on that I would just do what makes the most sense and split things up into a new NIP if that's needed.
I don't. I'm taking hodlbod's word for it (I can point you to where hodlbod said it if you want)
Yes please do link to hodlbod. I'm not saying 100 NIPs finite limit, is wrong by the way. Just want to find out more.
We must be getting close to 100 soon.
The root problem here seems to be that NIP 98 is incompatible with multipart/form-data
. NIP 96 also doesn't specify how to create a payload tag when multiple files are being uploaded. So why not just overload payload
in NIP 98 so that "when using multipart/form-data, payload
should be a hex-encoded sha256 hash of all field contents joined by a colon"? (colon, to fix the attack @Ademan mentioned, no field names because order is defined by the fields already). That wouldn't break any NIP 98 implementations, and since no NIP 96 authorization implementation actually works currently, it wouldn't break NIP 96 either. Or am I missing something?
The NIPs discussion seems irrelevant to this issue, but yes, fiatjaf wants to not have NIP numbers >99 right now.
The NIPs discussion seems irrelevant to this issue, but yes, fiatjaf wants to not have NIP numbers >99 right now.
No. This seems to affect every NIP including this one, as was specifically mentioned above.
What is your position on this @staab do you want to limit the NIPs to only 100. Again, not saying it's, wrong but it's important to know. See for example the way blossom solved this problem, without violating the NIP constraints.
@Ademan for thoroughness, perhaps we should discuss this with the web folks to see if it's a show stopper at the W3C Nostr Community Group. My main issue is that there is a large % increase in text and complexity added to NIP-98 that may not be needed. This might be a good time to standardize NIP-98 at the web level as a W3C Note, thereby giving some stability which is my main motivation.
I have reread NIP-98 and it does actually say the SHA-256 is only a SHOULD. Does this not solve the problem? Why do you feel the need to send extra tags for multi-part payloads, when the spec does not mandate it?
If we were to make an optional part to guide developers, this could be done in a Primer document, too, rather than bloating the protocol.
One thing I think is that n-adic multi-length tags tend not to play well with the wider web, so multiple tags with "payload" or similar I think is better.
Firstly, can you state that this is causing a problem for multiple implementations right now, or do you think it's a "nice to have".
I have raised this with the wider web community, including asking about multi-part uploads, and aligning with blossom. Feel free to chime in!
https://lists.w3.org/Archives/Public/public-nostr/2024Aug/0000.html
I have raised this with the wider web community, including asking about multi-part uploads, and aligning with blossom. Feel free to chime in!
https://lists.w3.org/Archives/Public/public-nostr/2024Aug/0000.html
There are currently adjacent implementations like Blossom [2] and my own
proof-of-concept called NosDAV [3]. An issue with aligning Blossom with
NIP-98 is that the website on an HTTP request is mandatory.
If this is referring to multi-part uploads, I don't see why TUS should not be used.
Sorry everyone, in a good week I don't have much time for FOSS during the work week. Last week was not a good week...
@Ademan for thoroughness, perhaps we should discuss this with the web folks to see if it's a show stopper at the W3C Nostr Community Group. My main issue is that there is a large % increase in text and complexity added to NIP-98 that may not be needed. This might be a good time to standardize NIP-98 at the web level as a W3C Note, thereby giving some stability which is my main motivation.
Thanks for starting that.
I have reread NIP-98 and it does actually say the SHA-256 is only a SHOULD. Does this not solve the problem?
I am on the fence on this, I think the most literal reading means yes, it's technically not a direct contradiction like I thought originally.
Why do you feel the need to send extra tags for multi-part payloads, when the spec does not mandate it?
I'm not sure what you mean by this, including the hash of the file in the NIP-98 auth is desirable security, overloading the meaning of payload
is undesirable for implementers (and aesthetically displeasing fwiw).
If we were to make an optional part to guide developers, this could be done in a Primer document, too, rather than bloating the protocol.
I'm not sure what you're referring to here exactly, but a primer doc or implementation guide might be a good place to document things like this.
One thing I think is that n-adic multi-length tags tend not to play well with the wider web, so multiple tags with "payload" or similar I think is better.
payload_multipart
is heavily inspired by the HTTP Signatures so at least IETF considers the n-adic approach acceptable, and there's precedent in NIP-92 for variable length tags.
Firstly, can you state that this is causing a problem for multiple implementations right now, or do you think it's a "nice to have".
Multiple implementations have incorrect implementations of NIP-96 due to the language of the spec(s), so whether or not the specs themselves are directly contradictory, the language is problematic.
I'm not aware of a single correctly implemented NIP-96 upload client that includes the payload
(but I don't claim to have exhaustive knowledge of this).
I'm now inclined to take the lowest possible impact approach here: amend NIP-96 to specify that payload
should be hex, and that it is not the same as it's described in NIP-98.
But, on the other hand if every implementation is currently wrong (I'm not aware of any correct implementations) maybe changing NIP-96 to use payload_multipart
might be worthwhile (or just payload_file
). fiatjaf expressed some willingness to go to 256 nips so maybe I could put payload_multipart
in NIP-198 and see if there's any interest.
I think before I do anything more on this though, I'll try to enumerate NIP-96 implementations (client and server) and figure out the state of their auth implementations.
I have put together this spec as a starting point, that I think can be used by a wider audience. I have written two emails to the mailing list, and have left the door open to multi-part payloads.
Apologies for letting this lay dormant, but here's my (inexhaustive) survey of NIP-96 implementations. As far as I can tell the only implementation that properly¹ implements the NIP is Amethyst. I welcome corrections or additions to this list, but it seems that modifying NIP-96 to specify the "payload_file" (or similar) tag would have zero affect on existing implementations. It would create a small change for amethyst, but since no servers are validating it, nor requiring the "payload" tag afaict, amethyst could leave it unchanged indefinitely.
I'm still unsure what I think the right way forward is, but as best I can tell from the state of implementations, pretty much anything is an option.
Name | Repo | Relevant Code | NIP-96? | Delegate Library | Hashes File Only? | Hex or base64 | Notes |
---|---|---|---|---|---|---|---|
damus | Link | Link | Yes | None | No Payload Hash | N/A | |
plebstr | None? | ||||||
amethyst | Link | Link | Yes | None | No | Hex | |
primal (Web App) | Link | Link | No | ||||
primal (Android) | Link | No? | |||||
yakihonne (Web App) | Link | Link | No | ||||
snort | Link | Link | Yes | nostr-dev-kit? | No Payload Hash | ||
iris | Link | Link | No | ||||
coracle | Link | Link | Yes | No | No | Hex | Hashes "[object FormData]" instead of the file contents. |
nostr-tools | Link | Link | Yes | N/A | No | Hex | nostr-tools makes you provide your own auth token so technically you can implement it correctly, however hashPayload() will not do what the caller wants for a File, Blob, or typed array. |
Name | Repo | Relevant Code | NIP-96? | Delegate Library | Hashes File Only? | Hex or base64 | Notes |
---|---|---|---|---|---|---|---|
void-cat-rs | Link | Link | Yes | No | No Payload Hash Checked | N/A | |
nostrcheck | Link | Link | Yes | No | No Payload Hash Checked | N/A | |
nostr.build | Link | Link | Yes | No | No Payload Hash Checked | N/A | Not 100% certain my analysis of this one is correct |
I see that https://github.com/nostr-protocol/nips/pull/1415 corrects the language, which would make Amethyst strictly correct.
I maintain my concern about overloading the "payload" tag, but it's fairly minor, all things considered. Maybe I'll close this and open a separate PR just for "payload_multipart" in its own NIP for further discussion, but as it relates to NIP-96, #1415 probably resolves the issue well enough.
I just deleted #1415. Hope you can reach consensus among nip96 service maintainers and fix this.
It's always better to separate fixes from new additions in PRs.
@arthurfranca I think that #1415 was probably good enough, I hope you didn't delete it because of me, that wasn't my intent.
If I was certain I really had captured all NIP-96 implementations, I'd feel more emboldened to change the NIP-96 language but as it stands I'm inclined to be more conservative, I think ( #1415 was the conservative option imho)
At least for the moment, fixing NIP-96 to say "payload" is hex encoded is a clear improvement, and I can't imagine any objection to it. There are no implementations that encode the payload hash in base64.
The reason I closed it was that people behind nip96 servers can't collectively agree on small fixes. It won't be me trying to improve things anymore.
quentin said "No problem for me" for the u
tag small addition on the PR but at another comment he said that blossom (competing rogue standard) auth is better and u
tag is useless. And fishcake wanted another approach.
Also v0l didn't comment on any aspect of the PR.
The payload part was meant for the servers to have a proof of who uploaded what and to make them able to cancel the upload if they already have the file but the fact is no server cares about it.
Also the servers only read the form fields after the whole file upload and they think it is ok, meaning that fields like "no_transform" are probably ignored?
Reconcile conflicting language in NIP-96 and NIP-98 identified in #1376 by introducing a new, optional NIP-98 tag
payload_multipart
which allows simple and flexible selective authentication ofmultipart/form-data
requests.Rationale
NIP-96 specifies that uploaders should include in the
Authorization
header, thepayload
field in a format incompatible with NIP-98 (base64) calculated from a different preimage, the uploaded file. This is opposed to NIP-98 which specifiespayload
hash should be encoded in hex, and should be calculated over the entire request body.There are significant implementation difficulties and drawbacks to using NIP-98 as-is to authenticate NIP-96 uploads (see below). I suspect this is why the two NIPs are contradictory in the first place.
This change attempts to provide a usefully generic mechanism to NIP-98 that can be used unmodified for NIP-96 uploads.
Why...?
Why must the order of fields be consistent between the
payload_multipart
tag and the request body?To limit denial of service potential, keeping the order of fields in the
payload_multipart
consistent with the order of the fields in the request body, together with the other rules allows server implementations to process the request body in a single pass. Furthermore, a consistent and unambiguous ordering is necessary, and this (I believe) satisfies that, and also permits efficient implementation.Why not...?
Why not leave it as-is?
They instead (attempt) to provide a hex encoded sha256 hash of the uploaded file.
payload
tag overloaded meaning is undesirable, especially if the encoding (base64 vs sha256) is meant to disambiguate two conflicting meanings of the tag.The minimum change would be to modify NIP-96 to use a different tag such as
nip96_payload
, and probably to use a hex encoding instead of base64 for consistency.Why not implement the minimum change you just described?
It's not generic, it remains a special case in NIP-98 handling. Since NIP-96 is so ubiquitous and important, this could be ok, but we can provide a generic mechanism without much effort.
Why not strike the conflicting language from NIP-96 and implement NIP-98 as-is?
See below, the best case scenario creates significant implementation burden, and will require loading entire files into memory. This could be acceptable, maybe, given that NIP-96 uploads tend to be on the order of a few megabytes to tens of megabytes. However, it is preferable to avoid this limitation altogether, in my opinion.
Open Questions
Should we allow signing field metadata?
multipart/form-data
request fields include metadata such ascontent-type
,content-disposition
, and a few others.content-type
is redundant to thecontent_type
form field in NIP-96 but could be significant in other protocols.content-disposition
includes the desiredfilename
of the upload, and its manipulation could be exploited (how? encoding embarassing or incriminating strings in the filename?).These could be accomodated by a semi-special case, using a dotted notation.
The filename of the field
file
could be represented, for instance, by"file.content-disposition.filename"
. This adds minimal implementation complexity but increases functionality. I'm inclined (and have chosen to) omit this functionality from this proposal, but I want to propose it as potentially desirable.Should we allow signing HTTP headers?
Like form field metadata, HTTP headers may have significant meaning for a request, and ought to be authenticated in some cases. These could be included with a separate tag such as
http_headers
which works similarly to this proposal.Are there any good ideas worth stealing from HTTP Signatures which are relevant to NIP-98?
Did I miss something that would enable implementing NIP-98 as-is on the browser (without the drawback mentioned below) ?
Implementation difficulty of NIP-98 as-is for NIP-96
The browser is an extremely important target for nostr implementations, so NIPs should be implementable on them without major caveats.
The browser provides two mechanisms for making arbitrary HTTP requests:
XMLHttpRequest
and more recentlyfetch()
. When making aContent-Type: multipart/form-data
request, both of these will serialize aFormData
object for the caller. This involves generating aboundary
in an implementation specific manner which is not available to the caller. Thisboundary
changes the request body, and thus changes the sha256 value provided in the NIP-98payload
tag. This makes it impossible(?) for an implementor to rely on the browser's serialization ofFormData
objects intomultipart/form-data
payloads and calculate the correct payload hash.A javascript library could with some effort, perform this serialization itself, and use the serialized request body to both calculate the payload hash and to submit the request. The problem with this, is that particularly for NIP-96, the serialized request body may be very large, and take up unacceptable amounts of memory.
The sha256 payload hash may be calculated in constant memory, but the entire serialized request body must be provided to
XMLHttpRequest
orfetch()
as a string, anArrayBuffer
(or similar), or aBlob
(or similar).This presents drastically different limitations to users compared to the
FormData
approach. In theFormData
approach, the browser may be more clever, and streamFile
s from persistent storage rather than loading them into memory in their entirety.