Files
cpp-httplib/example/upload.cc
Mario Limonciello 76b54e7de4 Sanitize uploaded filenames in upload example to prevent path traversal
The upload example wrote each uploaded file using the filename supplied
verbatim in the multipart Content-Disposition header. A client could set
that filename to an absolute path or one containing "../" components and
cause the server to create or overwrite files outside the working
directory.

Reduce each client-supplied filename to its base name and reject the
request with 400 Bad Request if the result is empty, ".", "..", or
still contains a path separator (including colon for Windows drive
letters).
2026-07-27 10:56:49 -05:00

92 lines
2.4 KiB
C++

//
// upload.cc
//
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// MIT License
//
#include <fstream>
#include <httplib.h>
#include <iostream>
using namespace httplib;
using namespace std;
const char *html = R"(
<form id="formElem">
<input type="file" name="image_file" accept="image/*">
<input type="file" name="text_file" accept="text/*">
<input type="submit">
</form>
<script>
formElem.onsubmit = async (e) => {
e.preventDefault();
let res = await fetch('/post', {
method: 'POST',
body: new FormData(formElem)
});
console.log(await res.text());
};
</script>
)";
int main(void) {
Server svr;
svr.Get("/", [](const Request & /*req*/, Response &res) {
res.set_content(html, "text/html");
});
svr.Post("/post", [](const Request &req, Response &res) {
const auto &image_file = req.form.get_file("image_file");
const auto &text_file = req.form.get_file("text_file");
cout << "image file length: " << image_file.content.length() << endl
<< "image file name: " << image_file.filename << endl
<< "text file length: " << text_file.content.length() << endl
<< "text file name: " << text_file.filename << endl;
// Reduce a client-supplied filename to a safe base name, or return an
// empty string if it cannot be trusted (empty, ".", "..", or contains a
// path separator).
auto sanitize = [](const string &filename) -> string {
auto name = filename.substr(filename.find_last_of("/\\") + 1);
if (name.empty() || name == "." || name == ".." ||
name.find(':') != string::npos) {
return string();
}
return name;
};
const auto image_name = sanitize(image_file.filename);
const auto text_name = sanitize(text_file.filename);
if (image_name.empty() || text_name.empty()) {
res.status = StatusCode::BadRequest_400;
return;
}
{
ofstream ofs(image_name, ios::binary);
if (!ofs) {
res.status = StatusCode::InternalServerError_500;
res.set_content("Failed to write image file", "text/plain");
return;
}
ofs << image_file.content;
}
{
ofstream ofs(text_name);
if (!ofs) {
res.status = StatusCode::InternalServerError_500;
res.set_content("Failed to write text file", "text/plain");
return;
}
ofs << text_file.content;
}
res.set_content("done", "text/plain");
});
svr.listen("localhost", 1234);
}