-
Notifications
You must be signed in to change notification settings - Fork 191
Expand file tree
/
Copy pathfile_upload_with_callback.cpp
More file actions
155 lines (137 loc) · 6.07 KB
/
Copy pathfile_upload_with_callback.cpp
File metadata and controls
155 lines (137 loc) · 6.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*
This file is part of libhttpserver
Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
*/
#include <cstdio>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
#include <httpserver.hpp>
// HTML-escape user-controlled text before placing it in a response body
// to prevent reflected XSS (CWE-79). Never echo unfiltered client input.
static std::string html_escape(std::string_view in) {
std::string out;
out.reserve(in.size());
for (char c : in) {
switch (c) {
case '&': out += "&"; break;
case '<': out += "<"; break;
case '>': out += ">"; break;
case '"': out += """; break;
case '\'': out += "'"; break;
default: out += c; break;
}
}
return out;
}
// Reject filenames containing path separators, '..' segments, or NUL bytes
// to prevent path traversal (CWE-22) when joining with permanent_dir.
static bool is_safe_filename(std::string_view name) {
if (name.empty()) return false;
if (name == "." || name == "..") return false;
if (name.find('/') != std::string_view::npos) return false;
if (name.find('\\') != std::string_view::npos) return false;
if (name.find('\0') != std::string_view::npos) return false;
return true;
}
class file_upload_resource : public httpserver::http_resource {
public:
httpserver::http_response render_get(const httpserver::http_request&) override {
return httpserver::http_response::string(R"html(<html>
<body>
<h1>File Upload with Cleanup Callback Demo</h1>
<p>Uploaded files will be moved to the permanent directory.</p>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="file" multiple>
<br><br>
<input type="submit" value="Upload">
</form>
</body>
</html>
)html", "text/html");
}
httpserver::http_response render_post(const httpserver::http_request& req) override {
std::string post_response = R"html(<html>
<body>
<h1>Upload Complete</h1>
<p>Files have been moved to permanent storage:</p>
<ul>
)html";
post_response.reserve(post_response.size() + 256);
for (auto &file_key : req.get_files()) {
for (auto &files : file_key.second) {
post_response += " <li>";
post_response += html_escape(files.first);
post_response += " (";
post_response += std::to_string(files.second.get_file_size());
post_response += " bytes)</li>\n";
}
}
post_response += " </ul>\n";
post_response += " <a href=\"/\">Upload more</a>\n";
post_response += "</body>\n</html>";
return httpserver::http_response::string(post_response, "text/html").with_status(201);
}
};
int main(int argc, char** argv) {
if (3 != argc) {
std::cout << "Usage: file_upload_with_callback <temp_dir> <permanent_dir>" << std::endl;
std::cout << std::endl;
std::cout << " temp_dir: directory for temporary upload storage" << std::endl;
std::cout << " permanent_dir: directory where files will be moved after upload" << std::endl;
return -1;
}
std::string temp_dir = argv[1];
std::string permanent_dir = argv[2];
std::cout << "Starting file upload server on port 8080..." << std::endl;
std::cout << " Temporary directory: " << temp_dir << std::endl;
std::cout << " Permanent directory: " << permanent_dir << std::endl;
std::cout << std::endl;
std::cout << "Open http://localhost:8080 in your browser to upload files." << std::endl;
httpserver::webserver ws{httpserver::create_webserver(8080)
.file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY)
.file_upload_dir(temp_dir)
.generate_random_filename_on_upload()
.file_cleanup_callback([&permanent_dir](const std::string& key,
const std::string& filename,
const httpserver::http::file_info& info) {
(void)key; // Unused in this example
// Validate the filename before joining with permanent_dir.
// generate_random_filename_on_upload() already gives us a
// server-generated name, but a defensive check here protects
// against future code changes that may pass a client-supplied
// name through.
if (!is_safe_filename(filename)) {
std::cerr << "Rejected unsafe filename, will be deleted"
<< std::endl;
return true;
}
// Move the uploaded file to permanent storage
std::string dest = permanent_dir + "/" + filename;
int result = std::rename(info.get_file_system_file_name().c_str(), dest.c_str());
if (result == 0) {
std::cout << "Moved: " << filename << " -> " << dest << std::endl;
return false; // Don't delete - we moved it
} else {
std::cerr << "Failed to move " << filename << ", will be deleted" << std::endl;
return true; // Delete the temp file on failure
}
})};
auto fur = std::make_shared<file_upload_resource>();
ws.register_path("/", fur);
ws.start(true);
return 0;
}