Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2019 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU Lesser General Public License as
7 : published by the Free Software Foundation; either version 2.1,
8 : or (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU Lesser General Public License for more details.
14 :
15 : You should have received a copy of the GNU Lesser General Public
16 : License along with TALER; see the file COPYING.LGPL. If not,
17 : see <http://www.gnu.org/licenses/>
18 : */
19 :
20 : /**
21 : * @file lib/sync_api_upload.c
22 : * @brief Implementation of the upload POST
23 : * @author Christian Grothoff
24 : */
25 : #include "platform.h"
26 : #include <curl/curl.h>
27 : #include <jansson.h>
28 : #include <microhttpd.h> /* just for HTTP status codes */
29 : #include <gnunet/gnunet_util_lib.h>
30 : #include <gnunet/gnunet_curl_lib.h>
31 : #include <taler/taler_signatures.h>
32 : #include <taler/taler_json_lib.h>
33 : #include "sync_service.h"
34 : #include "sync_api_curl_defaults.h"
35 :
36 :
37 : /**
38 : * @brief Handle for an upload operation.
39 : */
40 : struct SYNC_UploadOperation
41 : {
42 :
43 : /**
44 : * The url for this request.
45 : */
46 : char *url;
47 :
48 : /**
49 : * Handle for the request.
50 : */
51 : struct GNUNET_CURL_Job *job;
52 :
53 : /**
54 : * Reference to the execution context.
55 : */
56 : struct GNUNET_CURL_Context *ctx;
57 :
58 : /**
59 : * Function to call with the result.
60 : */
61 : SYNC_UploadCallback cb;
62 :
63 : /**
64 : * Closure for @e cb.
65 : */
66 : void *cb_cls;
67 :
68 : /**
69 : * Payment URI we received from the service, or NULL.
70 : */
71 : char *pay_uri;
72 :
73 : /**
74 : * Hash of the data we are uploading.
75 : */
76 : struct GNUNET_HashCode new_upload_hash;
77 : };
78 :
79 :
80 : /**
81 : * Function called when we're done processing the
82 : * HTTP /backup request.
83 : *
84 : * @param cls the `struct SYNC_UploadOperation`
85 : * @param response_code HTTP response code, 0 on error
86 : * @param response
87 : */
88 : static void
89 0 : handle_upload_finished (void *cls,
90 : long response_code,
91 : const void *data,
92 : size_t data_size)
93 : {
94 0 : struct SYNC_UploadOperation *uo = cls;
95 0 : enum TALER_ErrorCode ec = TALER_EC_INVALID;
96 : struct SYNC_UploadDetails ud;
97 : struct SYNC_UploadDetails *udp;
98 :
99 0 : uo->job = NULL;
100 0 : udp = NULL;
101 0 : memset (&ud, 0, sizeof (ud));
102 0 : switch (response_code)
103 : {
104 0 : case 0:
105 0 : break;
106 0 : case MHD_HTTP_NO_CONTENT:
107 0 : ud.us = SYNC_US_SUCCESS;
108 0 : ud.details.curr_backup_hash = &uo->new_upload_hash;
109 0 : udp = &ud;
110 0 : ec = TALER_EC_NONE;
111 0 : break;
112 0 : case MHD_HTTP_NOT_MODIFIED:
113 0 : ud.us = SYNC_US_SUCCESS;
114 0 : ud.details.curr_backup_hash = &uo->new_upload_hash;
115 0 : udp = &ud;
116 0 : ec = TALER_EC_NONE;
117 0 : break;
118 0 : case MHD_HTTP_BAD_REQUEST:
119 0 : GNUNET_break (0);
120 0 : ec = TALER_JSON_get_error_code2 (data,
121 : data_size);
122 0 : break;
123 0 : case MHD_HTTP_PAYMENT_REQUIRED:
124 0 : ud.us = SYNC_US_PAYMENT_REQUIRED;
125 0 : ud.details.payment_request = uo->pay_uri;
126 0 : udp = &ud;
127 0 : ec = TALER_EC_NONE;
128 0 : break;
129 0 : case MHD_HTTP_FORBIDDEN:
130 0 : GNUNET_break (0);
131 0 : ec = TALER_JSON_get_error_code2 (data,
132 : data_size);
133 0 : break;
134 0 : case MHD_HTTP_CONFLICT:
135 0 : ud.us = SYNC_US_CONFLICTING_BACKUP;
136 0 : GNUNET_CRYPTO_hash (data,
137 : data_size,
138 : &ud.details.recovered_backup.existing_backup_hash);
139 : ud.details.recovered_backup.existing_backup_size
140 0 : = data_size;
141 : ud.details.recovered_backup.existing_backup
142 0 : = data;
143 0 : udp = &ud;
144 0 : ec = TALER_EC_NONE;
145 0 : break;
146 0 : case MHD_HTTP_GONE:
147 0 : ec = TALER_JSON_get_error_code2 (data,
148 : data_size);
149 0 : break;
150 0 : case MHD_HTTP_LENGTH_REQUIRED:
151 0 : GNUNET_break (0);
152 0 : break;
153 0 : case MHD_HTTP_REQUEST_ENTITY_TOO_LARGE:
154 0 : ec = TALER_JSON_get_error_code2 (data,
155 : data_size);
156 0 : break;
157 0 : case MHD_HTTP_TOO_MANY_REQUESTS:
158 0 : ec = TALER_JSON_get_error_code2 (data,
159 : data_size);
160 0 : break;
161 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
162 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
163 : "Internal server error: `%.*s\n",
164 : (int) data_size,
165 : (const char *) data);
166 0 : break;
167 : }
168 0 : if (NULL != uo->cb)
169 : {
170 0 : uo->cb (uo->cb_cls,
171 : ec,
172 : response_code,
173 : udp);
174 0 : uo->cb = NULL;
175 : }
176 0 : SYNC_upload_cancel (uo);
177 0 : }
178 :
179 :
180 : /**
181 : * Handle HTTP header received by curl.
182 : *
183 : * @param buffer one line of HTTP header data
184 : * @param size size of an item
185 : * @param nitems number of items passed
186 : * @param userdata our `struct SYNC_DownloadOperation *`
187 : * @return `size * nitems`
188 : */
189 : static size_t
190 0 : handle_header (char *buffer,
191 : size_t size,
192 : size_t nitems,
193 : void *userdata)
194 : {
195 0 : struct SYNC_UploadOperation *uo = userdata;
196 0 : size_t total = size * nitems;
197 : char *ndup;
198 : const char *hdr_type;
199 : char *hdr_val;
200 : char *sp;
201 :
202 0 : ndup = GNUNET_strndup (buffer,
203 : total);
204 0 : hdr_type = strtok_r (ndup,
205 : ":",
206 : &sp);
207 0 : if (NULL == hdr_type)
208 : {
209 0 : GNUNET_free (ndup);
210 0 : return total;
211 : }
212 0 : hdr_val = strtok_r (NULL,
213 : "",
214 : &sp);
215 0 : if (NULL == hdr_val)
216 : {
217 0 : GNUNET_free (ndup);
218 0 : return total;
219 : }
220 0 : if (' ' == *hdr_val)
221 0 : hdr_val++;
222 0 : if (0 == strcasecmp (hdr_type,
223 : "Taler"))
224 : {
225 : size_t len;
226 :
227 : /* found payment URI we care about! */
228 0 : uo->pay_uri = GNUNET_strdup (hdr_val);
229 0 : len = strlen (uo->pay_uri);
230 0 : while ( (len > 0) &&
231 0 : ( ('\n' == uo->pay_uri[len - 1]) ||
232 0 : ('\r' == uo->pay_uri[len - 1]) ) )
233 : {
234 0 : len--;
235 0 : uo->pay_uri[len] = '\0';
236 : }
237 : }
238 0 : GNUNET_free (ndup);
239 0 : return total;
240 : }
241 :
242 :
243 : struct SYNC_UploadOperation *
244 0 : SYNC_upload (struct GNUNET_CURL_Context *ctx,
245 : const char *base_url,
246 : struct SYNC_AccountPrivateKeyP *priv,
247 : const struct GNUNET_HashCode *prev_backup_hash,
248 : size_t backup_size,
249 : const void *backup,
250 : enum SYNC_PaymentOptions po,
251 : const char *paid_order_id,
252 : SYNC_UploadCallback cb,
253 : void *cb_cls)
254 : {
255 : struct SYNC_AccountSignatureP account_sig;
256 : struct SYNC_UploadOperation *uo;
257 : CURL *eh;
258 : struct curl_slist *job_headers;
259 0 : struct SYNC_UploadSignaturePS usp = {
260 0 : .purpose.purpose = htonl (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD),
261 0 : .purpose.size = htonl (sizeof (usp))
262 : };
263 :
264 0 : if (NULL != prev_backup_hash)
265 0 : usp.old_backup_hash = *prev_backup_hash;
266 0 : GNUNET_CRYPTO_hash (backup,
267 : backup_size,
268 : &usp.new_backup_hash);
269 0 : GNUNET_CRYPTO_eddsa_sign (&priv->eddsa_priv,
270 : &usp,
271 : &account_sig.eddsa_sig);
272 :
273 : /* setup our HTTP headers */
274 0 : job_headers = NULL;
275 : {
276 : struct curl_slist *ext;
277 : char *val;
278 : char *hdr;
279 :
280 : /* Set Sync-Signature header */
281 0 : val = GNUNET_STRINGS_data_to_string_alloc (&account_sig,
282 : sizeof (account_sig));
283 0 : GNUNET_asprintf (&hdr,
284 : "Sync-Signature: %s",
285 : val);
286 0 : GNUNET_free (val);
287 0 : ext = curl_slist_append (job_headers,
288 : hdr);
289 0 : GNUNET_free (hdr);
290 0 : if (NULL == ext)
291 : {
292 0 : GNUNET_break (0);
293 0 : curl_slist_free_all (job_headers);
294 0 : return NULL;
295 : }
296 0 : job_headers = ext;
297 :
298 : /* set If-None-Match header */
299 0 : val = GNUNET_STRINGS_data_to_string_alloc (&usp.new_backup_hash,
300 : sizeof (struct GNUNET_HashCode));
301 0 : GNUNET_asprintf (&hdr,
302 : "%s: %s",
303 : MHD_HTTP_HEADER_IF_NONE_MATCH,
304 : val);
305 0 : GNUNET_free (val);
306 0 : ext = curl_slist_append (job_headers,
307 : hdr);
308 0 : GNUNET_free (hdr);
309 0 : if (NULL == ext)
310 : {
311 0 : GNUNET_break (0);
312 0 : curl_slist_free_all (job_headers);
313 0 : return NULL;
314 : }
315 0 : job_headers = ext;
316 :
317 : /* Setup If-Match header */
318 0 : if (NULL != prev_backup_hash)
319 : {
320 0 : val = GNUNET_STRINGS_data_to_string_alloc (&usp.old_backup_hash,
321 : sizeof (struct
322 : GNUNET_HashCode));
323 0 : GNUNET_asprintf (&hdr,
324 : "If-Match: %s",
325 : val);
326 0 : GNUNET_free (val);
327 0 : ext = curl_slist_append (job_headers,
328 : hdr);
329 0 : GNUNET_free (hdr);
330 0 : if (NULL == ext)
331 : {
332 0 : GNUNET_break (0);
333 0 : curl_slist_free_all (job_headers);
334 0 : return NULL;
335 : }
336 0 : job_headers = ext;
337 : }
338 : }
339 : /* Finished setting up headers */
340 :
341 0 : uo = GNUNET_new (struct SYNC_UploadOperation);
342 0 : uo->new_upload_hash = usp.new_backup_hash;
343 : {
344 : char *path;
345 : char *account_s;
346 : struct SYNC_AccountPublicKeyP pub;
347 :
348 0 : GNUNET_CRYPTO_eddsa_key_get_public (&priv->eddsa_priv,
349 : &pub.eddsa_pub);
350 0 : account_s = GNUNET_STRINGS_data_to_string_alloc (&pub,
351 : sizeof (pub));
352 0 : GNUNET_asprintf (&path,
353 : "backups/%s",
354 : account_s);
355 0 : GNUNET_free (account_s);
356 0 : if (0 != (po & SYNC_PO_FRESH_ORDER))
357 : {
358 0 : uo->url = (0 != (po & SYNC_PO_FORCE_PAYMENT))
359 0 : ? TALER_url_join (base_url,
360 : path,
361 : "fresh",
362 : "y",
363 : "pay",
364 : "y",
365 : (NULL != paid_order_id)
366 : ? "paying"
367 : : NULL,
368 : paid_order_id,
369 : NULL)
370 0 : : TALER_url_join (base_url,
371 : path,
372 : "fresh",
373 : "y",
374 : (NULL != paid_order_id)
375 : ? "paying"
376 : : NULL,
377 : paid_order_id,
378 : NULL);
379 : }
380 : else
381 : {
382 0 : uo->url = (0 != (po & SYNC_PO_FORCE_PAYMENT))
383 0 : ? TALER_url_join (base_url,
384 : path,
385 : "pay",
386 : "y",
387 : (NULL != paid_order_id)
388 : ? "paying"
389 : : NULL,
390 : paid_order_id,
391 : NULL)
392 0 : : TALER_url_join (base_url,
393 : path,
394 : (NULL != paid_order_id)
395 : ? "paying"
396 : : NULL,
397 : paid_order_id,
398 : NULL);
399 : }
400 :
401 0 : GNUNET_free (path);
402 : }
403 0 : uo->ctx = ctx;
404 0 : uo->cb = cb;
405 0 : uo->cb_cls = cb_cls;
406 0 : eh = SYNC_curl_easy_get_ (uo->url);
407 0 : GNUNET_assert (CURLE_OK ==
408 : curl_easy_setopt (eh,
409 : CURLOPT_POSTFIELDS,
410 : backup));
411 0 : GNUNET_assert (CURLE_OK ==
412 : curl_easy_setopt (eh,
413 : CURLOPT_POSTFIELDSIZE,
414 : (long) backup_size));
415 0 : GNUNET_assert (CURLE_OK ==
416 : curl_easy_setopt (eh,
417 : CURLOPT_HEADERFUNCTION,
418 : &handle_header));
419 0 : GNUNET_assert (CURLE_OK ==
420 : curl_easy_setopt (eh,
421 : CURLOPT_HEADERDATA,
422 : uo));
423 0 : uo->job = GNUNET_CURL_job_add_raw (ctx,
424 : eh,
425 : job_headers,
426 : &handle_upload_finished,
427 : uo);
428 0 : curl_slist_free_all (job_headers);
429 0 : return uo;
430 : }
431 :
432 :
433 : /**
434 : * Cancel the upload. Note that aborting an upload does NOT guarantee
435 : * that it did not complete, it is possible that the server did
436 : * receive the full request before the upload is aborted.
437 : *
438 : * @param uo operation to cancel.
439 : */
440 : void
441 0 : SYNC_upload_cancel (struct SYNC_UploadOperation *uo)
442 : {
443 0 : if (NULL != uo->job)
444 : {
445 0 : GNUNET_CURL_job_cancel (uo->job);
446 0 : uo->job = NULL;
447 : }
448 0 : GNUNET_free (uo->pay_uri);
449 0 : GNUNET_free (uo->url);
450 0 : GNUNET_free (uo);
451 0 : }
452 :
453 :
454 : /* end of sync_api_upload.c */
|