Reference · Files and processes
save_upload
save_upload(map, str) -> str | error(str)What it does
Writes a file the request uploaded to a path under the program's folder, making
folders as needed, and gives the path back. The first argument is a file record from
request.files ({ field, filename, type, size }, from a form with
enctype="multipart/form-data"), or the file a component passed to a server fn. The
bytes stay with the request until saved, so call it in the route or server fn that
received them.
Examples
A page with a form, and the route it posts to. The form needs method="post" and
enctype="multipart/form-data" (without it the browser sends only the file's name), and
the file input's name is its key in request.files:
route GET /avatar = <form method="post" action="/avatar" enctype="multipart/form-data"> <label>Your photo <input type="file" name="photo" accept="image/*" required/></label> <button>Upload</button> </form>route POST /avatar { let f = request.files.photo if f == null { reply 400 "Choose a photo" } // `required` can be skipped if f.size > 2_000_000 { reply 413 "Photos up to 2 MB" } let path = save_upload(f, `uploads/${uuid()}.png`)? // into the program's folder redirect(`/${path}`)}route GET /uploads/:name = file(`uploads/${name}`)Several files from one input with multiple, and a text field beside them:
route GET /docs = <form method="post" action="/docs" enctype="multipart/form-data"> <input type="text" name="title"/> <input type="file" name="docs" accept=".pdf" multiple/> <button>Send</button> </form>route POST /docs { files: { docs: [map] } } { for d in docs { save_upload(d, `uploads/${uuid()}.pdf`)? } `${request.form.title}: ${len(docs)} saved`}From a component, a file goes to a server fn as it is chosen, with no form:
server fn add_photo(file: map) = save_upload(file, `photos/${uuid()}.jpg`)component Photo() { state saved = null <input type="file" accept="image/*" @change={(e) => { saved = add_photo(e.target.files[0]) }}/>}Edge cases
- The path is yours to choose:
filenameis what the browser sent, stripped of folders, and is not safe as a path. Name the file withuuid()and keep the original name in the database if it matters. Clients other than browsers are read too: a\"in a quoted name (Go, curl) is a", andfilename*=UTF-8''...gives the name when no plainfilenameis there. - A form of more than 1,000 parts, fields and files together, is answered
413before the route runs. - Outside a request, or in a request that uploaded nothing, the result is an error
value:
save_upload: the current request has no files. So is a record that is not one of this request's files. - The record carries
startandend, the file's place in the body; a map without them is not a file (expects a file from request.files). - The bytes are written as they are, so an image stays an image;
read_fileof the saved path reads them as text. - It is the
fseffect, aswrite_fileis: a function declaredwithwithoutfscannot call it, inquillo checkand when it runs. - Links are followed: a path that leads out of the program's folder through a link, or through a link that leads nowhere, is refused, and nothing is written. A link that stays inside the folder works.
- The program's own files are refused with an error value, as
write_filerefuses them: its.qweand Rust files,.envand.env.*, the--env-fileand--tls-key, the databases it has open and a tenant's database not made yet. Names are compared in any case and as Windows reads them, so an upload saved as.ENVorMain.QWEis refused too.
let photo = { field: "photo", filename: "me.png", type: "image/png", size: 3 }print(save_upload(photo, "uploads/a.png"))print(save_upload(photo, "uploads/a.png") | is_err)print(read_file("uploads/a.png") ?? "nothing was written")err("save_upload: the current request has no files")truenothing was writtenErrors
Having no file to save is an error value, which ? turns into the request's answer. A
path that leaves the program's folder (.., or starting at /), or the empty path, is
a runtime error, checked before anything else; so is a path that is not a string.
print(save_upload({}, "../a.png"))line 1: `save_upload` needs a path inside the program's folder, got "../a.png"fn keep(u) with time = save_upload(u, "uploads/a.txt")print(keep({ field: "f", filename: "a.txt", type: "text/plain", size: 1 }))line 1: `save_upload` needs the fs effect, but `keep` is declared `with time` in keep, called at main.qwe:2Chaining
The file comes first in a pipeline, the path in the call:
route POST /docs { let paths = request.files.docs | map((f) => f | save_upload(`docs/${uuid()}.pdf`)) paths}See also
read_upload, write_file, image.info, image.convert, uuid
Thread 0 posts
On save_upload(): questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.
Edit this page on GitHub · every example's output was made by running it (tools/refcheck.mjs) · questions on Discord or r/quillo