lsnes rr2-β24
[lsnes.git] / src / core / fileupload.cpp
blob979d8fd29e15368ac4959f4f43e1c6c82987a620
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 <functional>
11 #include <fstream>
12 #include <boost/iostreams/categories.hpp>
13 #include <boost/iostreams/copy.hpp>
14 #include <boost/iostreams/stream.hpp>
15 #include <boost/iostreams/stream_buffer.hpp>
16 #include <boost/iostreams/filter/symmetric.hpp>
17 #include <boost/iostreams/filter/zlib.hpp>
18 #include <boost/iostreams/filtering_stream.hpp>
19 #include <boost/iostreams/device/back_inserter.hpp>
21 namespace
23 void file_upload_trampoline(file_upload* x)
25 x->_do_async();
28 struct upload_output_handler : http_request::output_handler
30 upload_output_handler(std::function<void(std::string&)> _output_cb)
32 output_cb = _output_cb;
34 ~upload_output_handler() {}
35 void header(const std::string& name, const std::string& content)
37 if(http_strlower(name) == "location") location = content;
39 void write(const char* source, size_t srcsize)
41 std::string x(source, srcsize);
42 while(x.find_first_of("\n") < x.length()) {
43 size_t split = x.find_first_of("\n");
44 std::string line = x.substr(0, split);
45 x = x.substr(split + 1);
46 incomplete_line += line;
47 while(incomplete_line.length() > 0 &&
48 incomplete_line[incomplete_line.length() - 1] == '\r')
49 incomplete_line = incomplete_line.substr(0, incomplete_line.length() - 1);
50 output_cb(incomplete_line);
51 incomplete_line = "";
53 if(x != "") incomplete_line += x;
55 void flush()
57 if(incomplete_line != "") output_cb(incomplete_line);
59 std::string get_location()
61 return location;
63 private:
64 std::function<void(std::string&)> output_cb;
65 std::string location;
66 std::string incomplete_line;
69 void compress(std::vector<char>& buf, std::string& output, std::string& compression)
71 streamcompress::base* X = NULL;
72 try {
73 if(!X) {
74 X = streamcompress::base::create_compressor("xz", "level=7,extreme=true");
75 compression = "xz";
77 } catch(...) {
79 try {
80 if(!X) {
81 X = streamcompress::base::create_compressor("gzip", "level=7");
82 compression = "gzip";
84 } catch(...) {
87 std::vector<char> out;
88 boost::iostreams::filtering_istream* s = new boost::iostreams::filtering_istream();
89 if(X) s->push(streamcompress::iostream(X));
90 s->push(boost::iostreams::array_source(&buf[0], buf.size()));
91 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
92 boost::iostreams::copy(*s, rd);
93 delete s;
94 if(X) delete X;
95 output = std::string(&out[0], out.size());
98 void load_dh25519_key(uint8_t* out)
100 std::string path = get_config_path() + "/dh25519.key";
101 std::ifstream fp(path, std::ios::binary);
102 if(!fp)
103 throw std::runtime_error("Can't open dh25519 keyfile");
104 skein::hash h(skein::hash::PIPE_512, 256);
105 while(true) {
106 char buf[4096];
107 fp.read(buf, sizeof(buf));
108 if(fp.gcount() == 0) break;
109 h.write((const uint8_t*)buf, fp.gcount());
110 skein::zeroize(buf, fp.gcount());
112 h.read(out);
113 curve25519_clamp(out);
117 file_upload::file_upload()
119 dh25519 = NULL;
120 req = NULL;
121 finished = false;
122 success = false;
125 file_upload::~file_upload()
127 if(dh25519) delete dh25519;
128 if(req) delete req;
131 void file_upload::do_async()
133 (new threads::thread(file_upload_trampoline, this))->detach();
136 void file_upload::_do_async()
138 uint8_t key[32];
139 load_dh25519_key(key);
140 dh25519 = new dh25519_http_auth(key);
141 skein::zeroize(key, sizeof(key));
143 http_async_request obtainkey;
145 threads::alock h(m);
146 req = &obtainkey;
148 http_request::null_input_handler nullinput;
149 auto auth = dh25519;
150 http_request::www_authenticate_extractor extractor([auth](const std::string& content) {
151 auth->parse_auth_response(content);
153 obtainkey.ihandler = &nullinput;
154 obtainkey.ohandler = &extractor;
155 obtainkey.verb = "PUT";
156 obtainkey.url = base_url;
157 obtainkey.authorization = dh25519->format_get_session_request();
158 add_msg("Obtaining short-term credentials...");
159 obtainkey.lauch_async();
160 while(!obtainkey.finished) {
161 threads::alock hx(obtainkey.m);
162 obtainkey.finished_cond.wait(hx);
164 if(obtainkey.errormsg != "") {
165 add_msg((stringfmt() << "Failed: " << obtainkey.errormsg).str());
166 { threads::alock h(m); req = NULL; }
167 finished = true;
168 return;
170 if(obtainkey.http_code != 401) {
171 add_msg((stringfmt() << "Failed: Expected 401, got " << obtainkey.http_code).str());
172 { threads::alock h(m); req = NULL; }
173 finished = true;
174 return;
176 if(!dh25519->is_ready()) {
177 add_msg((stringfmt() << "Failed: Authenticator is not ready!").str());
178 { threads::alock h(m); req = NULL; }
179 finished = true;
180 return;
182 add_msg("Got short-term credentials.");
183 { threads::alock h(m); req = NULL; }
186 http_async_request upload;
188 threads::alock h(m);
189 req = &upload;
191 property_upload_request input;
192 upload_output_handler output([this](const std::string& msg) { add_msg(msg); });
194 input.data["filename"] = filename;
195 if(title != "") input.data["title"] = title;
196 if(description != "") input.data["description"] = description;
197 if(gamename != "") input.data["game"] = gamename;
198 input.data["hidden"] = hidden ? "1" : "0";
199 compress(content, input.data["content"], input.data["compression"]);
201 upload.ihandler = &input;
202 upload.ohandler = &output;
203 upload.verb = "PUT";
204 upload.url = base_url;
205 add_msg("Hashing file...");
206 auto authobj = dh25519->start_request(upload.url, upload.verb);
207 while(true) {
208 char buf[4096];
209 size_t r = input.read(buf, sizeof(buf));
210 if(!r) break;
211 authobj.hash((const uint8_t*)buf, r);
213 upload.authorization = authobj.get_authorization();
214 input.rewind();
215 add_msg("Uploading file...");
216 upload.lauch_async();
217 while(!upload.finished) {
218 threads::alock hx(upload.m);
219 upload.finished_cond.wait(hx);
221 output.flush();
222 if(upload.errormsg != "") {
223 add_msg((stringfmt() << "Failed: " << upload.errormsg).str());
224 finished = true;
225 { threads::alock h(m); req = NULL; }
226 return;
228 if(upload.http_code != 201) {
229 add_msg((stringfmt() << "Failed: Expected 201, got " << upload.http_code).str());
230 finished = true;
231 { threads::alock h(m); req = NULL; }
232 return;
234 add_msg((stringfmt() << "Sucessful! URL: " << output.get_location()).str());
235 final_url = output.get_location();
236 finished = true;
237 success = true;
238 { threads::alock h(m); req = NULL; }
242 void file_upload::cancel()
244 threads::alock h(m);
245 if(req) req->cancel();
248 std::list<std::string> file_upload::get_messages()
250 threads::alock h(m);
251 std::list<std::string> x = msgs;
252 msgs.clear();
253 return x;
256 int file_upload::get_progress_ppm()
258 threads::alock h(m);
259 int ppm = -1;
260 if(req) {
261 int64_t dnow, dtotal, unow, utotal;
262 req->get_xfer_status(dnow, dtotal, unow, utotal);
263 if(utotal)
264 return 1000000 * unow / utotal;
266 return ppm;
269 void file_upload::add_msg(const std::string& msg)
271 threads::alock h(m);
272 msgs.push_back(msg);
275 void get_dh25519_pubkey(uint8_t* out)
277 uint8_t privkey[32];
278 load_dh25519_key(privkey);
279 curve25519_clamp(privkey);
280 curve25519(out, privkey, curve25519_base);
281 skein::zeroize(privkey, sizeof(privkey));