Don't print an error message for empty courses
[nci.git] / util.c
blobd56b46aa7837e04679ffb400b21fae9bd35cb621
1 /*
2 * Copyright (c) 2016-2019 S. Gilles <sgilles@math.umd.edu>
4 * Permission to use, copy, modify, and/or distribute this software
5 * for any purpose with or without fee is hereby granted, provided
6 * that the above copyright notice and this permission notice appear
7 * in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
13 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
14 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
15 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <errno.h>
19 #include <stddef.h>
20 #include <stdint.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <strings.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 #include <unistd.h>
28 #include <curl/curl.h>
29 #include <yajl_parse.h>
31 #include "macros.h"
32 #include "util.h"
34 /* Upper bound on length of an acceptable config file line */
35 #define LINE_BUF_LEN (1 << 11)
37 /* How many bytes to read at once during CSV parsing */
38 #define READ_BUF_LEN (1 << 10)
40 typedef enum {
41 CPS_START_OF_CELL,
42 CPS_IN_QUOTED_CELL,
43 CPS_IN_NONQUOTED_CELL,
44 CPS_JUST_SAW_0x22,
45 CPS_JUST_ENDED_CELL,
46 CPS_JUST_ENDED_LINE,
47 CPS_INVALID,
48 CPS_END_OF_INPUT
49 } csv_parse_state;
51 static size_t curl_to_devnull(char *ptr, size_t size, size_t nmemb, void *ctx);
52 static char * get_config_file_contents(const char *config_file_name);
53 static size_t extract_next_link(char *buffer, size_t size, size_t nitems,
54 void *userdata);
55 static int ie_yc_number(void *ctx, const char *val, size_t len);
56 static int ie_yc_map_key(void *ctx, const unsigned char *key, size_t len);
57 static int ie_yc_string(void *ctx, const unsigned char *val, size_t len);
58 static int fu_pt1_yc_boolean(void *ctx, int val);
59 static int fu_pt1_yc_end_map(void *ctx);
60 static int fu_pt1_yc_map_key(void *ctx, const unsigned char *key, size_t len);
61 static int fu_pt1_yc_start_map(void *ctx);
62 static int fu_pt1_yc_string(void *ctx, const unsigned char *val, size_t len);
63 static int fu_pt1_yc_number(void *ctx, const char *nval, size_t len);
64 static int kve_yc_boolean(void *ctx, int val);
65 static int kve_yc_end_map(void *ctx);
66 static int kve_yc_map_key(void *ctx, const unsigned char *key, size_t len);
67 static int kve_yc_start_map(void *ctx);
68 static int kve_yc_string(void *ctx, const unsigned char *val, size_t len);
69 static int kve_yc_number(void *ctx, const char *nval, size_t len);
70 static size_t map_hash(const char *s);
71 yajl_callbacks ie_callbacks = {
72 /* */
73 .yajl_number = ie_yc_number, /* */
74 .yajl_map_key = ie_yc_map_key, /* */
75 .yajl_string = ie_yc_string, /* */
77 yajl_callbacks fu_pt1_callbacks = {
78 /* */
79 .yajl_end_map = fu_pt1_yc_end_map, /* */
80 .yajl_map_key = fu_pt1_yc_map_key, /* */
81 .yajl_start_map = fu_pt1_yc_start_map, /* */
82 .yajl_string = fu_pt1_yc_string, /* */
83 .yajl_number = fu_pt1_yc_number, /* */
84 .yajl_boolean = fu_pt1_yc_boolean, /* */
86 yajl_callbacks kve_callbacks = {
87 /* */
88 .yajl_end_map = kve_yc_end_map, /* */
89 .yajl_map_key = kve_yc_map_key, /* */
90 .yajl_start_map = kve_yc_start_map, /* */
91 .yajl_string = kve_yc_string, /* */
92 .yajl_number = kve_yc_number, /* */
93 .yajl_boolean = kve_yc_boolean, /* */
95 static CURL *
96 create_curl_handle(void)
98 CURL *c = curl_easy_init();
100 if (!c) {
101 return 0;
104 curl_easy_setopt(c, CURLOPT_USERAGENT, NCI_USERAGENT);
105 curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
106 curl_easy_setopt(c, CURLOPT_NOPROGRESS, 1L);
107 curl_easy_setopt(c, CURLOPT_MAXREDIRS, 10L);
108 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, curl_to_devnull);
109 curl_easy_setopt(c, CURLOPT_WRITEDATA, (void *) 0);
111 return c;
114 static size_t
115 curl_to_devnull(char *ptr, size_t size, size_t nmemb, void *ctx)
117 UNUSED(ptr);
118 UNUSED(size);
119 UNUSED(nmemb);
120 UNUSED(ctx);
122 return size * nmemb;
125 static size_t
126 curl_to_yajl(char *ptr, size_t size, size_t nmemb, void *ctx)
128 size_t total = size * nmemb;
129 curl_to_yajl_ctx *c = (curl_to_yajl_ctx *) ctx;
130 yajl_status ys;
131 unsigned char *ye = 0;
134 * As part of some security thing I don't give a damn about,
135 * Canvas prepends all their responses with `while(1);' or
136 * something.
138 if (!c->skipped_chars) {
139 while (nmemb &&
140 (*ptr != '[' &&
141 *ptr != '{')) {
142 nmemb--;
143 ptr++;
146 if (nmemb &&
147 (*ptr == '[' ||
148 *ptr == '{')) {
149 c->skipped_chars = 1;
150 } else {
151 return total;
155 ys = yajl_parse(c->yh, (const unsigned char *) ptr, size * nmemb);
157 if (ys == yajl_status_client_canceled ||
158 ys == yajl_status_error) {
159 ye = yajl_get_error(c->yh, 1, (const unsigned char *) ptr,
160 size * nmemb);
161 fprintf(stderr, "yajl: %s\n", ye);
162 yajl_free_error(c->yh, ye);
164 return 0;
167 return total;
170 static size_t
171 extract_next_link(char *buffer, size_t size, size_t nitems, void *userdata)
173 size_t total = size * nitems;
174 char **storage_location = (char **) userdata;
175 char *p = 0;
176 char *lt = 0;
177 char *terminus = buffer + (total - 1);
178 char *dup = 0;
180 for (p = buffer; p < terminus - 13; ++p) {
181 if (*p == '<') {
182 lt = p;
183 } else if (!strncmp(p, ">; rel=\"next\"", 13) &&
184 lt) {
185 if (!(dup = strndup(lt + 1, (p - lt) - 1))) {
186 return 0;
189 free(*storage_location);
190 *storage_location = dup;
194 return total;
197 char *
198 get_auth_token(void)
200 return get_config_file_contents("token");
203 static char *
204 get_config_file_contents(const char *config_file_name)
206 char *xdg_config_home = getenv("XDG_CONFIG_HOME");
207 char *home = getenv("HOME");
208 char *path = 0;
209 FILE *f;
210 char *contents = 0;
211 char *extra_newline = 0;
212 size_t len = 0;
213 size_t j;
214 char buf[LINE_BUF_LEN];
215 char *b = buf;
216 size_t bytes_read = 0;
217 uint_fast8_t eof = 0;
219 if (!xdg_config_home ||
220 !xdg_config_home[0]) {
221 if (!home) {
222 fprintf(stderr, "You don't have a $HOME. I give up.\n");
223 goto cleanup;
226 len = snprintf(0, 0, "%s/.config/nci/%s", home,
227 config_file_name);
229 if (len + 1 < len) {
230 errno = EOVERFLOW;
231 perror(L(""));
232 goto cleanup;
235 if (!(path = malloc(len + 1))) {
236 perror(L("malloc"));
237 goto cleanup;
240 sprintf(path, "%s/.config/nci/%s", home, config_file_name);
241 } else {
242 len = snprintf(0, 0, "%s/nci/%s", xdg_config_home,
243 config_file_name);
245 if (len + 1 < len) {
246 errno = EOVERFLOW;
247 perror(L(""));
248 goto cleanup;
251 if (!(path = malloc(len + 1))) {
252 perror(L("malloc"));
253 goto cleanup;
256 sprintf(path, "%s/nci/%s", xdg_config_home, config_file_name);
259 if (!(f = fopen(path, "r"))) {
260 perror(L("fopen"));
261 fprintf(stderr, "Could not open %s\n", path);
262 goto cleanup;
265 while (!eof) {
266 if (buf + (LINE_BUF_LEN - 1) <= b) {
267 fprintf(stderr,
268 "File %s is too long - refusing to use it\n",
269 path);
270 goto cleanup;
273 bytes_read = fread(b, 1, (buf + (LINE_BUF_LEN - 1) - b), f);
274 eof = feof(f);
275 b += bytes_read;
278 if (b != buf) {
279 j = (b - buf) - 1;
280 buf[j] = '\0';
282 if (j > 1 &&
283 buf[j - 1] == '/') {
284 buf[j - 1] = '\0';
286 } else {
287 fprintf(stderr, "File %s is empty - it shouldn't be\n", path);
288 goto cleanup;
292 * Need not check for overflow here, since the maximum
293 * length of buffer is capped at LINE_BUF_LEN
295 if (!(contents = malloc((b - buf) + 1))) {
296 perror(L("malloc"));
297 goto cleanup;
300 sprintf(contents, "%s", buf);
302 if ((extra_newline = strchr(contents, '\n'))) {
303 *extra_newline = '\0';
306 cleanup:
308 if (f) {
309 fclose(f);
312 free(path);
314 return contents;
317 char *
318 get_url_base(void)
320 return get_config_file_contents("site");
323 static int
324 ie_yc_number(void *ctx, const char *val, size_t len)
326 return ie_yc_string(ctx, (const unsigned char *) val, len);
329 static int
330 ie_yc_map_key(void *ctx, const unsigned char *key, size_t len)
332 ie_y_ctx *c = (ie_y_ctx *) ctx;
334 c->saw_id_key = !strncmp((const char *) key, "id", 2) &&
335 len == 2;
336 c->saw_message_key = !strncmp((const char *) key, "message", 7);
338 return 1;
341 static int
342 ie_yc_string(void *ctx, const unsigned char *val, size_t len)
344 ie_y_ctx *c = (ie_y_ctx *) ctx;
345 char *dup = 0;
347 if (!c->saw_id_key &&
348 !c->saw_message_key) {
349 return 1;
352 if (c->saw_id_key &&
353 !c->id_str) {
354 return 1;
357 if (!(dup = strndup((const char *) val, len))) {
358 perror(L("strndup"));
360 return 0;
363 if (c->saw_id_key) {
364 free(*c->id_str);
365 *c->id_str = dup;
366 } else {
367 c->error_message = dup;
370 return 1;
374 key_value_extract(const char *original_path, const char *auth_token, const
375 char **field_names, char *enclosing_id, map *m)
377 int ret = 0;
378 CURL *c = 0;
379 yajl_handle y = { 0 };
380 kve_y_ctx ct = { 0 };
381 size_t j = 0;
382 curl_to_yajl_ctx ctyc = { 0 };
383 CURLcode curl_ret;
384 char ce[CURL_ERROR_SIZE];
385 long http;
386 char *free_after_next_perform = 0;
387 char *next_uri = 0;
388 char *auth_header = 0;
389 size_t len = 0;
390 struct curl_slist *custom_headers = 0;
391 const char *path = original_path;
393 ce[0] = '\0';
395 if (!(c = create_curl_handle())) {
396 ret = errno = ENOMEM;
397 perror(L("create_curl_handle"));
398 goto cleanup;
401 len = snprintf(0, 0, "Authorization: Bearer %s", auth_token);
403 if (len + 1 < len) {
404 ret = errno = EOVERFLOW;
405 perror(L(""));
406 goto cleanup;
409 if (!(auth_header = malloc(len + 1))) {
410 ret = errno;
411 perror(L("malloc"));
412 goto cleanup;
415 sprintf(auth_header, "Authorization: Bearer %s", auth_token);
417 if (!(custom_headers = curl_slist_append(custom_headers,
418 auth_header))) {
419 if (!errno) {
420 errno = ENOMEM;
423 perror(L("curl_slist_append"));
424 goto cleanup;
427 curl_easy_setopt(c, CURLOPT_HTTPHEADER, custom_headers);
428 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, curl_to_yajl);
429 curl_easy_setopt(c, CURLOPT_WRITEDATA, (void *) &ctyc);
430 curl_easy_setopt(c, CURLOPT_ERRORBUFFER, &ce);
431 curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, extract_next_link);
432 curl_easy_setopt(c, CURLOPT_HEADERDATA, (void *) &next_uri);
434 do {
435 next_uri = 0;
436 ct = (kve_y_ctx) { .m = m, .field_names = field_names,
437 .enclosing_id = enclosing_id };
439 if (!(y = yajl_alloc(&kve_callbacks, 0, (void *) &ct))) {
440 ret = errno = ENOMEM;
441 perror(L("yajl_alloc"));
442 goto cleanup;
445 ctyc = (curl_to_yajl_ctx) { .yh = y };
446 curl_easy_setopt(c, CURLOPT_URL, path);
447 curl_ret = curl_easy_perform(c);
448 curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &http);
449 yajl_complete_parse(y);
451 if (y) {
452 yajl_free(y);
453 y = 0;
456 free(free_after_next_perform);
457 free(ct.field0);
458 ct.field0 = 0;
460 if (ct.otherfields) {
461 for (j = 1; field_names[j]; ++j) {
462 free(ct.otherfields[j - 1]);
466 free(ct.otherfields);
467 ct.otherfields = 0;
469 if (curl_ret != CURLE_OK ||
470 http / 100 != 2) {
471 if (ct.error_message) {
472 fprintf(stderr, L("Error: HTTP %ld: %s\n"),
473 http, ct.error_message);
474 ret = EACCES;
475 } else {
476 fprintf(stderr, "%s: %s (http %ld)\n\n%s\n", L(
477 "libcurl"), curl_easy_strerror(
478 curl_ret), http,
479 ce);
480 ret = EINVAL;
483 break;
486 path = next_uri;
487 free_after_next_perform = next_uri;
488 } while (next_uri);
490 cleanup:
491 free(ct.error_message);
493 if (c) {
494 curl_easy_cleanup(c);
497 curl_slist_free_all(custom_headers);
498 free(auth_header);
500 if (y) {
501 yajl_free(y);
504 free(next_uri);
506 return ret;
509 static int
510 kve_yc_end_map(void *ctx)
512 kve_y_ctx *c = (kve_y_ctx *) ctx;
513 int mret = 0;
515 if (c->exhausted_enclosing_id) {
516 return 1;
519 if (c->depth == c->enclosing_id_depth) {
520 c->exhausted_enclosing_id = 1;
523 if (c->depth-- != c->enclosing_id_depth + 1) {
524 return 1;
527 if (!c->field0) {
528 return 1;
531 mret = map_add(c->m, c->field0, c->otherfields);
532 c->field0 = 0;
533 c->otherfields = 0;
535 return mret >= 0;
538 static int
539 kve_handle(kve_y_ctx *c, const char *s, size_t l)
541 int truncated_l = (l > 70) ? 70 : l;
542 char *dup;
543 size_t j = 0;
545 if (c->saw_id_key &&
546 c->enclosing_id &&
547 !c->enclosing_id_depth &&
548 l == strlen(c->enclosing_id) &&
549 !strncmp(s, c->enclosing_id, l)) {
550 c->enclosing_id_depth = c->depth;
552 return 0;
555 if (c->enclosing_id &&
556 (c->exhausted_enclosing_id ||
557 !c->enclosing_id_depth)) {
558 return 0;
561 if (c->idx_of_next_entry < 0 &&
562 !c->saw_message_key) {
563 return 0;
566 /* Need not check for overflow; truncated_l <= 70 */
567 dup = malloc(truncated_l + 1);
569 if (!dup) {
570 perror(L("malloc"));
572 return -1;
575 snprintf(dup, truncated_l + 1, "%s", s);
577 if (c->saw_message_key) {
578 c->error_message = dup;
580 return 0;
583 switch (c->idx_of_next_entry) {
584 case 0:
585 c->field0 = dup;
586 break;
587 default:
589 if (!c->otherfields) {
590 if (!c->m->num_values_per_entry) {
591 for (j = 0; c->field_names[j]; ++j) {
594 c->m->num_values_per_entry = j - 1;
597 if (!(c->otherfields = calloc(
598 c->m->num_values_per_entry,
599 sizeof *c->field_names))) {
600 return -1;
604 c->otherfields[c->idx_of_next_entry - 1] = dup;
605 break;
608 c->idx_of_next_entry = -1;
610 return 0;
613 static int
614 kve_yc_boolean(void *ctx, int val)
616 if (val) {
617 if (kve_handle((kve_y_ctx *) ctx, "true", 4)) {
618 return 0;
620 } else {
621 if (kve_handle((kve_y_ctx *) ctx, "false", 5)) {
622 return 0;
626 return 1;
629 static int
630 kve_yc_map_key(void *ctx, const unsigned char *key, size_t len)
632 kve_y_ctx *c = (kve_y_ctx *) ctx;
633 const char *s = (const char *) key;
634 size_t j;
636 c->saw_message_key = (!(strncmp(s, "message", 7)) &&
637 c->depth == 2);
638 c->saw_id_key = c->enclosing_id &&
639 len == 2 &&
640 (!(strncmp(s, "id", 2))) &&
641 c->depth == 1;
643 if (!strncmp(s, c->field_names[0], len)) {
644 c->idx_of_next_entry = 0;
646 return 1;
647 } else {
648 for (j = 1; c->field_names[j]; ++j) {
649 if (!strncmp((const char *) key, c->field_names[j],
650 len)) {
651 c->idx_of_next_entry = j;
653 return 1;
658 c->idx_of_next_entry = -1;
660 return 1;
663 static int
664 kve_yc_number(void *ctx, const char *nval, size_t len)
666 if (kve_handle((kve_y_ctx *) ctx, nval, len)) {
667 return 0;
670 return 1;
673 static int
674 kve_yc_start_map(void *ctx)
676 kve_y_ctx *c = (kve_y_ctx *) ctx;
678 c->depth++;
680 return 1;
683 static int
684 kve_yc_string(void *ctx, const unsigned char *val, size_t len)
686 kve_y_ctx *c = (kve_y_ctx *) ctx;
687 const char *s = (const char *) val;
689 if (kve_handle(c, s, len)) {
690 return 0;
693 return 1;
697 make_gse_post(grading_standard_entry *cutoffs, size_t cutoffs_num, struct
698 curl_httppost **out_post)
700 struct curl_httppost *post = 0;
701 struct curl_httppost *postend = 0;
702 int ret = EINVAL;
703 size_t j = 0;
705 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME, "title",
706 CURLFORM_PTRCONTENTS, "Grading Scheme",
707 CURLFORM_END)) {
708 ret = errno ? errno : ENOMEM;
709 errno = ret;
710 perror(L("curl_formadd"));
711 goto cleanup;
714 for (j = 0; j < cutoffs_num; ++j) {
715 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
716 "grading_scheme_entry[][name]",
717 CURLFORM_PTRCONTENTS,
718 cutoffs[j].name, CURLFORM_END)) {
719 ret = errno ? errno : ENOMEM;
720 errno = ret;
721 perror(L("curl_formadd"));
722 goto cleanup;
725 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
726 "grading_scheme_entry[][value]",
727 CURLFORM_PTRCONTENTS,
728 cutoffs[j].value, CURLFORM_END)) {
729 ret = errno ? errno : ENOMEM;
730 errno = ret;
731 perror(L("curl_formadd"));
732 goto cleanup;
736 ret = 0;
737 cleanup:
739 if (ret) {
740 curl_formfree(post);
741 post = 0;
744 *out_post = post;
746 return ret;
750 make_course_post(const char *hfg, const char *aagw, int
751 disable_grading_standard, const char *start_date, const
752 char *end_date, const
753 char *gse_id, struct curl_httppost **out_post)
755 struct curl_httppost *post = 0;
756 struct curl_httppost *postend = 0;
757 int ret = EINVAL;
759 if (hfg) {
760 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
761 "course[hide_final_grades]",
762 CURLFORM_PTRCONTENTS, hfg,
763 CURLFORM_END)) {
764 ret = errno ? errno : ENOMEM;
765 errno = ret;
766 perror(L("curl_formadd"));
767 goto cleanup;
771 if (aagw) {
772 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
773 "course[apply_assignment_group_weights]",
774 CURLFORM_PTRCONTENTS,
775 aagw, CURLFORM_END)) {
776 ret = errno ? errno : ENOMEM;
777 errno = ret;
778 perror(L("curl_formadd"));
779 goto cleanup;
783 if (disable_grading_standard) {
784 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
785 "course[grading_standard_id]",
786 CURLFORM_PTRCONTENTS, "",
787 CURLFORM_END)) {
788 ret = errno ? errno : ENOMEM;
789 errno = ret;
790 perror(L("curl_formadd"));
791 goto cleanup;
795 if (start_date) {
796 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
797 "course[start_at]", CURLFORM_PTRCONTENTS,
798 start_date,
799 CURLFORM_END)) {
800 ret = errno ? errno : ENOMEM;
801 errno = ret;
802 perror(L("curl_formadd"));
803 goto cleanup;
807 if (end_date) {
808 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
809 "course[end_at]", CURLFORM_PTRCONTENTS,
810 end_date,
811 CURLFORM_END)) {
812 ret = errno ? errno : ENOMEM;
813 errno = ret;
814 perror(L("curl_formadd"));
815 goto cleanup;
819 if (gse_id) {
820 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
821 "course[grading_standard_id]",
822 CURLFORM_PTRCONTENTS, gse_id,
823 CURLFORM_END)) {
824 ret = errno ? errno : ENOMEM;
825 errno = ret;
826 perror(L("curl_formadd"));
827 goto cleanup;
831 ret = 0;
832 cleanup:
834 if (ret) {
835 curl_formfree(post);
836 post = 0;
839 *out_post = post;
841 return ret;
844 struct curl_httppost *
845 make_agroup_post(const char *name, const char *weight, const char *drop_lowest,
846 const char *drop_highest)
848 struct curl_httppost *post = 0;
849 struct curl_httppost *postend = 0;
851 if (name) {
852 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME, "name",
853 CURLFORM_PTRCONTENTS, name, CURLFORM_END)) {
854 errno = errno ? errno : ENOMEM;
855 perror(L("curl_formadd"));
856 goto error;
860 if (weight) {
861 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
862 "group_weight", CURLFORM_PTRCONTENTS, weight,
863 CURLFORM_END)) {
864 errno = errno ? errno : ENOMEM;
865 perror(L("curl_formadd"));
866 goto error;
870 if (drop_lowest) {
871 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
872 "rules[drop_lowest]", CURLFORM_PTRCONTENTS,
873 drop_lowest,
874 CURLFORM_END)) {
875 errno = errno ? errno : ENOMEM;
876 perror(L("curl_formadd"));
877 goto error;
881 if (drop_highest) {
882 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
883 "rules[drop_highest]", CURLFORM_PTRCONTENTS,
884 drop_highest,
885 CURLFORM_END)) {
886 errno = errno ? errno : ENOMEM;
887 perror(L("curl_formadd"));
888 goto error;
892 return post;
893 error:
894 curl_formfree(post);
896 return 0;
899 struct curl_httppost *
900 make_assignment_post(const char *name, const char *max_points, const
901 char *due_date, const char *group_id)
903 struct curl_httppost *post = 0;
904 struct curl_httppost *postend = 0;
906 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
907 "assignment[submission_types][]", CURLFORM_PTRCONTENTS,
908 "on_paper",
909 CURLFORM_END)) {
910 errno = errno ? errno : ENOMEM;
911 perror(L("curl_formadd"));
912 goto error;
915 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
916 "assignment[notify_of_update]", CURLFORM_PTRCONTENTS,
917 "false",
918 CURLFORM_END)) {
919 errno = errno ? errno : ENOMEM;
920 perror(L("curl_formadd"));
921 goto error;
924 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
925 "assignment[grading_type]", CURLFORM_PTRCONTENTS,
926 "points",
927 CURLFORM_END)) {
928 errno = errno ? errno : ENOMEM;
929 perror(L("curl_formadd"));
930 goto error;
933 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
934 "assignment[published]", CURLFORM_PTRCONTENTS, "true",
935 CURLFORM_END)) {
936 errno = errno ? errno : ENOMEM;
937 perror(L("curl_formadd"));
938 goto error;
941 if (name) {
942 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
943 "assignment[name]", CURLFORM_PTRCONTENTS, name,
944 CURLFORM_END)) {
945 errno = errno ? errno : ENOMEM;
946 perror(L("curl_formadd"));
947 goto error;
951 if (max_points) {
952 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
953 "assignment[points_possible]",
954 CURLFORM_PTRCONTENTS, max_points,
955 CURLFORM_END)) {
956 errno = errno ? errno : ENOMEM;
957 perror(L("curl_formadd"));
958 goto error;
962 if (due_date) {
963 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
964 "assignment[due_at]", CURLFORM_PTRCONTENTS,
965 due_date,
966 CURLFORM_END)) {
967 errno = errno ? errno : ENOMEM;
968 perror(L("curl_formadd"));
969 goto error;
973 if (group_id) {
974 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
975 "assignment[assignment_group_id]",
976 CURLFORM_PTRCONTENTS,
977 group_id, CURLFORM_END)) {
978 errno = errno ? errno : ENOMEM;
979 perror(L("curl_formadd"));
980 goto error;
984 return post;
985 error:
986 curl_formfree(post);
988 return 0;
991 struct curl_httppost *
992 make_update_grade_post(char ***csv, size_t col, size_t rows)
994 struct curl_httppost *post = 0;
995 struct curl_httppost *postend = 0;
996 size_t j = 0;
997 char *param_name = 0;
998 size_t param_name_len = 0;
999 size_t len = 0;
1001 for (j = 1; j < rows; ++j) {
1002 if (!csv[j] ||
1003 !csv[j][col] ||
1004 !csv[j][col][0] ||
1005 !csv[j][1] ||
1006 !csv[j][1][0]) {
1007 continue;
1010 if (!strcasecmp(csv[j][col], "ex")) {
1011 len = snprintf(0, 0, "grade_data[%s][excuse]",
1012 csv[j][1]);
1014 if (len > param_name_len) {
1015 if (len + 1 < len) {
1016 errno = EOVERFLOW;
1017 perror(L(""));
1018 goto error;
1021 param_name_len = len + 1;
1023 if (!(param_name = realloc(param_name,
1024 param_name_len))) {
1025 perror(L("realloc"));
1026 goto error;
1030 sprintf(param_name, "grade_data[%s][excuse]",
1031 csv[j][1]);
1033 if (curl_formadd(&post, &postend, CURLFORM_COPYNAME,
1034 param_name, CURLFORM_PTRCONTENTS,
1035 "true",
1036 CURLFORM_END)) {
1037 errno = errno ? errno : ENOMEM;
1038 perror(L("curl_formadd"));
1039 goto error;
1041 } else {
1042 len = snprintf(0, 0, "grade_data[%s][posted_grade]",
1043 csv[j][1]);
1045 if (len > param_name_len) {
1046 if (len + 1 < len) {
1047 errno = EOVERFLOW;
1048 perror(L(""));
1049 goto error;
1052 param_name_len = len + 1;
1054 if (!(param_name = realloc(param_name,
1055 param_name_len))) {
1056 perror(L("realloc"));
1057 goto error;
1061 sprintf(param_name, "grade_data[%s][posted_grade]",
1062 csv[j][1]);
1064 if (curl_formadd(&post, &postend, CURLFORM_COPYNAME,
1065 param_name, CURLFORM_PTRCONTENTS,
1066 csv[j][col],
1067 CURLFORM_END)) {
1068 errno = errno ? errno : ENOMEM;
1069 perror(L("curl_formadd"));
1070 goto error;
1075 goto done;
1076 error:
1077 curl_formfree(post);
1078 post = 0;
1079 done:
1080 free(param_name);
1082 return post;
1085 /* I hate this function. I hate that I had to write it. */
1087 file_upload(const char *uri, const char *auth_token, const char *name, const
1088 char *path, const char *folder_name)
1090 struct curl_httppost *post1 = 0;
1091 struct curl_httppost *post2 = 0;
1092 struct curl_httppost *postend = 0;
1093 struct stat fstat = { 0 };
1094 int ret = EINVAL;
1095 size_t len = 0;
1096 char *filesize = 0;
1097 CURL *c1 = 0;
1098 CURL *c2 = 0;
1099 curl_to_yajl_ctx ctyc = { 0 };
1100 yajl_handle y = { 0 };
1101 fu_pt1_y_ctx ct1 = { 0 };
1102 CURLcode curl_ret;
1103 char ce[CURL_ERROR_SIZE];
1104 long http = 0;
1105 char *auth_header = 0;
1106 struct curl_slist *custom_headers = 0;
1107 size_t j = 0;
1109 ce[0] = '\0';
1111 /* Get file information */
1112 if (stat(path, &fstat)) {
1113 ret = errno;
1114 perror(L("stat"));
1115 fprintf(stderr, "invalid path: %s\n", path);
1116 goto cleanup;
1119 len = snprintf(0, 0, "%zu", (size_t) fstat.st_size);
1121 if (len + 1 < len) {
1122 ret = errno = EOVERFLOW;
1123 perror(L(""));
1124 goto cleanup;
1127 if (!(filesize = malloc(len + 1))) {
1128 ret = errno;
1129 perror(L("malloc"));
1130 goto cleanup;
1133 sprintf(filesize, "%zu", (size_t) fstat.st_size);
1135 /* Part 1: post to canvas */
1136 if (!(c1 = create_curl_handle())) {
1137 ret = errno = ENOMEM;
1138 perror(L("create_curl_handle"));
1139 goto cleanup;
1142 len = snprintf(0, 0, "Authorization: Bearer %s", auth_token);
1144 if (len + 1 < len) {
1145 ret = errno = EOVERFLOW;
1146 perror(L(""));
1147 goto cleanup;
1150 if (!(auth_header = malloc(len + 1))) {
1151 ret = errno;
1152 perror(L("malloc"));
1153 goto cleanup;
1156 sprintf(auth_header, "Authorization: Bearer %s", auth_token);
1158 if (!(custom_headers = curl_slist_append(custom_headers,
1159 auth_header))) {
1160 if (!errno) {
1161 errno = ENOMEM;
1164 perror(L("curl_slist_append"));
1165 goto cleanup;
1168 if (!(y = yajl_alloc(&fu_pt1_callbacks, 0, (void *) &ct1))) {
1169 ret = errno = ENOMEM;
1170 perror(L("yajl_alloc"));
1171 goto cleanup;
1174 curl_easy_setopt(c1, CURLOPT_URL, path);
1175 curl_easy_setopt(c1, CURLOPT_HTTPHEADER, custom_headers);
1176 curl_easy_setopt(c1, CURLOPT_WRITEFUNCTION, curl_to_yajl);
1177 ctyc = (curl_to_yajl_ctx) { .yh = y };
1178 curl_easy_setopt(c1, CURLOPT_WRITEDATA, (void *) &ctyc);
1179 curl_easy_setopt(c1, CURLOPT_ERRORBUFFER, &ce);
1181 if (curl_formadd(&post1, &postend, CURLFORM_PTRNAME, "name",
1182 CURLFORM_PTRCONTENTS, name, CURLFORM_END)) {
1183 ret = errno ? errno : ENOMEM;
1184 errno = ret;
1185 perror(L("curl_formadd"));
1186 goto cleanup;
1189 if (curl_formadd(&post1, &postend, CURLFORM_PTRNAME, "size",
1190 CURLFORM_PTRCONTENTS, filesize, CURLFORM_END)) {
1191 ret = errno ? errno : ENOMEM;
1192 errno = ret;
1193 perror(L("curl_formadd"));
1194 goto cleanup;
1197 if (!folder_name ||
1198 !folder_name[0]) {
1199 folder_name = "";
1202 if (curl_formadd(&post1, &postend, CURLFORM_PTRNAME,
1203 "parent_folder_path", CURLFORM_PTRCONTENTS,
1204 folder_name,
1205 CURLFORM_END)) {
1206 ret = errno ? errno : ENOMEM;
1207 errno = ret;
1208 perror(L("curl_formadd"));
1209 goto cleanup;
1212 curl_easy_setopt(c1, CURLOPT_URL, uri);
1213 curl_easy_setopt(c1, CURLOPT_HTTPPOST, post1);
1214 curl_ret = curl_easy_perform(c1);
1215 curl_easy_getinfo(c1, CURLINFO_RESPONSE_CODE, &http);
1216 yajl_complete_parse(y);
1218 /* Was step 1 successful? */
1219 if (curl_ret != CURLE_OK ||
1220 http / 100 != 2) {
1221 if (ct1.error_message) {
1222 fprintf(stderr, L("Error: %s (http %ld) for %s\n"),
1223 ct1.error_message, http, uri);
1224 ret = EACCES;
1225 goto cleanup;
1226 } else {
1227 fprintf(stderr, "%s: %s (http %ld)\n\n%s\n", L(
1228 "libcurl"), curl_easy_strerror(
1229 curl_ret), http, ce);
1230 ret = EINVAL;
1231 goto cleanup;
1235 if (!ct1.upload_url) {
1236 fprintf(stderr, L(
1237 "Error: Canvas did not reply with upload url\n"));
1238 ret = EACCES;
1239 goto cleanup;
1242 /* Step 2 */
1243 if (!(c2 = create_curl_handle())) {
1244 ret = errno = ENOMEM;
1245 perror(L("create_curl_handle"));
1246 goto cleanup;
1249 curl_easy_setopt(c2, CURLOPT_URL, ct1.upload_url);
1250 curl_easy_setopt(c2, CURLOPT_FOLLOWLOCATION, 1);
1251 curl_easy_setopt(c2, CURLOPT_MAXREDIRS, 3);
1252 curl_easy_setopt(c2, CURLOPT_ERRORBUFFER, &ce);
1253 postend = 0;
1255 for (j = 0; j < ct1.upload_params_len; ++j) {
1256 if (curl_formadd(&post2, &postend, CURLFORM_PTRNAME,
1257 ct1.upload_params_keys[j],
1258 CURLFORM_PTRCONTENTS,
1259 ct1.upload_params_vals[j], CURLFORM_END)) {
1260 ret = errno ? errno : ENOMEM;
1261 errno = ret;
1262 perror(L("curl_formadd"));
1263 goto cleanup;
1267 if (curl_formadd(&post2, &postend, CURLFORM_PTRNAME, "file",
1268 CURLFORM_FILECONTENT, path, CURLFORM_END)) {
1269 ret = errno ? errno : ENOMEM;
1270 errno = ret;
1271 perror(L("curl_formadd"));
1272 goto cleanup;
1275 curl_easy_setopt(c2, CURLOPT_HTTPPOST, post2);
1276 curl_ret = curl_easy_perform(c2);
1277 curl_easy_getinfo(c2, CURLINFO_RESPONSE_CODE, &http);
1279 if (http / 100 != 2) {
1280 fprintf(stderr, "%s: %s (http %ld)\n\n%s\n", L("libcurl"),
1281 curl_easy_strerror(curl_ret), http, ce);
1282 ret = EINVAL;
1283 goto cleanup;
1286 /* Step 3 is in the FOLLOW_REDIRS of step 2. It switches to GET correctly. */
1287 ret = 0;
1288 cleanup:
1290 if (c1) {
1291 curl_easy_cleanup(c1);
1294 if (c2) {
1295 curl_easy_cleanup(c2);
1298 if (y) {
1299 yajl_free(y);
1302 for (j = 0; j < ct1.upload_params_len; ++j) {
1303 free(ct1.upload_params_keys[j]);
1304 free(ct1.upload_params_vals[j]);
1307 free(ct1.upload_params_keys);
1308 free(ct1.upload_params_vals);
1309 ct1 = (fu_pt1_y_ctx) { 0 };
1310 curl_formfree(post1);
1311 post1 = 0;
1312 curl_formfree(post2);
1313 post2 = 0;
1314 curl_slist_free_all(custom_headers);
1315 free(auth_header);
1317 return ret;
1320 static int
1321 fu_pt1_yc_end_map(void *ctx)
1323 fu_pt1_y_ctx *c = (fu_pt1_y_ctx *) ctx;
1324 int mret = 0;
1326 if (c->depth == c->depth_of_upload_params) {
1327 c->in_upload_params = 0;
1330 c->depth--;
1332 return mret >= 0;
1335 static int
1336 fu_pt1_handle_val(fu_pt1_y_ctx *c, const char *s, size_t l)
1338 char *dup = 0;
1340 if (c->next_val_is_url) {
1341 c->next_val_is_url = 0;
1343 if (!(dup = malloc(l + 1))) {
1344 perror(L("malloc"));
1346 return -1;
1349 snprintf(dup, l + 1, "%s", s);
1350 free(c->upload_url);
1351 c->upload_url = dup;
1353 return 0;
1356 if (!c->in_upload_params) {
1357 return 0;
1360 if (l + 1 < l) {
1361 errno = EOVERFLOW;
1362 perror(L(""));
1364 return -1;
1367 free(c->upload_params_vals[c->upload_params_len - 1]);
1369 if (!(dup = malloc(l + 1))) {
1370 perror(L("malloc"));
1372 return -1;
1375 snprintf(dup, l + 1, "%s", s);
1376 c->upload_params_vals[c->upload_params_len - 1] = dup;
1378 return 0;
1381 static int
1382 fu_pt1_yc_boolean(void *ctx, int val)
1384 if (val) {
1385 if (fu_pt1_handle_val((fu_pt1_y_ctx *) ctx, "true", 4)) {
1386 return 0;
1388 } else {
1389 if (fu_pt1_handle_val((fu_pt1_y_ctx *) ctx, "false", 5)) {
1390 return 0;
1394 return 1;
1397 static int
1398 fu_pt1_yc_map_key(void *ctx, const unsigned char *key, size_t len)
1400 fu_pt1_y_ctx *c = (fu_pt1_y_ctx *) ctx;
1401 const char *s = (const char *) key;
1402 void *new_keys = 0;
1403 void *new_vals = 0;
1404 size_t new_len = c->upload_params_len + 1;
1406 if (!c->in_upload_params) {
1407 if (len == 13 &&
1408 !strncmp(s, "upload_params", len)) {
1409 c->in_upload_params = 1;
1410 c->depth_of_upload_params = c->depth + 1;
1412 return 1;
1415 if (len == 10 &&
1416 !strncmp(s, "upload_url", len)) {
1417 c->next_val_is_url = 1;
1419 return 1;
1422 return 1;
1425 if (new_len < c->upload_params_len) {
1426 errno = EOVERFLOW;
1427 perror(L(""));
1429 return 0;
1432 if (new_len * (sizeof *(c->upload_params_keys)) / new_len !=
1433 sizeof *(c->upload_params_keys)) {
1434 errno = EOVERFLOW;
1435 perror(L(""));
1437 return 0;
1440 if (!(new_keys = realloc(c->upload_params_keys, new_len *
1441 (sizeof *(c->upload_params_keys))))) {
1442 perror(L("realloc"));
1444 return 0;
1447 c->upload_params_keys = new_keys;
1448 c->upload_params_keys[new_len - 1] = 0;
1450 if (!(new_vals = realloc(c->upload_params_vals, new_len *
1451 (sizeof *(c->upload_params_vals))))) {
1452 perror(L("realloc"));
1454 return 0;
1457 c->upload_params_vals = new_vals;
1458 c->upload_params_vals[new_len - 1] = 0;
1459 c->upload_params_len = new_len;
1461 if (!(c->upload_params_keys[new_len - 1] = malloc(len + 1))) {
1462 perror(L("malloc"));
1464 return 0;
1467 snprintf(c->upload_params_keys[new_len - 1], len + 1, "%s", s);
1468 c->upload_params_keys[new_len - 1][len] = '\0';
1470 return 1;
1473 static int
1474 fu_pt1_yc_number(void *ctx, const char *nval, size_t len)
1476 if (fu_pt1_handle_val((fu_pt1_y_ctx *) ctx, nval, len)) {
1477 return 0;
1480 return 1;
1483 static int
1484 fu_pt1_yc_start_map(void *ctx)
1486 fu_pt1_y_ctx *c = (fu_pt1_y_ctx *) ctx;
1488 c->depth++;
1490 return 1;
1493 static int
1494 fu_pt1_yc_string(void *ctx, const unsigned char *val, size_t len)
1496 fu_pt1_y_ctx *c = (fu_pt1_y_ctx *) ctx;
1497 const char *s = (const char *) val;
1499 if (fu_pt1_handle_val(c, s, len)) {
1500 return 0;
1503 return 1;
1507 map_add(map *m, char *k, char **vs)
1509 size_t j = 0;
1510 size_t l = 0;
1511 entry *e = 0;
1512 void *newmem = 0;
1513 size_t hash = map_hash(k);
1515 if (!k ||
1516 !m) {
1517 errno = EINVAL;
1519 return -1;
1522 for (j = 0; j < m->es[hash]; ++j) {
1523 e = &m->e[hash][j];
1525 if (!(strcmp(k, e->k))) {
1526 free(k);
1528 if (e->vs) {
1529 for (l = 0; l < m->num_values_per_entry; ++l) {
1530 free(e->vs[l]);
1534 free(e->vs);
1535 e->vs = vs;
1537 return 0;
1541 if (m->es[hash] + 1 < m->es[hash] ||
1542 m->es[hash] + 1 >= SIZE_MAX / (sizeof *m->e[hash])) {
1543 errno = EOVERFLOW;
1545 return -1;
1548 if (!(newmem = realloc(m->e[hash], (m->es[hash] + 1) *
1549 sizeof *m->e[hash]))) {
1550 goto malloc_err;
1553 m->e[hash] = newmem;
1554 m->e[hash][m->es[hash]] = (entry) { .k = k, .vs = vs };
1555 m->es[hash]++;
1557 return 0;
1558 malloc_err:
1559 errno = ENOMEM;
1561 return -1;
1564 void
1565 map_clean(map *m)
1567 size_t j = 0;
1568 size_t k = 0;
1569 size_t l = 0;
1570 entry *e = 0;
1572 if (!m) {
1573 return;
1576 for (j = 0; j < BUCKET_NUM; ++j) {
1577 for (k = 0; k < m->es[j]; ++k) {
1578 e = &m->e[j][k];
1579 free(e->k);
1581 if (e->vs) {
1582 for (l = 0; l < m->num_values_per_entry; ++l) {
1583 free(e->vs[l]);
1587 free(e->vs);
1590 free(m->e[j]);
1593 *m = (map) { 0 };
1596 static int
1597 map_cmp_key(const void *a, const void *b)
1599 const char *s = *((const char **) a);
1600 const char *t = *((const char **) b);
1601 long long m = strtoll(s, 0, 0);
1602 long long n = strtoll(t, 0, 0);
1604 return (m < n) ? -1 : ((m > n) ? 1 : strcmp(s, t));
1607 char **
1608 map_get(map *m, char *k)
1610 size_t j = 0;
1611 entry *e = 0;
1612 size_t hash = map_hash(k);
1614 if (!m ||
1615 !k) {
1616 return 0;
1619 for (j = 0; j < m->es[hash]; ++j) {
1620 e = &m->e[hash][j];
1622 if (!(strcmp(k, e->k))) {
1623 return e->vs;
1627 return 0;
1630 void
1631 map_get_keys(map *m, char ***out_k, size_t *out_num)
1633 size_t num_keys = 0;
1634 size_t j = 0;
1635 size_t k = 0;
1636 size_t l = 0;
1638 for (j = 0; j < BUCKET_NUM; ++j) {
1639 num_keys += m->es[j];
1642 if (!(*out_k = calloc(num_keys, sizeof **out_k))) {
1643 *out_num = 0;
1645 return;
1648 for (j = 0; j < BUCKET_NUM; ++j) {
1649 for (k = 0; k < m->es[j]; ++k) {
1650 (*out_k)[l++] = m->e[j][k].k;
1654 qsort(*out_k, num_keys, sizeof **out_k, map_cmp_key);
1655 *out_num = num_keys;
1658 static size_t
1659 map_hash(const char *s)
1661 const char *p = 0;
1662 uint_fast8_t j;
1663 size_t hash = 5381;
1665 if (!s) {
1666 return 0;
1669 for (p = s, j = 0; *p &&
1670 j < 15; ++p, ++j) {
1671 hash = hash * 33 ^ *p;
1674 return hash % BUCKET_NUM;
1677 void
1678 print_esc_0x22(const char *s)
1680 const char *p = s;
1682 /* Quote as per RFC 4180 */
1683 while (*p) {
1684 if (*p == '"') {
1685 putchar('"');
1686 putchar('"');
1687 } else {
1688 putchar(*p);
1691 ++p;
1695 static int
1696 process_char_csv(int c, csv_parse_state *s, char **cell, size_t *cell_sz,
1697 size_t *cell_pos, char ****csv, char *reading_header,
1698 size_t *record_len,
1699 size_t *y, size_t *x)
1701 void *newmem;
1703 if (*cell_pos + 1 >= *cell_sz) {
1704 if (*cell_sz + 64 < *cell_sz ||
1705 (*cell_sz + 64) >= SIZE_MAX / (sizeof **cell)) {
1706 errno = EOVERFLOW;
1707 perror(L(""));
1708 *s = CPS_INVALID;
1710 return -1;
1713 if (!(newmem = realloc(*cell, (*cell_sz + 64) *
1714 sizeof **cell))) {
1715 perror(L("realloc"));
1716 *s = CPS_INVALID;
1718 return -1;
1721 *cell = newmem;
1722 *cell_sz += 64;
1725 switch (*s) {
1726 case CPS_START_OF_CELL:
1728 if (c == EOF) {
1729 *s = CPS_END_OF_INPUT;
1730 } else if (c == '"') {
1731 *s = CPS_IN_QUOTED_CELL;
1732 } else if (c == ',') {
1733 *s = CPS_JUST_ENDED_CELL;
1734 (*cell)[*cell_pos] = '\0';
1735 } else if (c == '\n') {
1736 *s = CPS_JUST_ENDED_LINE;
1737 (*cell)[*cell_pos] = '\0';
1738 } else {
1739 (*cell)[(*cell_pos)++] = (char) c;
1740 *s = CPS_IN_NONQUOTED_CELL;
1743 break;
1744 case CPS_IN_QUOTED_CELL:
1746 if (c == EOF) {
1747 *s = CPS_INVALID;
1748 } else if (c == '"') {
1749 *s = CPS_JUST_SAW_0x22;
1750 } else {
1751 (*cell)[(*cell_pos)++] = (char) c;
1754 break;
1755 case CPS_IN_NONQUOTED_CELL:
1757 if (c == EOF) {
1758 *s = CPS_END_OF_INPUT;
1759 (*cell)[*cell_pos] = '\0';
1760 } else if (c == ',') {
1761 *s = CPS_JUST_ENDED_CELL;
1762 (*cell)[*cell_pos] = '\0';
1763 } else if (c == '\n') {
1764 *s = CPS_JUST_ENDED_LINE;
1765 (*cell)[*cell_pos] = '\0';
1766 } else if (c == '"') {
1767 *s = CPS_INVALID;
1768 } else {
1769 (*cell)[(*cell_pos)++] = (char) c;
1772 break;
1773 case CPS_JUST_SAW_0x22:
1775 if (c == EOF) {
1776 *s = CPS_END_OF_INPUT;
1777 } else if (c == '"') {
1778 (*cell)[(*cell_pos)++] = '"';
1779 *s = CPS_IN_QUOTED_CELL;
1780 } else if (c == ',') {
1781 *s = CPS_JUST_ENDED_CELL;
1782 (*cell)[*cell_pos] = '\0';
1783 } else if (c == '\n') {
1784 *s = CPS_JUST_ENDED_LINE;
1785 (*cell)[*cell_pos] = '\0';
1786 } else {
1787 *s = CPS_INVALID;
1790 break;
1791 case CPS_JUST_ENDED_LINE:
1792 case CPS_JUST_ENDED_CELL:
1794 if (c == EOF) {
1795 *s = CPS_END_OF_INPUT;
1796 } else if (c == '\n') {
1797 *s = CPS_JUST_ENDED_LINE;
1798 (*cell)[*cell_pos] = '\0';
1799 } else if (c == ',') {
1800 *s = CPS_JUST_ENDED_CELL;
1801 (*cell)[*cell_pos] = '\0';
1802 } else if (c == '"') {
1803 *s = CPS_IN_QUOTED_CELL;
1804 } else {
1805 (*cell)[(*cell_pos)++] = (char) c;
1806 *s = CPS_IN_NONQUOTED_CELL;
1809 break;
1810 case CPS_INVALID:
1811 case CPS_END_OF_INPUT:
1812 break;
1815 if (*reading_header &&
1816 (*s == CPS_JUST_ENDED_CELL ||
1817 *s == CPS_JUST_ENDED_LINE ||
1818 *s == CPS_END_OF_INPUT)) {
1819 (*record_len)++;
1821 if (*reading_header) {
1822 if (*record_len + 1 < *record_len ||
1823 (*record_len + 1) >= SIZE_MAX / (sizeof ***csv)) {
1824 errno = EOVERFLOW;
1825 perror(L(""));
1827 return -1;
1830 if (!(newmem = realloc((*csv)[0], (*record_len + 1) *
1831 sizeof ***csv))) {
1832 perror(L("realloc"));
1834 return -1;
1837 (*csv)[0] = newmem;
1841 if (*s == CPS_JUST_ENDED_CELL ||
1842 *s == CPS_JUST_ENDED_LINE ||
1843 *s == CPS_END_OF_INPUT) {
1844 if (*x >= *record_len) {
1845 *s = CPS_INVALID;
1847 return -1;
1850 (*csv)[*y][*x] = *cell;
1852 /* No need to check overflow; pointers are tiny */
1853 if (!(*cell = malloc(1 * sizeof **cell))) {
1854 perror(L("malloc"));
1856 return -1;
1859 *cell_sz = 1;
1860 *cell_pos = 0;
1863 if (*s == CPS_JUST_ENDED_LINE) {
1864 (*y)++;
1865 *x = 0;
1866 *reading_header = 0;
1868 if (*y + 1 < *y ||
1869 (*y + 1) >= SIZE_MAX / (sizeof *csv)) {
1870 errno = EOVERFLOW;
1871 perror(L(""));
1873 return -1;
1876 if (!(newmem = realloc(*csv, (*y + 1) * sizeof *csv))) {
1877 perror(L("realloc"));
1879 return -1;
1882 *csv = newmem;
1884 if (!((*csv)[*y] = calloc(*record_len, sizeof ***csv))) {
1885 perror(L("calloc"));
1887 return -1;
1891 if (*s == CPS_JUST_ENDED_CELL) {
1892 (*x)++;
1895 return 0;
1899 read_csv(FILE *f, char ****out_csv, size_t *out_rows, size_t *out_cols)
1901 size_t fread_ret = 0;
1902 csv_parse_state s = CPS_START_OF_CELL;
1903 char *cell = 0;
1904 size_t cell_sz = 1;
1905 size_t cell_pos = 0;
1906 char ***csv = 0;
1907 char reading_header = 1;
1908 size_t record_len = 0;
1909 size_t y = 0;
1910 size_t x = 0;
1911 size_t j;
1912 size_t k;
1913 char buf[READ_BUF_LEN];
1914 const char *buf_end = 0;
1915 const char *p = 0;
1917 if (!(cell = calloc(cell_sz, sizeof *cell))) {
1918 perror(L("calloc"));
1920 return -1;
1923 if (!(csv = calloc(1, sizeof *csv))) {
1924 perror(L("calloc"));
1926 return -1;
1929 if (!(csv[0] = calloc(1, sizeof **csv))) {
1930 perror(L("malloc"));
1932 return -1;
1935 while (!feof(f) &&
1936 !ferror(f)) {
1937 fread_ret = fread(buf, sizeof *buf, READ_BUF_LEN, f);
1939 if (!fread_ret) {
1940 continue;
1943 buf_end = buf + (fread_ret - 1);
1944 p = buf;
1946 while (p <= buf_end) {
1947 if (process_char_csv(*p, &s, &cell, &cell_sz, &cell_pos,
1948 &csv, &reading_header, &record_len,
1949 &y, &x) < 0) {
1950 goto error;
1953 p++;
1957 if (s == CPS_INVALID) {
1958 goto error;
1961 y++;
1962 free(cell);
1963 *out_csv = csv;
1964 *out_rows = y;
1965 *out_cols = record_len;
1967 return 0;
1968 error:
1970 for (j = 0; j < y; ++j) {
1971 for (k = 0; k < x; ++k) {
1972 free(csv[j][k]);
1975 free(csv[j]);
1978 free(csv);
1979 free(cell);
1981 return -1;
1985 send_and_id_scan(const char *uri, const char *auth_token, struct
1986 curl_httppost *post, const char *method, char **id)
1988 int ret = 0;
1989 CURL *c = 0;
1990 yajl_handle y = { 0 };
1991 ie_y_ctx ct = { .id_str = id };
1992 curl_to_yajl_ctx ctyc = { 0 };
1993 CURLcode curl_ret;
1994 char ce[CURL_ERROR_SIZE];
1995 long http;
1996 char *auth_header = 0;
1997 size_t len = 0;
1998 struct curl_slist *custom_headers = 0;
2000 ce[0] = '\0';
2002 if (!(c = create_curl_handle())) {
2003 ret = errno = ENOMEM;
2004 perror(L("create_curl_handle"));
2005 goto cleanup;
2008 len = snprintf(0, 0, "Authorization: Bearer %s", auth_token);
2010 if (len + 1 < len) {
2011 ret = errno = EOVERFLOW;
2012 perror(L(""));
2013 goto cleanup;
2016 if (!(auth_header = malloc(len + 1))) {
2017 ret = errno;
2018 perror(L("malloc"));
2019 goto cleanup;
2022 sprintf(auth_header, "Authorization: Bearer %s", auth_token);
2024 if (!(custom_headers = curl_slist_append(custom_headers,
2025 auth_header))) {
2026 if (!errno) {
2027 errno = ENOMEM;
2030 perror(L("curl_slist_append"));
2031 ret = errno;
2032 goto cleanup;
2035 if (!(y = yajl_alloc(&ie_callbacks, 0, (void *) &ct))) {
2036 ret = errno = ENOMEM;
2037 perror(L("yajl_alloc"));
2038 goto cleanup;
2041 ctyc = (curl_to_yajl_ctx) { .yh = y };
2042 curl_easy_setopt(c, CURLOPT_HTTPHEADER, custom_headers);
2043 curl_easy_setopt(c, CURLOPT_HTTPPOST, post);
2044 curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, method);
2045 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, curl_to_yajl);
2046 curl_easy_setopt(c, CURLOPT_WRITEDATA, (void *) &ctyc);
2047 curl_easy_setopt(c, CURLOPT_ERRORBUFFER, &ce);
2048 curl_easy_setopt(c, CURLOPT_URL, uri);
2049 curl_ret = curl_easy_perform(c);
2050 curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &http);
2051 yajl_complete_parse(y);
2053 if (curl_ret != CURLE_OK ||
2054 http / 100 != 2) {
2055 if (ct.error_message) {
2056 fprintf(stderr, L("Error: %s (http %ld) for %s\n"),
2057 ct.error_message, http, uri);
2058 ret = EACCES;
2059 } else {
2060 fprintf(stderr, "%s: %s (http %ld)\n\n%s\n", L(
2061 "libcurl"), curl_easy_strerror(
2062 curl_ret), http, ce);
2063 ret = EINVAL;
2067 cleanup:
2068 free(ct.error_message);
2070 if (c) {
2071 curl_easy_cleanup(c);
2074 curl_slist_free_all(custom_headers);
2075 free(auth_header);
2077 if (y) {
2078 yajl_free(y);
2081 return ret;