]>
Commit | Line | Data |
---|---|---|
c26aca37 MT |
1 | /*############################################################################# |
2 | # # | |
3 | # Pakfire - The IPFire package management system # | |
4 | # Copyright (C) 2023 Pakfire development team # | |
5 | # # | |
6 | # This program is free software: you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation, either version 3 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # This program is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
18 | # # | |
19 | #############################################################################*/ | |
20 | ||
21 | #include <errno.h> | |
90a6b8e4 | 22 | #include <fcntl.h> |
c26aca37 MT |
23 | #include <limits.h> |
24 | #include <sys/queue.h> | |
c26aca37 MT |
25 | |
26 | #include <curl/curl.h> | |
27 | ||
3fcf86a0 MT |
28 | #include <json.h> |
29 | ||
c26aca37 MT |
30 | #include <openssl/err.h> |
31 | #include <openssl/evp.h> | |
32 | ||
33 | #include <pakfire/ctx.h> | |
34 | #include <pakfire/mirrorlist.h> | |
35 | #include <pakfire/path.h> | |
36 | #include <pakfire/progress.h> | |
37 | #include <pakfire/string.h> | |
38 | #include <pakfire/util.h> | |
39 | #include <pakfire/xfer.h> | |
40 | ||
41 | struct pakfire_xfer { | |
42 | struct pakfire_ctx* ctx; | |
43 | int nrefs; | |
44 | ||
10db1fae MT |
45 | // Reference to the HTTP client |
46 | struct pakfire_httpclient* client; | |
c26aca37 MT |
47 | |
48 | // Reference to the progress indicator | |
49 | struct pakfire_progress* progress; | |
50 | ||
51 | // cURL handle | |
52 | CURL* handle; | |
53 | ||
54 | // Headers | |
55 | struct curl_slist* headers; | |
56 | ||
57 | // URL | |
58 | CURLU* fullurl; | |
59 | ||
60 | char url[PATH_MAX]; | |
61 | char title[NAME_MAX]; | |
62 | char path[PATH_MAX]; | |
c26aca37 MT |
63 | int tries; |
64 | ||
65 | // POST MIME Object | |
66 | curl_mime* mime; | |
67 | ||
68 | // Xfer direction | |
69 | enum { | |
70 | PAKFIRE_XFER_DOWNLOAD = 0, | |
71 | PAKFIRE_XFER_UPLOAD = 1, | |
72 | } direction; | |
73 | ||
74 | // Size | |
75 | size_t expected_size; | |
76 | size_t xferred; | |
77 | ||
c26aca37 MT |
78 | // File handles for streams |
79 | FILE* fin; | |
80 | FILE* fout; | |
81 | ||
82 | // Crypto Stuff | |
83 | EVP_MD_CTX* evp; | |
84 | const EVP_MD* md; | |
85 | unsigned char computed_digest[EVP_MAX_MD_SIZE]; | |
86 | unsigned int computed_digest_length; | |
87 | unsigned char expected_digest[EVP_MAX_MD_SIZE]; | |
88 | unsigned int expected_digest_length; | |
89 | ||
90 | // Mirrors | |
91 | char baseurl[PATH_MAX]; | |
92 | struct pakfire_mirrorlist* mirrors; | |
93 | struct pakfire_mirror* mirror; | |
94 | ||
95 | // Effective URL | |
96 | const char* effective_url; | |
97 | ||
98 | // Authentication | |
99 | unsigned int auth; | |
f48f3d09 MT |
100 | |
101 | // Space for cURL error message | |
102 | char error[CURL_ERROR_SIZE]; | |
c26aca37 MT |
103 | }; |
104 | ||
105 | static void pakfire_xfer_free(struct pakfire_xfer* xfer) { | |
c26aca37 MT |
106 | // Close any streams |
107 | if (xfer->fin) | |
108 | fclose(xfer->fin); | |
109 | ||
110 | // Free OpenSSL EVP context | |
111 | if (xfer->evp) | |
112 | EVP_MD_CTX_free(xfer->evp); | |
113 | ||
de1ecfbb MT |
114 | // cURL stuff |
115 | if (xfer->handle) | |
116 | curl_easy_cleanup(xfer->handle); | |
c26aca37 MT |
117 | if (xfer->headers) |
118 | curl_slist_free_all(xfer->headers); | |
119 | if (xfer->mime) | |
120 | curl_mime_free(xfer->mime); | |
121 | if (xfer->fullurl) | |
122 | curl_url_cleanup(xfer->fullurl); | |
123 | ||
124 | if (xfer->mirror) | |
125 | pakfire_mirror_unref(xfer->mirror); | |
126 | if (xfer->mirrors) | |
127 | pakfire_mirrorlist_unref(xfer->mirrors); | |
128 | if (xfer->progress) | |
129 | pakfire_progress_unref(xfer->progress); | |
bf34147e MT |
130 | if (xfer->client) |
131 | pakfire_httpclient_unref(xfer->client); | |
c26aca37 MT |
132 | if (xfer->ctx) |
133 | pakfire_ctx_unref(xfer->ctx); | |
134 | ||
135 | free(xfer); | |
136 | } | |
137 | ||
138 | #ifdef ENABLE_DEBUG | |
139 | static int pakfire_xfer_debug_callback(CURL *handle, curl_infotype type, | |
140 | char* data, size_t size, void* private) { | |
141 | struct pakfire_ctx* ctx = private; | |
142 | ||
143 | switch (type) { | |
144 | case CURLINFO_TEXT: | |
145 | CTX_DEBUG(ctx, "cURL: %.*s", (int)size, data); | |
146 | break; | |
147 | ||
148 | // Log headers | |
149 | case CURLINFO_HEADER_IN: | |
150 | CTX_DEBUG(ctx, "cURL: < %.*s", (int)size, data); | |
151 | break; | |
152 | ||
153 | case CURLINFO_HEADER_OUT: | |
154 | CTX_DEBUG(ctx, "cURL: > %.*s", (int)size, data); | |
155 | break; | |
156 | ||
157 | // Ignore everything else | |
158 | default: | |
159 | break; | |
160 | } | |
161 | ||
162 | return 0; | |
163 | } | |
164 | #endif | |
165 | ||
166 | static size_t pakfire_xfer_read(char* data, size_t size, size_t nmemb, void* p) { | |
167 | struct pakfire_xfer* xfer = p; | |
168 | ||
169 | return fread(data, size, nmemb, xfer->fout); | |
170 | } | |
171 | ||
172 | static int pakfire_xfer_seek(void* p, curl_off_t offset, int origin) { | |
173 | struct pakfire_xfer* xfer = p; | |
174 | int r; | |
175 | ||
176 | // Perform the seek | |
177 | r = fseek(xfer->fout, (long)offset, origin); | |
178 | if (r < 0) | |
179 | return CURL_SEEKFUNC_CANTSEEK; | |
180 | ||
181 | return CURL_SEEKFUNC_OK; | |
182 | } | |
183 | ||
184 | static size_t pakfire_xfer_write( | |
185 | char* data, size_t size, size_t nmemb, void* p) { | |
186 | struct pakfire_xfer* xfer = p; | |
187 | struct pakfire_ctx* ctx = xfer->ctx; | |
188 | int r; | |
189 | ||
190 | // Do not write empty blocks | |
191 | if (!nmemb) | |
192 | return nmemb; | |
193 | ||
194 | // Update message digest | |
195 | if (xfer->evp) { | |
196 | r = EVP_DigestUpdate(xfer->evp, data, nmemb); | |
197 | if (r != 1) { | |
198 | CTX_ERROR(ctx, "EVP_DigestUpdate failed: %s\n", | |
199 | ERR_error_string(ERR_get_error(), NULL)); | |
200 | return 0; | |
201 | } | |
202 | } | |
203 | ||
90a6b8e4 MT |
204 | // If there is no output steam, we just pretent that we have consumed the data |
205 | if (!xfer->fin) | |
206 | return nmemb; | |
207 | ||
c26aca37 MT |
208 | // Write everything to the allocated file descriptor |
209 | return fwrite(data, size, nmemb, xfer->fin); | |
210 | } | |
211 | ||
212 | static int pakfire_xfer_setup(struct pakfire_xfer* xfer) { | |
213 | struct pakfire_config* config = NULL; | |
214 | const char* proxy = NULL; | |
215 | int r; | |
216 | ||
10db1fae | 217 | CURLSH* share = pakfire_httpclient_share(xfer->client); |
c26aca37 MT |
218 | |
219 | // Configure the share handle | |
220 | r = curl_easy_setopt(xfer->handle, CURLOPT_SHARE, share); | |
221 | if (r) { | |
222 | CTX_ERROR(xfer->ctx, "Could not configure cURL share handle: %s\n", | |
223 | curl_easy_strerror(r)); | |
224 | return r; | |
225 | } | |
226 | ||
227 | // Fetch global configuration | |
228 | config = pakfire_ctx_get_config(xfer->ctx); | |
229 | ||
230 | // Set global configuration | |
231 | if (config) { | |
232 | proxy = pakfire_config_get(config, "general", "proxy", NULL); | |
233 | if (proxy) | |
234 | curl_easy_setopt(xfer->handle, CURLOPT_PROXY, proxy); | |
235 | } | |
236 | ||
237 | // Be a good net citizen and set a user agent | |
238 | curl_easy_setopt(xfer->handle, CURLOPT_USERAGENT, PACKAGE_NAME "/" PACKAGE_VERSION); | |
239 | ||
240 | #ifdef ENABLE_DEBUG | |
241 | // Enable logging/debugging | |
242 | curl_easy_setopt(xfer->handle, CURLOPT_VERBOSE, 1L); | |
243 | ||
244 | curl_easy_setopt(xfer->handle, CURLOPT_DEBUGFUNCTION, pakfire_xfer_debug_callback); | |
245 | curl_easy_setopt(xfer->handle, CURLOPT_DEBUGDATA, xfer->ctx); | |
246 | #endif | |
247 | ||
f48f3d09 MT |
248 | // Set error buffer |
249 | r = curl_easy_setopt(xfer->handle, CURLOPT_ERRORBUFFER, xfer->error); | |
250 | if (r) | |
251 | goto ERROR; | |
252 | ||
c26aca37 MT |
253 | // Limit protocols to HTTPS, HTTP, FTP and FILE |
254 | curl_easy_setopt(xfer->handle, CURLOPT_PROTOCOLS_STR, "HTTPS,HTTP,FTP,FILE"); | |
255 | ||
4d49c00b MT |
256 | // Raise any HTTP errors |
257 | r = curl_easy_setopt(xfer->handle, CURLOPT_FAILONERROR, 1L); | |
258 | if (r) | |
259 | goto ERROR; | |
260 | ||
541737df MT |
261 | // Allow all support encodings |
262 | curl_easy_setopt(xfer->handle, CURLOPT_ACCEPT_ENCODING, ""); | |
263 | ||
c26aca37 MT |
264 | // Reference back to this xfer |
265 | curl_easy_setopt(xfer->handle, CURLOPT_PRIVATE, xfer); | |
266 | ||
267 | // Follow any redirects | |
268 | curl_easy_setopt(xfer->handle, CURLOPT_FOLLOWLOCATION, 1); | |
269 | ||
270 | // Only follow up to 30 redirects | |
271 | curl_easy_setopt(xfer->handle, CURLOPT_MAXREDIRS, 30L); | |
272 | ||
273 | // Read any data from a callback function | |
274 | curl_easy_setopt(xfer->handle, | |
275 | CURLOPT_READFUNCTION, pakfire_xfer_read); | |
276 | curl_easy_setopt(xfer->handle, CURLOPT_READDATA, xfer); | |
277 | ||
278 | // Write all data to the callback function | |
279 | curl_easy_setopt(xfer->handle, | |
280 | CURLOPT_WRITEFUNCTION, pakfire_xfer_write); | |
281 | curl_easy_setopt(xfer->handle, CURLOPT_WRITEDATA, xfer); | |
282 | ||
283 | // Register the seek callback | |
284 | curl_easy_setopt(xfer->handle, | |
285 | CURLOPT_SEEKFUNCTION, pakfire_xfer_seek); | |
286 | curl_easy_setopt(xfer->handle, CURLOPT_SEEKDATA, xfer); | |
287 | ||
288 | // Success | |
289 | r = 0; | |
290 | ||
4d49c00b | 291 | ERROR: |
c26aca37 MT |
292 | if (config) |
293 | pakfire_config_unref(config); | |
294 | ||
295 | return r; | |
296 | } | |
297 | ||
298 | int pakfire_xfer_create(struct pakfire_xfer** xfer, struct pakfire_ctx* ctx, | |
10db1fae | 299 | struct pakfire_httpclient* client, const char* url) { |
c26aca37 MT |
300 | struct pakfire_xfer* x = NULL; |
301 | int r; | |
302 | ||
303 | // Allocate a new xfer | |
304 | x = calloc(1, sizeof(*x)); | |
305 | if (!x) | |
306 | return -errno; | |
307 | ||
308 | // Store a reference to the context | |
309 | x->ctx = pakfire_ctx_ref(ctx); | |
310 | ||
311 | // Initialize the reference counter | |
312 | x->nrefs = 1; | |
313 | ||
10db1fae MT |
314 | // Store a reference to the HTTP client |
315 | x->client = pakfire_httpclient_ref(client); | |
c26aca37 MT |
316 | |
317 | // Store the URL | |
318 | r = pakfire_string_set(x->url, url); | |
319 | if (r) | |
320 | goto ERROR; | |
321 | ||
322 | // Allocate a handle | |
323 | x->handle = curl_easy_init(); | |
324 | if (!x->handle) { | |
325 | r = 1; | |
326 | goto ERROR; | |
327 | } | |
328 | ||
329 | // Allocate the full URL | |
330 | x->fullurl = curl_url(); | |
331 | if (!x->fullurl) { | |
332 | r = -errno; | |
333 | goto ERROR; | |
334 | } | |
335 | ||
336 | // Setup the xfer | |
337 | r = pakfire_xfer_setup(x); | |
338 | if (r) | |
339 | goto ERROR; | |
340 | ||
341 | // Return the reference | |
342 | *xfer = x; | |
343 | ||
344 | return 0; | |
345 | ||
346 | ERROR: | |
347 | if (x) | |
348 | pakfire_xfer_unref(x); | |
349 | ||
350 | return r; | |
351 | } | |
352 | ||
353 | struct pakfire_xfer* pakfire_xfer_ref(struct pakfire_xfer* xfer) { | |
354 | ++xfer->nrefs; | |
355 | ||
356 | return xfer; | |
357 | } | |
358 | ||
359 | struct pakfire_xfer* pakfire_xfer_unref(struct pakfire_xfer* xfer) { | |
360 | if (--xfer->nrefs > 0) | |
361 | return xfer; | |
362 | ||
363 | pakfire_xfer_free(xfer); | |
364 | return NULL; | |
365 | } | |
366 | ||
367 | CURL* pakfire_xfer_handle(struct pakfire_xfer* xfer) { | |
368 | return xfer->handle; | |
369 | } | |
370 | ||
371 | int pakfire_xfer_set_method(struct pakfire_xfer* xfer, | |
372 | const pakfire_xfer_method_t method) { | |
373 | const char* m = NULL; | |
374 | ||
375 | switch (method) { | |
376 | case PAKFIRE_METHOD_DELETE: | |
377 | m = "DELETE"; | |
378 | break; | |
379 | ||
380 | default: | |
381 | return -EINVAL; | |
382 | } | |
383 | ||
384 | return curl_easy_setopt(xfer->handle, CURLOPT_CUSTOMREQUEST, m); | |
385 | } | |
386 | ||
387 | ||
388 | const char* pakfire_xfer_get_title(struct pakfire_xfer* xfer) { | |
389 | char title[PATH_MAX]; | |
390 | int r; | |
391 | ||
392 | // Default to the filename if no title is set | |
393 | if (!*xfer->title) { | |
394 | // Only use the basename | |
395 | r = pakfire_path_basename(title, xfer->url); | |
396 | if (r) | |
397 | return NULL; | |
398 | ||
399 | // Store the title | |
400 | r = pakfire_xfer_set_title(xfer, title); | |
401 | if (r) | |
402 | return NULL; | |
403 | } | |
404 | ||
405 | return xfer->title; | |
406 | } | |
407 | ||
408 | int pakfire_xfer_set_title(struct pakfire_xfer* xfer, const char* title) { | |
409 | return pakfire_string_set(xfer->title, title); | |
410 | } | |
411 | ||
412 | int pakfire_xfer_set_baseurl(struct pakfire_xfer* xfer, const char* baseurl) { | |
413 | return pakfire_string_set(xfer->baseurl, baseurl); | |
414 | } | |
415 | ||
416 | const char* pakfire_xfer_get_effective_url(struct pakfire_xfer* xfer) { | |
417 | return xfer->effective_url; | |
418 | } | |
419 | ||
420 | int pakfire_xfer_set_mirrorlist(struct pakfire_xfer* xfer, struct pakfire_mirrorlist* mirrors) { | |
421 | if (xfer->mirrors) | |
422 | pakfire_mirrorlist_unref(xfer->mirrors); | |
423 | ||
424 | xfer->mirrors = pakfire_mirrorlist_ref(mirrors); | |
425 | ||
426 | return 0; | |
427 | } | |
428 | ||
429 | // Size | |
430 | ||
431 | size_t pakfire_xfer_get_size(struct pakfire_xfer* xfer) { | |
432 | return xfer->expected_size; | |
433 | } | |
434 | ||
435 | int pakfire_xfer_set_size(struct pakfire_xfer* xfer, size_t size) { | |
436 | xfer->expected_size = size; | |
437 | ||
438 | return 0; | |
439 | } | |
440 | ||
441 | int pakfire_xfer_verify_digest(struct pakfire_xfer* xfer, const enum pakfire_digest_types md, | |
442 | const unsigned char* expected_digest, const size_t expected_digest_length) { | |
443 | // Check inputs | |
444 | if (!expected_digest || !expected_digest_length) | |
445 | return -EINVAL; | |
446 | ||
447 | // Expected digest length cannot be too long | |
448 | if (expected_digest_length > sizeof(xfer->expected_digest)) | |
449 | return -ENOBUFS; | |
450 | ||
451 | // Store digest type | |
452 | switch (md) { | |
453 | case PAKFIRE_DIGEST_SHA3_512: | |
454 | xfer->md = EVP_sha3_512(); | |
455 | break; | |
456 | ||
457 | case PAKFIRE_DIGEST_SHA3_256: | |
458 | xfer->md = EVP_sha3_256(); | |
459 | break; | |
460 | ||
461 | case PAKFIRE_DIGEST_BLAKE2B512: | |
462 | xfer->md = EVP_blake2b512(); | |
463 | break; | |
464 | ||
465 | case PAKFIRE_DIGEST_BLAKE2S256: | |
466 | xfer->md = EVP_blake2s256(); | |
467 | break; | |
468 | ||
469 | case PAKFIRE_DIGEST_SHA2_512: | |
470 | xfer->md = EVP_sha512(); | |
471 | break; | |
472 | ||
473 | case PAKFIRE_DIGEST_SHA2_256: | |
474 | xfer->md = EVP_sha256(); | |
475 | break; | |
476 | ||
477 | default: | |
478 | return -ENOTSUP; | |
479 | } | |
480 | ||
481 | // Store the expected digest and its length | |
482 | memcpy(xfer->expected_digest, expected_digest, expected_digest_length); | |
483 | xfer->expected_digest_length = expected_digest_length; | |
484 | ||
485 | return 0; | |
486 | } | |
487 | ||
488 | int pakfire_xfer_add_param(struct pakfire_xfer* xfer, | |
489 | const char* key, const char* format, ...) { | |
490 | curl_mimepart* part = NULL; | |
491 | char* buffer = NULL; | |
492 | va_list args; | |
493 | int r; | |
494 | ||
495 | // Allocate the MIME object if not done, yet | |
496 | if (!xfer->mime) { | |
497 | xfer->mime = curl_mime_init(xfer->handle); | |
498 | ||
499 | if (!xfer->mime) { | |
500 | CTX_ERROR(xfer->ctx, "Could not allocate the MIME object: %s\n", | |
501 | strerror(errno)); | |
502 | r = -errno; | |
503 | goto ERROR; | |
504 | } | |
505 | } | |
506 | ||
507 | // Format value | |
508 | va_start(args, format); | |
509 | r = vasprintf(&buffer, format, args); | |
510 | va_end(args); | |
511 | ||
512 | // Abort if we could not format the value | |
513 | if (r < 0) | |
514 | goto ERROR; | |
515 | ||
516 | // Allocate another MIME part | |
517 | part = curl_mime_addpart(xfer->mime); | |
518 | if (!part) { | |
519 | CTX_ERROR(xfer->ctx, "Could not allocate MIME part: %s\n", | |
520 | strerror(errno)); | |
521 | r = errno; | |
522 | goto ERROR; | |
523 | } | |
524 | ||
525 | // Set the key | |
526 | r = curl_mime_name(part, key); | |
527 | if (r) { | |
528 | CTX_ERROR(xfer->ctx, "Could not set parameter key (%s): %s\n", | |
529 | key, curl_easy_strerror(r)); | |
530 | goto ERROR; | |
531 | } | |
532 | ||
533 | // Set the data | |
534 | r = curl_mime_data(part, buffer, CURL_ZERO_TERMINATED); | |
535 | if (r) { | |
536 | CTX_ERROR(xfer->ctx, "Could not set parameter data (%s): %s\n", | |
537 | key, curl_easy_strerror(r)); | |
538 | goto ERROR; | |
539 | } | |
540 | ||
541 | ERROR: | |
542 | if (buffer) | |
543 | free(buffer); | |
544 | ||
545 | return r; | |
546 | } | |
547 | ||
3fcf86a0 MT |
548 | static void pakfire_xfer_reset_output(struct pakfire_xfer* xfer) { |
549 | if (xfer->fin) { | |
550 | fclose(xfer->fin); | |
551 | xfer->fin = NULL; | |
552 | } | |
553 | } | |
554 | ||
c26aca37 | 555 | int pakfire_xfer_set_output(struct pakfire_xfer* xfer, FILE* f) { |
3fcf86a0 MT |
556 | pakfire_xfer_reset_output(xfer); |
557 | ||
558 | // Store the new stream | |
c26aca37 MT |
559 | xfer->fin = f; |
560 | ||
561 | return 0; | |
562 | } | |
563 | ||
564 | int pakfire_xfer_set_output_buffer(struct pakfire_xfer* xfer, | |
565 | char** buffer, size_t* length) { | |
566 | FILE* f = NULL; | |
567 | ||
568 | // Open a memory stream | |
569 | f = open_memstream(buffer, length); | |
570 | if (!f) { | |
571 | CTX_ERROR(xfer->ctx, "Could not open memory stream: %s\n", strerror(errno)); | |
572 | return -errno; | |
573 | } | |
574 | ||
575 | return pakfire_xfer_set_output(xfer, f); | |
576 | } | |
577 | ||
578 | int pakfire_xfer_set_input(struct pakfire_xfer* xfer, FILE* f) { | |
579 | struct stat stat; | |
580 | int r; | |
581 | ||
582 | // Fetch the file descriptor | |
583 | const int fd = fileno(f); | |
584 | ||
585 | // Change direction | |
586 | xfer->direction = PAKFIRE_XFER_UPLOAD; | |
587 | ||
588 | // Store the file handle | |
589 | xfer->fout = f; | |
590 | ||
591 | // Try to find the upload size | |
592 | if (fd > 0) { | |
593 | r = fstat(fd, &stat); | |
594 | if (r) | |
595 | return 0; | |
596 | ||
597 | // Store the expected filesize | |
598 | xfer->expected_size = stat.st_size; | |
599 | } | |
600 | ||
601 | return 0; | |
602 | } | |
603 | ||
90a6b8e4 MT |
604 | static int pakfire_xfer_allocate_tmpfile(struct pakfire_xfer* xfer, const char* path) { |
605 | char dirname[PATH_MAX]; | |
606 | FILE* f = NULL; | |
607 | int fd = -1; | |
608 | int r; | |
609 | ||
610 | // Find the directory name | |
611 | r = pakfire_path_dirname(dirname, path); | |
612 | if (r) | |
613 | return r; | |
614 | ||
615 | // Ensure the directory exists | |
616 | r = pakfire_mkdir(dirname, 0755); | |
617 | if (r) | |
618 | return r; | |
619 | ||
620 | // Open a new temporary file | |
621 | fd = open(dirname, O_TMPFILE|O_RDWR, 0600); | |
622 | if (fd < 0) { | |
623 | CTX_ERROR(xfer->ctx, "Could not open temporary file in %s: %s\n", | |
624 | dirname, strerror(errno)); | |
625 | return -errno; | |
626 | } | |
627 | ||
628 | // Turn the file descriptor into a FILE handle | |
629 | f = fdopen(fd, "w+"); | |
630 | if (!f) | |
631 | return -errno; | |
632 | ||
633 | // Set the handle as output | |
634 | return pakfire_xfer_set_output(xfer, f); | |
635 | } | |
636 | ||
637 | int pakfire_xfer_set_output_path(struct pakfire_xfer* xfer, const char* path) { | |
638 | int r; | |
639 | ||
640 | // Store the output path | |
641 | r = pakfire_string_set(xfer->path, path); | |
642 | if (r) | |
643 | return r; | |
644 | ||
645 | // Allocate a temporary file | |
646 | r = pakfire_xfer_allocate_tmpfile(xfer, path); | |
647 | if (r) | |
648 | return r; | |
649 | ||
650 | return 0; | |
c26aca37 MT |
651 | } |
652 | ||
653 | int pakfire_xfer_auth(struct pakfire_xfer* xfer) { | |
654 | // Enable authentication | |
655 | xfer->auth = 1; | |
656 | ||
657 | return 0; | |
658 | } | |
659 | ||
660 | static int pakfire_xfer_select_mirror(struct pakfire_xfer* xfer) { | |
661 | // Choose the next mirror | |
662 | if (xfer->mirror) | |
663 | xfer->mirror = pakfire_mirrorlist_get_next(xfer->mirrors, xfer->mirror); | |
664 | ||
665 | // If no mirror has been selected yet, choose the first one | |
666 | else | |
667 | xfer->mirror = pakfire_mirrorlist_get_first(xfer->mirrors); | |
668 | ||
669 | // Skip this mirror if it is broken | |
670 | while (xfer->mirror && pakfire_mirror_is_broken(xfer->mirror)) { | |
671 | // Move on to the next mirror | |
672 | xfer->mirror = pakfire_mirrorlist_get_next(xfer->mirrors, xfer->mirror); | |
673 | } | |
674 | ||
675 | // No mirror found | |
676 | if (!xfer->mirror) { | |
677 | CTX_ERROR(xfer->ctx, "No mirrors left to try\n"); | |
678 | ||
679 | // No mirrors left | |
680 | return ENOENT; | |
681 | } | |
682 | ||
683 | CTX_DEBUG(xfer->ctx, "Selected mirror %s\n", pakfire_mirror_get_url(xfer->mirror)); | |
684 | ||
685 | return 0; | |
686 | } | |
687 | ||
688 | static const char* curl_http_version(long v) { | |
689 | switch (v) { | |
690 | #ifdef CURL_HTTP_VERSION_3_0 | |
691 | case CURL_HTTP_VERSION_3_0: | |
692 | return "HTTP/3.0"; | |
693 | #endif | |
694 | ||
695 | case CURL_HTTP_VERSION_2_0: | |
696 | return "HTTP/2.0"; | |
697 | ||
698 | case CURL_HTTP_VERSION_1_1: | |
699 | return "HTTP/1.1"; | |
700 | ||
701 | case CURL_HTTP_VERSION_1_0: | |
702 | return "HTTP/1.0"; | |
703 | } | |
704 | ||
705 | return "unknown"; | |
706 | } | |
707 | ||
708 | static int pakfire_xfer_save(struct pakfire_xfer* xfer) { | |
c26aca37 MT |
709 | int r; |
710 | ||
711 | // Flush any buffered data out to disk | |
712 | if (xfer->fin) | |
713 | fflush(xfer->fin); | |
714 | ||
715 | // Nothing to do if path isn't set | |
716 | if (!*xfer->path) | |
717 | return 0; | |
718 | ||
719 | CTX_DEBUG(xfer->ctx, "Download successful. Storing result in %s\n", xfer->path); | |
720 | ||
90a6b8e4 MT |
721 | int fd = fileno(xfer->fin); |
722 | ||
c26aca37 MT |
723 | // Make sure the parent directory exists |
724 | r = pakfire_mkparentdir(xfer->path, 0755); | |
725 | if (r) | |
726 | return r; | |
727 | ||
728 | // Unlink the destination file (if it exists) | |
729 | unlink(xfer->path); | |
730 | ||
731 | // Move the temporary file to its destination | |
90a6b8e4 | 732 | r = linkat(fd, "", AT_FDCWD, xfer->path, AT_EMPTY_PATH); |
c26aca37 | 733 | if (r) { |
99dc265e | 734 | CTX_ERROR(xfer->ctx, "Could not link destination file %s: %m\n", xfer->path); |
c26aca37 MT |
735 | return r; |
736 | } | |
737 | ||
c26aca37 MT |
738 | return 0; |
739 | } | |
740 | ||
741 | int pakfire_xfer_fail(struct pakfire_xfer* xfer, int code) { | |
742 | int r; | |
743 | ||
744 | CTX_DEBUG(xfer->ctx, "Xfer failed\n"); | |
745 | ||
92cc235a MT |
746 | // Throw away any downloaded data |
747 | if (xfer->fin) { | |
748 | // Get file descriptor | |
749 | int fd = fileno(xfer->fin); | |
750 | ||
751 | // Truncate downloaded data | |
752 | if (fd >= 0) { | |
753 | r = ftruncate(fd, 0); | |
754 | if (r) | |
755 | return r; | |
756 | } | |
c26aca37 | 757 | |
92cc235a MT |
758 | // Rewind |
759 | rewind(xfer->fin); | |
760 | } | |
c26aca37 MT |
761 | |
762 | // Did we use a mirror? | |
763 | if (xfer->mirror) { | |
764 | pakfire_mirror_xfer_failed(xfer->mirror); | |
765 | ||
766 | // Try again with another mirror | |
767 | return EAGAIN; | |
768 | } | |
769 | ||
770 | return 0; | |
771 | } | |
772 | ||
773 | int pakfire_xfer_done(struct pakfire_xfer* xfer, int code) { | |
774 | CURL* h = xfer->handle; | |
775 | int r; | |
776 | char* scheme = NULL; | |
777 | long response_code; | |
778 | long http_version; | |
779 | double total_time; | |
780 | ||
781 | curl_off_t download_size = 0; | |
782 | curl_off_t download_speed = 0; | |
783 | curl_off_t upload_size = 0; | |
784 | curl_off_t upload_speed = 0; | |
785 | ||
786 | // Finish progress | |
787 | r = pakfire_progress_finish(xfer->progress); | |
788 | if (r) | |
789 | return r; | |
790 | ||
791 | CTX_DEBUG(xfer->ctx, "cURL xfer done: %d - %s\n", | |
792 | code, curl_easy_strerror(code)); | |
793 | ||
794 | // Finish message digest computation | |
795 | if (xfer->evp) { | |
796 | r = EVP_DigestFinal_ex(xfer->evp, xfer->computed_digest, &xfer->computed_digest_length); | |
797 | if (r != 1) { | |
798 | CTX_ERROR(xfer->ctx, "Could not finish message digest computation: %s\n", | |
799 | ERR_error_string(ERR_get_error(), NULL)); | |
800 | return 1; | |
801 | } | |
802 | } | |
803 | ||
804 | // Protocol | |
805 | curl_easy_getinfo(h, CURLINFO_SCHEME, &scheme); | |
806 | ||
807 | // Effective URL | |
808 | curl_easy_getinfo(h, CURLINFO_EFFECTIVE_URL, &xfer->effective_url); | |
809 | if (xfer->effective_url) | |
810 | CTX_DEBUG(xfer->ctx, " Effective URL: %s\n", xfer->effective_url); | |
811 | ||
812 | // Response code | |
813 | curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &response_code); | |
814 | if (response_code) | |
815 | CTX_DEBUG(xfer->ctx, " Response code: %ld\n", response_code); | |
816 | ||
817 | // HTTP Version | |
818 | curl_easy_getinfo(h, CURLINFO_HTTP_VERSION, &http_version); | |
819 | if (http_version) | |
820 | CTX_DEBUG(xfer->ctx, " HTTP Version: %s\n", curl_http_version(http_version)); | |
821 | ||
822 | // Total Times | |
823 | curl_easy_getinfo(h, CURLINFO_TOTAL_TIME, &total_time); | |
824 | CTX_DEBUG(xfer->ctx, " Total Time: %.2fs\n", total_time); | |
825 | ||
826 | // Download Size | |
827 | r = curl_easy_getinfo(h, CURLINFO_SIZE_DOWNLOAD_T, &download_size); | |
828 | if (r) | |
829 | return r; | |
830 | ||
831 | if (download_size) | |
832 | CTX_DEBUG(xfer->ctx, " Download Size: %ld bytes\n", download_size); | |
833 | ||
834 | // Download Speed | |
835 | r = curl_easy_getinfo(h, CURLINFO_SPEED_DOWNLOAD_T, &download_speed); | |
836 | if (r) | |
837 | return r; | |
838 | ||
839 | if (download_speed) | |
840 | CTX_DEBUG(xfer->ctx, " Download Speed: %ld bps\n", download_speed); | |
841 | ||
842 | // Upload Size | |
843 | r = curl_easy_getinfo(h, CURLINFO_SIZE_UPLOAD_T, &upload_size); | |
844 | if (r) | |
845 | return r; | |
846 | ||
847 | if (upload_size) | |
848 | CTX_DEBUG(xfer->ctx, " Upload Size: %ld bytes\n", upload_size); | |
849 | ||
850 | // Upload Speed | |
851 | r = curl_easy_getinfo(h, CURLINFO_SPEED_UPLOAD_T, &upload_speed); | |
852 | if (r) | |
853 | return r; | |
854 | ||
855 | if (upload_speed) | |
856 | CTX_DEBUG(xfer->ctx, " Upload Speed: %ld bps\n", upload_speed); | |
857 | ||
858 | // Message Digest | |
859 | char* hexdigest = __pakfire_hexlify(xfer->computed_digest, xfer->computed_digest_length); | |
860 | if (hexdigest && *hexdigest) { | |
861 | CTX_DEBUG(xfer->ctx, " Message Digest: %s\n", hexdigest); | |
862 | free(hexdigest); | |
863 | } | |
864 | ||
865 | // Check if digests match | |
866 | if (xfer->evp) { | |
867 | r = CRYPTO_memcmp(xfer->computed_digest, xfer->expected_digest, | |
868 | xfer->computed_digest_length); | |
869 | ||
870 | // If they don't match, log an error and try again | |
871 | if (r) { | |
872 | char* computed_hexdigest = __pakfire_hexlify(xfer->computed_digest, | |
873 | xfer->computed_digest_length); | |
874 | char* expected_hexdigest = __pakfire_hexlify(xfer->expected_digest, | |
875 | xfer->expected_digest_length); | |
876 | ||
877 | CTX_ERROR(xfer->ctx, "Download checksum for %s didn't match:\n", xfer->effective_url); | |
878 | CTX_ERROR(xfer->ctx, " Expected: %s\n", expected_hexdigest); | |
879 | CTX_ERROR(xfer->ctx, " Computed: %s\n", computed_hexdigest); | |
880 | ||
881 | if (computed_hexdigest) | |
882 | free(computed_hexdigest); | |
883 | if (expected_hexdigest) | |
884 | free(expected_hexdigest); | |
885 | ||
886 | // Make this download fail | |
887 | r = pakfire_xfer_fail(xfer, 0); | |
888 | if (r) | |
889 | return r; | |
890 | ||
891 | return 1; | |
892 | } | |
893 | } | |
894 | ||
895 | // If we could not determine the scheme... | |
896 | if (!scheme) | |
897 | r = pakfire_xfer_fail(xfer, 0); | |
898 | ||
899 | // FILE | |
900 | else if (strcmp(scheme, "FILE") == 0) { | |
901 | // Handle any errors | |
902 | if (code) | |
903 | r = pakfire_xfer_fail(xfer, code); | |
904 | else | |
905 | r = pakfire_xfer_save(xfer); | |
906 | ||
907 | return r; | |
908 | ||
909 | // HTTPS + HTTP | |
910 | } else if ((strcmp(scheme, "HTTPS") == 0) || (strcmp(scheme, "HTTP") == 0)) { | |
911 | switch (response_code) { | |
912 | // 200 - OK | |
913 | case 200: | |
914 | r = pakfire_xfer_save(xfer); | |
915 | if (r) | |
916 | return r; | |
917 | break; | |
918 | ||
919 | // Treat all other response codes as an error | |
920 | default: | |
921 | r = pakfire_xfer_fail(xfer, code); | |
922 | if (r) | |
923 | return r; | |
924 | ||
925 | // Error | |
926 | return 1; | |
927 | } | |
928 | ||
929 | // FTP | |
930 | } else if (strcmp(scheme, "FTP") == 0) { | |
931 | if (response_code == 226) | |
932 | r = pakfire_xfer_save(xfer); | |
933 | else | |
934 | r = pakfire_xfer_fail(xfer, code); | |
935 | ||
936 | return r; | |
937 | } | |
938 | ||
939 | // Success | |
940 | return 0; | |
941 | } | |
942 | ||
943 | static int pakfire_xfer_update(void* data, | |
944 | curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { | |
945 | struct pakfire_xfer* xfer = data; | |
946 | ||
947 | switch (xfer->direction) { | |
948 | case PAKFIRE_XFER_DOWNLOAD: | |
949 | // Update the expected size | |
950 | xfer->expected_size = dltotal; | |
951 | ||
952 | // Update the xferred counter | |
953 | xfer->xferred = dlnow; | |
954 | break; | |
955 | ||
956 | case PAKFIRE_XFER_UPLOAD: | |
957 | // Update the expected size | |
958 | xfer->expected_size = ultotal; | |
959 | ||
960 | // Update the xferred counter | |
961 | xfer->xferred = ulnow; | |
962 | break; | |
963 | } | |
964 | ||
965 | // Do nothing if no progress indicator has been set up | |
966 | if (!xfer->progress) | |
967 | return 0; | |
968 | ||
969 | // Set maximum (because this might have changed) | |
970 | pakfire_progress_set_max_value(xfer->progress, xfer->expected_size); | |
971 | ||
972 | // Update current value | |
973 | return pakfire_progress_update(xfer->progress, xfer->xferred); | |
974 | } | |
975 | ||
c26aca37 MT |
976 | static int pakfire_xfer_prepare_progress(struct pakfire_xfer* xfer, |
977 | struct pakfire_progress* parent, int flags) { | |
978 | const char* title = NULL; | |
979 | int progress_flags = | |
980 | PAKFIRE_PROGRESS_SHOW_PERCENTAGE | | |
981 | PAKFIRE_PROGRESS_SHOW_BYTES_TRANSFERRED | | |
982 | PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED | | |
983 | PAKFIRE_PROGRESS_SHOW_ETA; | |
984 | int r; | |
985 | ||
986 | // If this has already been set up, we skip it | |
987 | if (xfer->progress) | |
988 | return 0; | |
989 | ||
990 | // Show no progress if requested | |
991 | if (flags & PAKFIRE_XFER_NO_PROGRESS) | |
992 | progress_flags |= PAKFIRE_PROGRESS_NO_PROGRESS; | |
993 | ||
994 | // Show no progress if we have a parent progress | |
995 | else if (parent) | |
996 | progress_flags |= PAKFIRE_PROGRESS_NO_PROGRESS; | |
997 | ||
998 | // Make a new progress meter | |
999 | r = pakfire_progress_create(&xfer->progress, xfer->ctx, progress_flags, parent); | |
1000 | if (r) | |
1001 | return r; | |
1002 | ||
1003 | // Set the title | |
1004 | title = pakfire_xfer_get_title(xfer); | |
1005 | if (title) { | |
1006 | r = pakfire_progress_set_title(xfer->progress, title); | |
1007 | if (r) | |
1008 | return r; | |
1009 | } | |
1010 | ||
1011 | // Configure callbacks | |
1012 | curl_easy_setopt(xfer->handle, CURLOPT_XFERINFOFUNCTION, | |
1013 | pakfire_xfer_update); | |
1014 | curl_easy_setopt(xfer->handle, CURLOPT_XFERINFODATA, xfer); | |
1015 | curl_easy_setopt(xfer->handle, CURLOPT_NOPROGRESS, 0L); | |
1016 | ||
1017 | // Start progress | |
1018 | r = pakfire_progress_start(xfer->progress, xfer->expected_size); | |
1019 | if (r) | |
1020 | return r; | |
1021 | ||
1022 | return 0; | |
1023 | } | |
1024 | ||
c26aca37 MT |
1025 | static int pakfire_xfer_prepare_url(struct pakfire_xfer* xfer) { |
1026 | int r; | |
1027 | ||
1028 | // Simply set absolute URLs | |
1029 | if (pakfire_string_is_url(xfer->url)) { | |
1030 | r = curl_url_set(xfer->fullurl, CURLUPART_URL, xfer->url, 0); | |
1031 | if (r) | |
1032 | goto ERROR; | |
1033 | ||
1034 | // Join path if we are using mirrors | |
1035 | } else if (xfer->mirrors && !pakfire_mirrorlist_empty(xfer->mirrors)) { | |
1036 | r = pakfire_xfer_select_mirror(xfer); | |
1037 | if (r) | |
1038 | goto ERROR; | |
1039 | ||
1040 | // Set the mirror's base URL first | |
1041 | r = curl_url_set(xfer->fullurl, CURLUPART_URL, | |
1042 | pakfire_mirror_get_url(xfer->mirror), 0); | |
1043 | if (r) | |
1044 | goto ERROR; | |
1045 | ||
1046 | // Then append our own part | |
1047 | r = curl_url_set(xfer->fullurl, CURLUPART_URL, xfer->url, 0); | |
1048 | if (r) | |
1049 | goto ERROR; | |
1050 | ||
1051 | // Use baseurl | |
1052 | } else if (*xfer->baseurl) { | |
1053 | // Set the base URL first | |
1054 | r = curl_url_set(xfer->fullurl, CURLUPART_URL, xfer->baseurl, 0); | |
1055 | if (r) | |
1056 | goto ERROR; | |
1057 | ||
1058 | // Then append our own part | |
1059 | r = curl_url_set(xfer->fullurl, CURLUPART_URL, xfer->url, 0); | |
1060 | if (r) | |
1061 | goto ERROR; | |
1062 | ||
1063 | // Fail if we could not set the URL | |
1064 | } else { | |
1065 | CTX_ERROR(xfer->ctx, "Invalid xfer %s\n", xfer->url); | |
1066 | r = -EINVAL; | |
1067 | goto ERROR; | |
1068 | } | |
1069 | ||
1070 | // Set the URL | |
1071 | r = curl_easy_setopt(xfer->handle, CURLOPT_CURLU, xfer->fullurl); | |
1072 | if (r) { | |
1073 | CTX_ERROR(xfer->ctx, "Could not set the URL: %s\n", curl_easy_strerror(r)); | |
1074 | goto ERROR; | |
1075 | } | |
1076 | ||
1077 | ERROR: | |
1078 | return r; | |
1079 | } | |
1080 | ||
1081 | int pakfire_xfer_prepare(struct pakfire_xfer* xfer, struct pakfire_progress* progress, int flags) { | |
1082 | int r; | |
1083 | ||
1084 | // Increment tries | |
1085 | xfer->tries++; | |
1086 | ||
1087 | // Set special options for direction | |
1088 | switch (xfer->direction) { | |
1089 | case PAKFIRE_XFER_DOWNLOAD: | |
1090 | break; | |
1091 | ||
1092 | case PAKFIRE_XFER_UPLOAD: | |
1093 | // Let cURL know that we are uploading things | |
1094 | r = curl_easy_setopt(xfer->handle, CURLOPT_UPLOAD, 1L); | |
1095 | if (r) { | |
1096 | CTX_ERROR(xfer->ctx, "Could not enable upload\n"); | |
1097 | return r; | |
1098 | } | |
1099 | ||
1100 | // Tell it the expected upload size | |
1101 | if (xfer->expected_size) { | |
1102 | r = curl_easy_setopt(xfer->handle, | |
1103 | CURLOPT_INFILESIZE_LARGE, (curl_off_t)xfer->expected_size); | |
1104 | if (r) { | |
1105 | CTX_ERROR(xfer->ctx, "Could not set upload size\n"); | |
1106 | return r; | |
1107 | } | |
1108 | } | |
1109 | ||
1110 | // Upload files chunked | |
1111 | xfer->headers = curl_slist_append(xfer->headers, "Transfer-Encoding: chunked"); | |
1112 | break; | |
1113 | } | |
1114 | ||
1115 | // Compose the URL | |
1116 | r = pakfire_xfer_prepare_url(xfer); | |
1117 | if (r) { | |
1118 | CTX_ERROR(xfer->ctx, "Could not compose URL: %m\n"); | |
1119 | return r; | |
1120 | } | |
1121 | ||
1122 | // Add any headers | |
1123 | if (xfer->headers) { | |
1124 | r = curl_easy_setopt(xfer->handle, CURLOPT_HTTPHEADER, xfer->headers); | |
1125 | if (r) { | |
1126 | CTX_ERROR(xfer->ctx, "Could not set headers: %s\n", curl_easy_strerror(r)); | |
1127 | return r; | |
1128 | } | |
1129 | } | |
1130 | ||
1131 | // Add any payload | |
1132 | if (xfer->mime) { | |
1133 | r = curl_easy_setopt(xfer->handle, CURLOPT_MIMEPOST, xfer->mime); | |
1134 | if (r) { | |
1135 | CTX_ERROR(xfer->ctx, "Could not set POST payload: %s\n", curl_easy_strerror(r)); | |
1136 | return r; | |
1137 | } | |
1138 | } | |
1139 | ||
c26aca37 MT |
1140 | // Authentication |
1141 | if (xfer->auth) { | |
1142 | // Request SPNEGO | |
1143 | r = curl_easy_setopt(xfer->handle, CURLOPT_HTTPAUTH, CURLAUTH_NEGOTIATE|CURLAUTH_ONLY); | |
1144 | if (r) { | |
1145 | CTX_ERROR(xfer->ctx, "Could not enable SPNEGO\n"); | |
1146 | return r; | |
1147 | } | |
1148 | ||
1149 | // Set an empty username | |
1150 | r = curl_easy_setopt(xfer->handle, CURLOPT_USERPWD, ":"); | |
1151 | if (r) { | |
1152 | CTX_ERROR(xfer->ctx, "Could not set username\n"); | |
1153 | return r; | |
1154 | } | |
1155 | } | |
1156 | ||
1157 | // Drop any previously used EVP contexts | |
1158 | if (xfer->evp) { | |
1159 | EVP_MD_CTX_free(xfer->evp); | |
1160 | xfer->evp = NULL; | |
1161 | } | |
1162 | ||
1163 | // Create a new EVP context | |
1164 | if (xfer->md) { | |
1165 | xfer->evp = EVP_MD_CTX_new(); | |
1166 | if (!xfer->evp) { | |
1167 | CTX_ERROR(xfer->ctx, "Could not create EVP context: %m\n"); | |
1168 | return 1; | |
1169 | } | |
1170 | ||
1171 | // Initialize the EVP context | |
1172 | r = EVP_DigestInit_ex(xfer->evp, xfer->md, NULL); | |
1173 | if (r != 1) { | |
1174 | CTX_ERROR(xfer->ctx, "Could not initialize EVP context: %s\n", | |
1175 | ERR_error_string(ERR_get_error(), NULL)); | |
1176 | return 1; | |
1177 | } | |
1178 | } | |
1179 | ||
1180 | // Setup progress | |
1181 | r = pakfire_xfer_prepare_progress(xfer, progress, flags); | |
1182 | if (r) | |
1183 | return r; | |
1184 | ||
1185 | return 0; | |
1186 | } | |
1187 | ||
1188 | int pakfire_xfer_run(struct pakfire_xfer* xfer, int flags) { | |
1189 | int r; | |
1190 | ||
1191 | AGAIN: | |
1192 | // Prepare the xfer | |
1193 | r = pakfire_xfer_prepare(xfer, NULL, flags); | |
1194 | if (r) { | |
1195 | CTX_ERROR(xfer->ctx, "Could not prepare xfer %s: %s\n", | |
1196 | xfer->url, strerror(-r)); | |
1197 | return r; | |
1198 | } | |
1199 | ||
1200 | // Perform the action | |
1201 | r = curl_easy_perform(xfer->handle); | |
1202 | ||
1203 | // Handle the result | |
1204 | r = pakfire_xfer_done(xfer, r); | |
1205 | ||
1206 | // Repeat the xfer if asked | |
1207 | switch (-r) { | |
1208 | case EAGAIN: | |
1209 | goto AGAIN; | |
1210 | ||
1211 | default: | |
1212 | break; | |
1213 | } | |
1214 | ||
1215 | return r; | |
1216 | } | |
3fcf86a0 MT |
1217 | |
1218 | static int pakfire_xfer_handle_api_error( | |
1219 | struct pakfire_xfer* xfer, const struct json_object* error) { | |
1220 | struct json_object* message = NULL; | |
1221 | struct json_object* code = NULL; | |
1222 | const char* m = NULL; | |
1223 | unsigned int c = 0; | |
1224 | ||
1225 | // Fetch the URL | |
1226 | const char* url = pakfire_xfer_get_effective_url(xfer); | |
1227 | ||
1228 | // Fetch the code | |
1229 | if (!json_object_object_get_ex(error, "code", &code)) | |
1230 | return -EBADMSG; | |
1231 | ||
1232 | // Check if the code is an integer | |
1233 | if (!json_object_is_type(code, json_type_int)) | |
1234 | return -EBADMSG; | |
1235 | ||
1236 | // Fetch the message | |
1237 | if (!json_object_object_get_ex(error, "message", &message)) | |
1238 | return -EBADMSG; | |
1239 | ||
1240 | // Check if the message is a string | |
1241 | if (!json_object_is_type(message, json_type_string)) | |
1242 | return -EBADMSG; | |
1243 | ||
1244 | c = json_object_get_uint64(code); | |
1245 | m = json_object_get_string(message); | |
1246 | ||
1247 | // Log the error | |
1248 | CTX_ERROR(xfer->ctx, "%s responded with error %u (%s):\n %s\n", | |
1249 | url, c, strerror(c), m); | |
1250 | ||
1251 | return -c; | |
1252 | } | |
1253 | ||
1254 | /* | |
1255 | This function parses an API response | |
1256 | */ | |
1257 | static int pakfire_xfer_parse_api_response(struct pakfire_xfer* xfer, | |
1258 | const char* buffer, const size_t length, struct json_object** object) { | |
1259 | struct json_object* error = NULL; | |
1260 | struct json_object* o = NULL; | |
1261 | int r; | |
1262 | ||
1263 | // Check if we received any data | |
1264 | if (!length) { | |
1265 | CTX_ERROR(xfer->ctx, "Received an empty response\n"); | |
1266 | r = -EBADMSG; | |
1267 | goto ERROR; | |
1268 | } | |
1269 | ||
1270 | // XXX Maybe fetch the parser's error message here?! | |
1271 | ||
1272 | // Parse the buffer | |
1273 | o = pakfire_json_parse(xfer->ctx, buffer, length); | |
1274 | if (!o) { | |
1275 | CTX_ERROR(xfer->ctx, "Could not parse the response\n"); | |
1276 | r = -EBADMSG; | |
1277 | goto ERROR; | |
1278 | } | |
1279 | ||
1280 | // Check if the response is a dictionary | |
1281 | if (!json_object_is_type(o, json_type_object)) { | |
1282 | CTX_ERROR(xfer->ctx, "The received object is not a JSON dict\n"); | |
1283 | r = -EBADMSG; | |
1284 | goto ERROR; | |
1285 | } | |
1286 | ||
1287 | // Fetch error | |
1288 | r = json_object_object_get_ex(o, "error", &error); | |
1289 | if (r) { | |
1290 | r = pakfire_xfer_handle_api_error(xfer, error); | |
1291 | goto ERROR; | |
1292 | } | |
1293 | ||
1294 | // Return the object | |
1295 | if (object) | |
1296 | *object = json_object_get(o); | |
1297 | ||
1298 | ERROR: | |
1299 | if (o) | |
1300 | json_object_put(o); | |
1301 | ||
1302 | return r; | |
1303 | } | |
1304 | ||
1305 | static int pakfire_xfer_run_api_request_once(struct pakfire_xfer* xfer, struct json_object** response) { | |
1306 | char* buffer = NULL; | |
1307 | size_t length = 0; | |
1308 | int r; | |
1309 | ||
1310 | // Write the response to the buffer | |
1311 | r = pakfire_xfer_set_output_buffer(xfer, &buffer, &length); | |
1312 | if (r) | |
1313 | goto ERROR; | |
1314 | ||
1315 | // Run the xfer | |
1316 | r = pakfire_xfer_run(xfer, PAKFIRE_XFER_NO_PROGRESS); | |
1317 | if (r) | |
1318 | goto ERROR; | |
1319 | ||
1320 | // Parse the response | |
1321 | r = pakfire_xfer_parse_api_response(xfer, buffer, length, response); | |
1322 | if (r) { | |
1323 | CTX_ERROR(xfer->ctx, "Could not parse the API response: %s\n", strerror(-r)); | |
1324 | goto ERROR; | |
1325 | } | |
1326 | ||
1327 | ERROR: | |
1328 | // Reset the output stream | |
1329 | pakfire_xfer_reset_output(xfer); | |
1330 | ||
1331 | if (buffer) | |
1332 | free(buffer); | |
1333 | ||
1334 | return r; | |
1335 | } | |
1336 | ||
1337 | /* | |
1338 | This function sends a request and automatically parses the response. | |
1339 | The response might optionally be returned if response is not NULL. | |
1340 | */ | |
1341 | int pakfire_xfer_run_api_request(struct pakfire_xfer* xfer, struct json_object** response) { | |
1342 | int r; | |
1343 | ||
1344 | // Loop indefinitely... | |
1345 | for (;;) { | |
1346 | r = pakfire_xfer_run_api_request_once(xfer, response); | |
1347 | switch (r) { | |
1348 | // XXX need to catch errors and act accordingly | |
1349 | ||
1350 | default: | |
1351 | return r; | |
1352 | } | |
1353 | } | |
1354 | ||
1355 | return 0; | |
1356 | } |