Use master state for trampolines
[lsnes.git] / src / core / fileupload.cpp
blob0cca260953ca94cf2b942a2b0ee5011b8a7e5a81
1 #include "core/fileupload.hpp"
2 #include "core/misc.hpp"
3 #include "library/curve25519.hpp"
4 #include "library/httpauth.hpp"
5 #include "library/httpreq.hpp"
6 #include "library/skein.hpp"
7 #include "library/streamcompress.hpp"
8 #include "library/string.hpp"
10 #include <fstream>
11 #include <boost/iostreams/categories.hpp>
12 #include <boost/iostreams/copy.hpp>
13 #include <boost/iostreams/stream.hpp>
14 #include <boost/iostreams/stream_buffer.hpp>
15 #include <boost/iostreams/filter/symmetric.hpp>
16 #include <boost/iostreams/filter/zlib.hpp>
17 #include <boost/iostreams/filtering_stream.hpp>
18 #include <boost/iostreams/device/back_inserter.hpp>
20 namespace
22 void file_upload_trampoline(file_upload* x)
24 x->_do_async();
27 struct upload_output_handler : http_request::output_handler
29 upload_output_handler(std::function<void(std::string&)> _output_cb)
31 output_cb = _output_cb;
33 ~upload_output_handler() {}
34 void header(const std::string& name, const std::string& content)
36 if(http_strlower(name) == "location") location = content;
38 void write(const char* source, size_t srcsize)
40 std::string x(source, srcsize);
41 while(x.find_first_of("\n") < x.length()) {
42 size_t split = x.find_first_of("\n");
43 std::string line = x.substr(0, split);
44 x = x.substr(split + 1);
45 incomplete_line += line;
46 while(incomplete_line.length() > 0 &&
47 incomplete_line[incomplete_line.length() - 1] == '\r')
48 incomplete_line = incomplete_line.substr(0, incomplete_line.length() - 1);
49 output_cb(incomplete_line);
50 incomplete_line = "";
52 if(x != "") incomplete_line += x;
54 void flush()
56 if(incomplete_line != "") output_cb(incomplete_line);
58 std::string get_location()
60 return location;
62 private:
63 std::function<void(std::string&)> output_cb;
64 std::string location;
65 std::string incomplete_line;
68 void compress(std::vector<char>& buf, std::string& output, std::string& compression)
70 streamcompress::base* X = NULL;
71 try {
72 if(!X) {
73 X = streamcompress::base::create_compressor("xz", "level=7,extreme=true");
74 compression = "xz";
76 } catch(...) {
78 try {
79 if(!X) {
80 X = streamcompress::base::create_compressor("gzip", "level=7");
81 compression = "gzip";
83 } catch(...) {
86 std::vector<char> out;
87 boost::iostreams::filtering_istream* s = new boost::iostreams::filtering_istream();
88 if(X) s->push(streamcompress::iostream(X));
89 s->push(boost::iostreams::array_source(&buf[0], buf.size()));
90 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
91 boost::iostreams::copy(*s, rd);
92 delete s;
93 if(X) delete X;
94 output = std::string(&out[0], out.size());
97 void load_dh25519_key(uint8_t* out)
99 std::string path = get_config_path() + "/dh25519.key";
100 std::ifstream fp(path, std::ios::binary);
101 if(!fp)
102 throw std::runtime_error("Can't open dh25519 keyfile");
103 skein::hash h(skein::hash::PIPE_512, 256);
104 while(true) {
105 char buf[4096];
106 fp.read(buf, sizeof(buf));
107 if(fp.gcount() == 0) break;
108 h.write((const uint8_t*)buf, fp.gcount());
109 skein::zeroize(buf, fp.gcount());
111 h.read(out);
112 curve25519_clamp(out);
116 file_upload::file_upload()
118 dh25519 = NULL;
119 req = NULL;
120 finished = false;
121 success = false;
124 file_upload::~file_upload()
126 if(dh25519) delete dh25519;
127 if(req) delete req;
130 void file_upload::do_async()
132 (new threads::thread(file_upload_trampoline, this))->detach();
135 void file_upload::_do_async()
137 uint8_t key[32];
138 load_dh25519_key(key);
139 dh25519 = new dh25519_http_auth(key);
140 skein::zeroize(key, sizeof(key));
142 http_async_request obtainkey;
144 threads::alock h(m);
145 req = &obtainkey;
147 http_request::null_input_handler nullinput;
148 auto auth = dh25519;
149 http_request::www_authenticate_extractor extractor([auth](const std::string& content) {
150 auth->parse_auth_response(content);
152 obtainkey.ihandler = &nullinput;
153 obtainkey.ohandler = &extractor;
154 obtainkey.verb = "PUT";
155 obtainkey.url = base_url;
156 obtainkey.authorization = dh25519->format_get_session_request();
157 add_msg("Obtaining short-term credentials...");
158 obtainkey.lauch_async();
159 while(!obtainkey.finished) {
160 threads::alock hx(obtainkey.m);
161 obtainkey.finished_cond.wait(hx);
163 if(obtainkey.errormsg != "") {
164 add_msg((stringfmt() << "Failed: " << obtainkey.errormsg).str());
165 { threads::alock h(m); req = NULL; }
166 finished = true;
167 return;
169 if(obtainkey.http_code != 401) {
170 add_msg((stringfmt() << "Failed: Expected 401, got " << obtainkey.http_code).str());
171 { threads::alock h(m); req = NULL; }
172 finished = true;
173 return;
175 if(!dh25519->is_ready()) {
176 add_msg((stringfmt() << "Failed: Authenticator is not ready!").str());
177 { threads::alock h(m); req = NULL; }
178 finished = true;
179 return;
181 add_msg("Got short-term credentials.");
182 { threads::alock h(m); req = NULL; }
185 http_async_request upload;
187 threads::alock h(m);
188 req = &upload;
190 property_upload_request input;
191 upload_output_handler output([this](const std::string& msg) { add_msg(msg); });
193 input.data["filename"] = filename;
194 if(title != "") input.data["title"] = title;
195 if(description != "") input.data["description"] = description;
196 if(gamename != "") input.data["game"] = gamename;
197 input.data["hidden"] = hidden ? "1" : "0";
198 compress(content, input.data["content"], input.data["compression"]);
200 upload.ihandler = &input;
201 upload.ohandler = &output;
202 upload.verb = "PUT";
203 upload.url = base_url;
204 add_msg("Hashing file...");
205 auto authobj = dh25519->start_request(upload.url, upload.verb);
206 while(true) {
207 char buf[4096];
208 size_t r = input.read(buf, sizeof(buf));
209 if(!r) break;
210 authobj.hash((const uint8_t*)buf, r);
212 upload.authorization = authobj.get_authorization();
213 input.rewind();
214 add_msg("Uploading file...");
215 upload.lauch_async();
216 while(!upload.finished) {
217 threads::alock hx(upload.m);
218 upload.finished_cond.wait(hx);
220 output.flush();
221 if(upload.errormsg != "") {
222 add_msg((stringfmt() << "Failed: " << upload.errormsg).str());
223 finished = true;
224 { threads::alock h(m); req = NULL; }
225 return;
227 if(upload.http_code != 201) {
228 add_msg((stringfmt() << "Failed: Expected 201, got " << upload.http_code).str());
229 finished = true;
230 { threads::alock h(m); req = NULL; }
231 return;
233 add_msg((stringfmt() << "Sucessful! URL: " << output.get_location()).str());
234 final_url = output.get_location();
235 finished = true;
236 success = true;
237 { threads::alock h(m); req = NULL; }
241 void file_upload::cancel()
243 threads::alock h(m);
244 if(req) req->cancel();
247 std::list<std::string> file_upload::get_messages()
249 threads::alock h(m);
250 std::list<std::string> x = msgs;
251 msgs.clear();
252 return x;
255 int file_upload::get_progress_ppm()
257 threads::alock h(m);
258 int ppm = -1;
259 if(req) {
260 int64_t dnow, dtotal, unow, utotal;
261 req->get_xfer_status(dnow, dtotal, unow, utotal);
262 if(utotal)
263 return 1000000 * unow / utotal;
265 return ppm;
268 void file_upload::add_msg(const std::string& msg)
270 threads::alock h(m);
271 msgs.push_back(msg);
274 void get_dh25519_pubkey(uint8_t* out)
276 uint8_t privkey[32];
277 load_dh25519_key(privkey);
278 curve25519_clamp(privkey);
279 curve25519(out, privkey, curve25519_base);
280 skein::zeroize(privkey, sizeof(privkey));