Re: Issues with HTTP multipart/form-data file upload

Xavier Del Campo Romero <[email protected]> Wed, 28 Aug 2024 01:04:04 +0200
Newsgroups gmane.comp.web.dillo.devel
Message-ID <[email protected]>
Hi Rodrigo,

> Glad to read that you also consider Dillo for slcl, and thanks for preparing the patches :-) 

Thank you! I want slcl to be useful to anyone, including users who care
about minimalist software like Dillo. The web is already too crowded
with bloated "webapps" and other terrible things. :)

> Sounds good, not sure how complicated it would be to do this. 

I still need to investigate this further, but I assume this would
require Dillo to at least implement a sink callback.

In other words, the component responsible for transmitting the data
(probably src/IO/IO.c) should trigger a user-defined callback with an
arbitrarily-sized buffer (typically, of BUFSIZ bytes, as defined by
stdio.h) that must filled with file data. Then, the user-defined
callback can fill from zero up to BUFSIZ bytes, which are eventually
trasmitted to the server.

That said, I am still not sure how much actual effort this would take.
But I am glad to receive positive feedback so far - I will then continue
to find a solution.

> However, being able to upload multiple files at the same time sounds reasonable, so feel free to try on your own in the meanwhile. 

Uploading multiple files at once seems doable - the patches I sent on my
previous email are probably already doing most of the required work.
Again, the trickiest task is to send data on-the-fly for each selected file.

> Shouldn't it be 68 then? 

I understand the opposite: the boundary string with the two leading
dashes ("--") included can be up to 72 bytes long, and 74 bytes long for
the ending boundary (which includes two more dashes after the boundary
string). This is confirmed by reading the BNF defined by RFC 2046 (some
bits omitted for simplicity), section 5.1.1 [1]:

> boundary := 0*69<bchars> bcharsnospace
> bchars := bcharsnospace / " "
> bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
>                       "+" / "_" / "," / "-" / "." /
>                       "/" / ":" / "=" / "?"
> dash-boundary := "--" boundary
>                       ; boundary taken from the value of
>                       ; boundary parameter of the
>                       ; Content-Type field> multipart-body := [preamble CRLF]
>                        dash-boundary transport-padding CRLF
>                        body-part *encapsulation
>                        close-delimiter transport-padding
>                        [CRLF epilogue]
> delimiter := CRLF dash-boundary
> close-delimiter := delimiter "--"
Note: even if the specification tells receivers to handle transport
padding, for the time being I am assuming "transport-padding" as zero
length since composers must not generate non-zero length transport
padding. I am still not sure where transport padding would apply,
anyway. Probably outside web browsers?

> I would leave out all the symbols to avoid quoting and only use A-Z a-z and 0-9. 

Interestingly, Dillo would always quote boundary strings [2], even if
only using A-Z, a-z and 0-9. In fact, this is one of the wrong
assumptions I spotted when testing slcl against Dillo.

> Which, if I computed it correctly, is still too small to worry about. 

Not only it is too small of a chance: if we really wanted to do "the
right thing" and make Dillo absolutely sure the boundary string is not
contained within the selected files, this would imply a noticeable
performance impact when dealing with large files, much likely for a
near-zero benefit.

I have not inspected their source code yet (and I do not want to), but I
understand both Gecko and Chromium are also making that assumption,
because otherwise it would take them a lot of CPU time to upload large
files.

> Why sizeof " " instead of just 2?

Because, to my eyes, sizeof " " has more meaningful semantics, compared
to a magic integer constant such as 2. However, for this simple
scenario, I would still consider both acceptable.

I can replace it with 2 if you find the other construct unacceptable.

> PS: When are you playing? 

Sorry, I did not understand your last sentence. Could you please give a
bit more context? :)

Best regards,

Xavi

[1]: https://www.rfc-editor.org/rfc/rfc2046.html#section-5.1.1
[2]:
https://github.com/dillo-browser/dillo/blob/8a360e32ac3136494a494379a6dbbacef6f95da2/src/IO/http.c#L362

On 27/8/24 22:59, Rodrigo Arias wrote:
> Hi Xavier,
> 
> On Mon, Aug 26, 2024 at 01:27:29AM +0200, Xavier Del Campo Romero wrote:
>> Hello Dillo dev community,
>>
>> I am testing support among web browsers for slcl [1], a JS-less
>> minimalist web storage solution. slcl relies on
>> "multipart/form-data"-encoded HTTP requests to upload files to a server.
>> Whereas Dillo helped me to uncover a few wrong assumptions on my code, I
>> have realised a few issues on Dillo itself related to filue uploads that
>> should be considered.
> 
> Glad to read that you also consider Dillo for slcl, and thanks for
> preparing the patches :-)
> 
>> Issue #1:
>>
>> While Dillo is fine uploading small files (up to a few MiB), things go
>> wrong with larger files, so much that memory usage increases up to
>> multiple GiB and can even lock the system up. This is because Dillo is
>> designed to send requests always from memory, which means file contents
>> must be dumped into memory first, and this might be unfeasible for large
>> files.
>>
>> Suggestion:
>>
>> Instead, Dillo should ideally send file contents on-the-fly, so that
>> memory usage is kept to a minimum regardless the file size.
> 
> Sounds good, not sure how complicated it would be to do this.
> 
>> Issue #2:
>>
>> Even if Dillo generates a 70-byte, random boundary string (yet mostly
>> filled with '-', similarly to Firefox [2]), it ensures it is not found
>> anywhere inside the file contents. Again, this can be a serious
>> bottleneck in the case of large files, as it requires to scan the whole
>> file for a match.
>>
>> Suggestion:
>>
>> Define *all* of the 70 bytes in the boundary string as random, and
>> assume they would never be found inside a file. The chance of accidental
>> collision is so low that it is not worth the effort into checking them.
>>
>> Suggested patches:
>>
>> - 0001-dialog.cc-Generate-more-random-boundaries.patch)
> 
> Yes, I think is a good idea.
> 
> @@ -1246,6 +1246,24 @@ Dstr *DilloHtmlForm::buildQueryData(DilloHtmlInput
> *active_submit)
>     return DataStr;
>  }
> 
> +static void generate_boundary(Dstr *boundary)
> +{
> +   for (int i = 0; i < 70; i++) {
> 
> I think this is too long:
> 
>> Boundary delimiters must not appear within the encapsulated material,
>> and must be no longer than 70 characters, not counting the two
>> leading hyphens.
> 
> Shouldn't it be 68 then?
> 
> +      /* Extracted from RFC 2046, section 5.1.1. */
> +      static const char set[] = "abcdefghijklmnopqrstuvwxyz"
> +         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
> +         "0123456789"
> +         "'()+_,-./:=? ";
> 
> I would leave out all the symbols to avoid quoting and only use A-Z a-z
> and 0-9.
> 
> Assuming you are left with 62 symbols (26*2 + 10), the probability of
> finding a random 70 character string in a random sample of 70 characters
> is (using ** as ^):
> 
>   p = 1/62**70 = 3.4086e-126
> 
> But as you will be dealing with large files, each line of the input
> could match it. We are interested in the probability that a given
> boundary will mach any of the N lines. So we compute the probability
> that it will match none of the lines (1-p)**N and then the opposite of
> that (matches at least one line):
> 
>   1 - (1 - p) ** N
> 
> For example, a 1 TB encoded file, with 2**40/70 lines will have:
> 
>   p = 1 - (1 - 1/62**70) ** (2**40 / 70)
> 
> Of occurring, which is around 5.354e-116.
> 
> Which, if I computed it correctly, is still too small to worry about.
> 
> +      char s[sizeof " "] = {0};
> 
> Why sizeof " " instead of just 2?
> 
> +
> +      do {
> +         *s = rand();
> +      } while (!strspn(s, set));
> +
> +      dStr_append(boundary, s);
> +   }
> +}
> +
>  /**
>   * Generate a boundary string for use in separating the parts of a
>   * multipart/form-data submission.
> 
>> Issue #3:
>>
>> Dillo only supports uploading 1 file at a time. This is mostly because
>> it relies (probably temporarily?) on the a_Dialog_save_file function
>> [3]. However, this is not a limitation on the HTTP protocol, and other
>> implementation such as Firefox or Chromium-based browsers support this.
>>
>> Suggested patches:
>>
>> - 0002-dialog-Add-a_Dialog_select_files.patch
>> - 0003-WIP-multi-file-uploads.patch
>>
>> Conclusions:
>>
>> Dillo seems designed to always send requests from memory, so it is not
>> straightforward to break this assumption in order to support large file
>> uploads. 0003-WIP-multi-file-uploads.patch is an incomplete first step
>> into fixing this, but it surely needs deeper design changes.
>>
>> I did not put more effort into these patches for the time being because,
>> after seeing the potential complexity behind this task, I thought it was
>> a better idea to ask the community for feedback and guidelines.
> 
> I'm not very familiar with this part of Dillo, so I'll have to check a
> bit more before providing more feedback.
> 
> However, being able to upload multiple files at the same time sounds
> reasonable, so feel free to try on your own in the meanwhile.
> 
> PS: When are you playing?
> 
> Best,
> Rodrigo.
> _______________________________________________
> Dillo-dev mailing list -- dillo-dev-lx9mn2B4QYRWk0Htik3J/[email protected]
> To unsubscribe send an email to dillo-dev-leave-lx9mn2B4QYRWk0Htik3J/[email protected]

_______________________________________________
Dillo-dev mailing list -- dillo-dev-lx9mn2B4QYRWk0Htik3J/[email protected]
To unsubscribe send an email to dillo-dev-leave-lx9mn2B4QYRWk0Htik3J/[email protected]
OpenPGP_0x84FF3612A9BF43F2.asc (application/pgp-keys, 4.8 KB)
-----BEGIN PGP PUBLIC KEY BLOCK-----

xsFNBGO4Fv4BEAC0epH/5cbl9PPhHvxaxjNiQ4PH9V6vtziaH+Nu/gw3/sFt7Yvo
SGTKfr7+hj/1TsrtBdtQGBCw5Wz1QKy5/DeG61FMUBkgi0Ua1NIxuh3U3lBuNTwy
q0ue2BGq8fO0X+RJV4zTDMzzcDzaPSrUJ12ofWmZNqpZWAFq2BtLPJ6amyDW53LK
ROBgiEcn5stw+DkoRYKu2Ntgr0DZ0ZKr38yB9ILr6QDCpVCFLXoPurZiiM8e4wRW
WSqEusBBV+/dd3CtthZeebVVeY7ri9Hbsk+im4ZXwEGJU3NueVYxWutREODqyhKQ
sld/rVXbmudtIcitQ5uFWrIVhG+Djb8JUMj6CVj+Jc1B22dgh+OO86akj/Blu1To
R3OLBJFp+omsQdyvPBg9kxjTEpUuG8TGgJJSbcoaGdpxTrSyOBEgaKHiJA3dKaNy
iru0sJX31I7DexJ8Pahqon9xAdCVfRUAjnpqTYInhe4whnWmQ2tthsWuu36wh4j4
RzNLZGmOFOx7b56lQyfN2BEE+kIY3UeHynCOUGDZa5bVaV0PWozmgY+KMzTBuUTQ
1HqAfcHmAEkOnIB4mCRznwMHlweaTh6qy9h77t3DNFUj1rj5F5ECBH4zqX8Plnwj
g8ijeilwLJzGhAgKx0kWbiuirpN63dvPbeXmjIhm/irLOIHOSzIeURvaswARAQAB
zRJ4YXZpOTJAZGlzcm9vdC5vcmfCwZQEEwEKAD4WIQQvjAQwk/1hKSTwtvyE/zYS
qb9D8gUCZApscAIbIwUJB4YHsgULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCE
/zYSqb9D8oLnEACBwNTjEieAexFQE0G51P77hBAn3o572OS36aZHfoP/jVSQHGwS
Xk0U8dy4SH8N4oFDxIriJRyeRJrnRKUlZqU5P7ke+tuaNZbhFGMeRw3HCwckUdLr
ziC7CI0WLigPKulbPRwmsnZyX/DaDxaGroz3KwPo1qWCqVhG1V8PORSkHqlG2o7q
Mj6FC7Vnp3UQTpl8SELpy6RrqQJD2FdA5GoZiwop95PCUVLG6HfY7CdT8fluFxCY
3EAN2Ka986lFC6K6y87lq1m+D9+7Iakckml0Na/S8CysjHzAp9wfPEIa9fwHoD3M
RtZZCpYY1paGeMMgJbcaIApNhLo4GFbYv2qAFdusBlevnmPVj8J/Cu8oxMlLTz+G
LJqxYj6HbFYf9kzC0tNKnz+PP7pkT9X9AiT4oHsrTn9Csdnw91qwPRVemgqh3QKz
Dkg6JSdsgf+u+KdqPA1piQyGCE+K9keI7xjQ9dS/k7GGylu7AfSphJ5t89GQ+oXo
KlAmaWzx/3jWSsTb2djdKRvb3ARRt/FzEmmBFUJx7BE6u9ly4CFfwkkCyv9dySGs
+F5/9KlisptYhd2xF6C0VOBSzfWcMwc1RXSswk21kLCxgiGWtsZfq5uIffxLE3wa
cmQHmGNXv0Roatry0bxnlu0SvbhzsZzuHKN5U4la6uuSIiYc+TfouOrI680vWGF2
aWVyIERlbCBDYW1wbyBSb21lcm8gPHhhdmkuZGNyQHR1dGFub3RhLmNvbT7CwZQE
EwEKAD4WIQQvjAQwk/1hKSTwtvyE/zYSqb9D8gUCY7gW/gIbIwUJB4YHsgULCQgH
AgYVCgkICwIEFgIDAQIeAQIXgAAKCRCE/zYSqb9D8sSmD/9tUmuE7LNkpT4ZVuYR
Lc3Cs4t229cOU5sdCS74n+tPbbbVKmoGLQTc8bB1Gt7jQ3lPV5XQ0uuBcWN/ZvPU
inY9R9O9ffmxvx3ch2kj/6UL2394Ys6tifXYUFnPtmN8uraSJ9gfM2OXKo3OTe4u
pxueKHTZqmq/cKgUAicCPjfJynMWg8o7+oE6J3uHUJjQ2SfxvKGbtLj2rBqibFqO
FzmUS7oRA66mXoAUf124AfutCfZ84k+kTG3ytEe+0gRqfTvykk9CxAd9gRyhWAlY
XQVXDePsFsKLPTd9fODoj+zXbJNmqbHPRt/OUXioKRAhCvKICkP+uXM0clsvaVYb
XSfDDW1W7grfXRKfAIf9zG9yrMD4a6gTC8Qu7PNC3zNZlfOGzmneFrPiR0ZmlzEi
HEdpV2xZBWwtdbgQin5yktxQWPBNHZWT4JW79hEUUfZAFdhZDxFEBkZrhq7uvEqL
cKx7bGS6VNg3JHr/Fr+6A76FN3rdH38FVdC5izADNcfBjzQFWp3Rf2chiBokZuWR
8WKV1ENVhj6kv3XdXm8yXtwXmQDc/SEaRd7uBSpkhhholcwAeL7gpYGExp3O77YS
/MYaUx4azWGGjmTqkSex6ZmADXQ8dxGtFxw6Zc7rQ+LngGFlW26qhe7PWhdcn3Me
0IX3qeUfhyPJiGhGDJcvIV7o/M0sWGF2aWVyIERlbCBDYW1wbyBSb21lcm8gPHhh
dmk5MkBkaXNyb290Lm9yZz7CwZQEEwEKAD4WIQQvjAQwk/1hKSTwtvyE/zYSqb9D
8gUCZApwDwIbIwUJB4YHsgULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCE/zYS
qb9D8mQHD/40eIBQwInlhTvPtl3GOi4235ds3QqozIjnOSqU9GkUxvq/1ypI6nbp
OJE5RxNj+/0iv2osKjGen+UCn29LJ5mGg0TDnkMElDCJzDrpf52lS8PeLOHCHLnY
ok2nzPGDJXphzTKpEX02e8FNuh68vR34glxBOBpYlJ3+v2r3/BrKkoWnmIYXFshx
e1MsFJwm6DL+VLprNINs+u//MrappGGoUZz367pFtsNeGfenXFvEI2c9lA0QA3bk
7qk8XsN7FRyp9pgV0TMc5+OCB2bdhPTWFMwq9D5D8yEU0/bMhYsi6koe8bkUB0AM
f8RV/ArtR/CKWy6QuhyALcrrCuskoUqOYa/zqw4IxMmOuXLiPYTHlTqc6nqcnh8m
drgMd6PvtNdFFBHaz5DSDIvP1jdwpo5s1LaLBwz0Uq3wx37ElW5nKXgckkO71UA1
P+hSsnL6CIhoW61feAaDRG0x26lYo7kaHYTa6q7IN9mr8QwD38xNHLrdhpbMuO2b
D0IFa1FD0rsOUikSVHJkkib0iHFV5w+h1/FkEqelzS96edPoWlojZCMsCg7IELwt
dHRb3ePPQl9KLTGCIQkK9pGR4Rmv759Bpi4LK8u6S5/J7n78wM0T2NcwGprBDU6q
kA9lReHn5D7b8cCjT83Qymosi3pG0vihQrth//CsFwJFAqOKOA8whc7BTQRjuBb+
ARAAs6BQ6Qno3MccV1XkxqtzUtQDCd7Lue9Ky47mOpl/F6Hh++uauZcoFxa22UGi
uo2SjjWYsw5+ZsrbAHyFbYFMXx84ey5iFymw6Bts9psTU8ZuqWH2V6HONJGVmIKF
/4uAPZ2KZPT9MgkZ8i5Tr7ZgIivxwig7a50twvl0IZrKo1GVWUu9+Kipf80IUzvf
aG47IEbqwMoysbL0ThwV6M6oN/mcPMZ8KMDG7WYTYMwbN9t5YFvVcso0lvDfJBBE
MTilC2q6WuOMfiDXJHdc/SzbtWC4aktuhQ5vnZr2aCxgkLecuTrmVGXOnqrqOKXW
mWG2aQ3Hs34shOqe0ZESguesXvTecK5gTJSIpPG7SwTsTAGRHAZwnwSiw9DwDwLg
8YJ9C0LuOg0v4JpWgKfjf9pIk14YNATZMN+A2eppk2DjVkl0zANSkgzQngDOKue0
7eM2zIWz4u2cWv0i4YgH5CZTydLEWexAiyZfGYLh4HUcmpBW3kRixLYU4zz86ECQ
y+82CKSg8JTj2wGoaxipK/K1hKIU0saIYSHGrEfthndDOZgQ9QXrWatC40X+UG/K
WFDIYKilSwDi3j2d4/qE64LPt7jGJqPA5vUKBoJfeYuGC1aQW2XALkQBSVfbg/YU
opI50zA0/naNybUeCem39/819mSlL3dpj31gP4G0ok69GCkAEQEAAcLBfAQYAQoA
JhYhBC+MBDCT/WEpJPC2/IT/NhKpv0PyBQJjuBb+AhsMBQkHhgeyAAoJEIT/NhKp
v0Pyu2wP/31o++NBfCHUqMY0sC56xT2lV4+UAzo7VzLTcYUqirdoPym7Rhmzsns6
lBuk9ruEytfIThgd8Y6RdvFzafUphgIhsVEuehHHk3J2aEapmcX8AWy/0GIopt2E
BnVZ8A57ZVloIYwfCwcRtnLK/KaSirGdls48Ww7MoiQOyoQUVjKuFiQ8xz0CTkiz
wWDnAUGALxnGRjTiicU5jEpGhtCp6vMMNH0llXYaFTlzLMKJuWE3NM6YFlZBUXFS
Ji4GZcbY+TABDhfFKVQ28YsOcnidBNhdQ4+DFXH/He12VOvwRnoh81f+i1IZp5np
w8/cL5bkPkVxRNe7bcHxfcrF3XQFibdAgRDaCNwelO/fjn7x9zxbqVgJRfXbDpxM
kSA7mftFGc0PSuD/GLo/HwaYhBJ1p1RfWdVqaMW5YC1fnp7LfgH2Kpr0JXkspQE/
rOAW/TuK4Pu/bXI7dWZrn2HnzupWUdUWZ1FlI8tWNttSQH1v9wsCuEvM3fBBeO1a
ZLAFrh5tvaDV0CtB071weaVwgCyseiYXCKB0VeEMWONuGwYkSjEQ9ALUdGylHJkp
olriBRvCXRJVg5NIjoKEJM8ZY+CBYTVFDmuSPf5thlpBM8n+KhcyJihcqkz4EtZk
tuzwnf4MVB0pC1ZPrNGpnx1d6PTHI30xMxAdvxKbBTMdthrG594P
=Om+c
-----END PGP PUBLIC KEY BLOCK-----
OpenPGP_signature.asc (application/pgp-signature, 840 B)
-----BEGIN PGP SIGNATURE-----

wsF5BAABCAAjFiEEL4wEMJP9YSkk8Lb8hP82Eqm/Q/IFAmbOW2UFAwAAAAAACgkQhP82Eqm/Q/I3
iw//Ze1FRswxVzFkNl2DjQjuXperY1J+s+Eb7JiXte8WqKXJiKCaUgGUsdRImV1ZHoCLBso6IVSK
fKJk3q7RU4vdZXQ7XReriIVbrfVKafTEYTPbkcAm5NpTCB0UvXwClcOFo3BJCFXyjzk6fP8Q1mZD
kxeuFwOMJaFKQpVZo9eDlHMyOzdXR60rP6IfnL5jfz8Yk/b6kujGOtvvVy/9Mj/1jWZK5pOm3FpO
UORZOy8zZH/3imq9iH3iRd3FS6bA1TVNzA/jdhp/q7PjGPhueKyDf0oS02Yk5+Z1W+NRCcTTwghr
9wXlIiYd5YcKmTzXwUbcCZAaSrHoxkGVgIHO/ta43+tUBBGH156gysEqTBdxDAEhCtdBlkiagNHe
BW3jXpJn6l6wc0ofNQnE2/+TwB2AWugMz9VXvYtSJkFjAFUuPelOUmSnAu6cafdniCl1Piv0vz9h
IkgPYwBJmu4ksOMSZkTJmqcSBVHf8WtZp40KwhCsg8PgjM1q/ZfV4iQ/QWo9xQMpIF9s4Zmg8OfB
UGX5C9xehPRoXsdrSfvLBNn02PQLAKOlBORZPbkKP5WInPaqidy754xcK8pfFBbGv4NZy6ay1+0u
5mQ9YgHEMYvHDMc1vjoWwU1IO+Xlsjc78OFzB9Yjfc+VMYeV3jWJy/AyvHmzdvryl2PSymaNDelC
Sa4=
=IG1m
-----END PGP SIGNATURE-----