Reorder ld commands, hopefully for less dependency on $(LD)=$(CC)
[nci.git] / util.c
blob155f3f63b685f0e7b9a98ca38b5c4171dbe11961
1 /*
2 * Copyright (c) 2016, 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>
25 #include <curl/curl.h>
26 #include <yajl_parse.h>
28 #include "macros.h"
29 #include "util.h"
31 /* Upper bound on length of an acceptable config file line */
32 #define LINE_BUF_LEN (1 << 11)
34 /* How many bytes to read at once during CSV parsing */
35 #define READ_BUF_LEN (1 << 10)
37 typedef enum {
38 CPS_START_OF_CELL,
39 CPS_IN_QUOTED_CELL,
40 CPS_IN_NONQUOTED_CELL,
41 CPS_JUST_SAW_0x22,
42 CPS_JUST_ENDED_CELL,
43 CPS_JUST_ENDED_LINE,
44 CPS_INVALID,
45 CPS_END_OF_INPUT
46 } csv_parse_state;
48 static size_t
49 curl_to_devnull(char *ptr, size_t size, size_t nmemb, void *ctx);
50 static char *
51 get_config_file_contents(const char *config_file_name);
52 static size_t
53 extract_next_link(char *buffer, size_t size, size_t nitems, void *userdata);
54 static int
55 ie_yc_number(void *ctx, const char *val, size_t len);
56 static int
57 ie_yc_map_key(void *ctx, const unsigned char *key, size_t len);
58 static int
59 ie_yc_string(void *ctx, const unsigned char *val, size_t len);
60 static int
61 kve_yc_boolean(void *ctx, int val);
62 static int
63 kve_yc_end_map(void *ctx);
64 static int
65 kve_yc_map_key(void *ctx, const unsigned char *key, size_t len);
66 static int
67 kve_yc_start_map(void *ctx);
68 static int
69 kve_yc_string(void *ctx, const unsigned char *val, size_t len);
70 static int
71 kve_yc_number(void *ctx, const char *nval, size_t len);
72 static size_t
73 map_hash(const char *s);
74 yajl_callbacks ie_callbacks = {
75 /* */
76 .yajl_number = ie_yc_number, /* */
77 .yajl_map_key = ie_yc_map_key, /* */
78 .yajl_string = ie_yc_string, /* */
80 yajl_callbacks kve_callbacks = {
81 /* */
82 .yajl_end_map = kve_yc_end_map, /* */
83 .yajl_map_key = kve_yc_map_key, /* */
84 .yajl_start_map = kve_yc_start_map, /* */
85 .yajl_string = kve_yc_string, /* */
86 .yajl_number = kve_yc_number, /* */
87 .yajl_boolean = kve_yc_boolean /* */
89 static CURL * create_curl_handle(void)
91 CURL *c = curl_easy_init();
93 if (!c) {
94 return 0;
97 curl_easy_setopt(c, CURLOPT_USERAGENT, NCI_USERAGENT);
98 curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
99 curl_easy_setopt(c, CURLOPT_NOPROGRESS, 1L);
100 curl_easy_setopt(c, CURLOPT_MAXREDIRS, 10L);
101 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, curl_to_devnull);
102 curl_easy_setopt(c, CURLOPT_WRITEDATA, (void *) 0);
104 return c;
107 static size_t curl_to_devnull(char *ptr, size_t size, size_t nmemb, void *ctx)
109 UNUSED(ptr);
110 UNUSED(size);
111 UNUSED(nmemb);
112 UNUSED(ctx);
114 return size * nmemb;
117 static size_t curl_to_yajl(char *ptr, size_t size, size_t nmemb, void *ctx)
119 size_t total = size * nmemb;
120 curl_to_yajl_ctx *c = (curl_to_yajl_ctx *) ctx;
121 yajl_status ys;
122 unsigned char *ye = 0;
125 * As part of some security thing I don't give a damn about,
126 * Canvas prepends all their responses with `while(1);' or
127 * something.
129 if (!c->skipped_chars) {
130 while (nmemb &&
131 (*ptr != '[' &&
132 *ptr != '{')) {
133 nmemb--;
134 ptr++;
137 if (nmemb &&
138 (*ptr == '[' ||
139 *ptr == '{')) {
140 c->skipped_chars = 1;
141 } else {
142 return total;
146 ys = yajl_parse(c->yh, (const unsigned char *) ptr, size * nmemb);
148 if (ys == yajl_status_client_canceled ||
149 ys == yajl_status_error) {
150 ye = yajl_get_error(c->yh, 1, (const unsigned char *) ptr,
151 size * nmemb);
152 fprintf(stderr, "yajl: %s\n", ye);
153 yajl_free_error(c->yh, ye);
155 return 0;
158 return total;
161 static size_t extract_next_link(char *buffer, size_t size, size_t nitems,
162 void *userdata)
164 size_t total = size * nitems;
165 char **storage_location = (char **) userdata;
166 char *p = 0;
167 char *lt = 0;
168 char *terminus = buffer + (total - 1);
169 char *dup = 0;
171 for (p = buffer; p < terminus - 13; ++p) {
172 if (*p == '<') {
173 lt = p;
174 } else if (!strncmp(p, ">; rel=\"next\"", 13) &&
175 lt) {
176 if (!(dup = strndup(lt + 1, (p - lt) - 1))) {
177 return 0;
180 free(*storage_location);
181 *storage_location = dup;
185 return total;
188 char * get_auth_token(void)
190 return get_config_file_contents("token");
193 static char * get_config_file_contents(const char *config_file_name)
195 char *xdg_config_home = getenv("XDG_CONFIG_HOME");
196 char *home = getenv("HOME");
197 char *path = 0;
198 FILE *f;
199 char *contents = 0;
200 char *extra_newline = 0;
201 size_t len = 0;
202 size_t j;
203 char buf[LINE_BUF_LEN];
204 char *b = buf;
205 size_t bytes_read = 0;
206 uint_fast8_t eof = 0;
208 if (!xdg_config_home ||
209 !xdg_config_home[0]) {
210 if (!home) {
211 fprintf(stderr, "You don't have a $HOME. I give up.\n");
212 goto cleanup;
215 len = snprintf(0, 0, "%s/.config/nci/%s", home,
216 config_file_name);
218 if (!(path = malloc(len + 1))) {
219 perror(L("malloc"));
220 goto cleanup;
223 sprintf(path, "%s/.config/nci/%s", home, config_file_name);
224 } else {
225 len = snprintf(0, 0, "%s/nci/%s", xdg_config_home,
226 config_file_name);
228 if (!(path = malloc(len + 1))) {
229 perror(L("malloc"));
230 goto cleanup;
233 sprintf(path, "%s/nci/%s", xdg_config_home, config_file_name);
236 if (!(f = fopen(path, "r"))) {
237 perror(L("fopen"));
238 fprintf(stderr, "Could not open %s\n", path);
239 goto cleanup;
242 while (!eof) {
243 if (buf + (LINE_BUF_LEN - 1) <= b) {
244 fprintf(stderr,
245 "File %s is too long - refusing to use it\n",
246 path);
247 goto cleanup;
250 bytes_read = fread(b, 1, (buf + (LINE_BUF_LEN - 1) - b), f);
251 eof = feof(f);
252 b += bytes_read;
255 if (b != buf) {
256 j = (b - buf) - 1;
257 buf[j] = '\0';
259 if (j > 1 &&
260 buf[j - 1] == '/') {
261 buf[j - 1] = '\0';
263 } else {
264 fprintf(stderr, "File %s is empty - it shouldn't be\n", path);
265 goto cleanup;
268 if (!(contents = malloc((b - buf) + 1))) {
269 perror(L("malloc"));
270 goto cleanup;
273 sprintf(contents, "%s", buf);
275 if ((extra_newline = strchr(contents, '\n'))) {
276 *extra_newline = '\0';
279 cleanup:
281 if (f) {
282 fclose(f);
285 free(path);
287 return contents;
290 char * get_url_base(void)
292 return get_config_file_contents("site");
295 static int ie_yc_number(void *ctx, const char *val, size_t len)
297 return ie_yc_string(ctx, (const unsigned char *) val, len);
300 static int ie_yc_map_key(void *ctx, const unsigned char *key, size_t len)
302 ie_y_ctx *c = (ie_y_ctx *) ctx;
304 c->saw_id_key = !strncmp((const char *) key, "id", 2) &&
305 len == 2;
306 c->saw_message_key = !strncmp((const char *) key, "message", 7);
308 return 1;
311 static int ie_yc_string(void *ctx, const unsigned char *val, size_t len)
313 ie_y_ctx *c = (ie_y_ctx *) ctx;
314 char *dup = 0;
316 if (!c->saw_id_key &&
317 !c->saw_message_key) {
318 return 1;
321 if (c->saw_id_key &&
322 !c->id_str) {
323 return 1;
326 if (!(dup = strndup((const char *) val, len))) {
327 perror(L("strndup"));
329 return 0;
332 if (c->saw_id_key) {
333 free(*c->id_str);
334 *c->id_str = dup;
335 } else {
336 c->error_message = dup;
339 return 1;
342 int key_value_extract(const char *original_path, const char *auth_token, const
343 char **field_names, char *enclosing_id, map *m)
345 int ret = 0;
346 CURL *c = 0;
347 yajl_handle y = { 0 };
348 kve_y_ctx ct = { 0 };
349 size_t j = 0;
350 curl_to_yajl_ctx ctyc = { 0 };
351 CURLcode curl_ret;
352 char ce[CURL_ERROR_SIZE];
353 long http;
354 char *free_after_next_perform = 0;
355 char *next_uri = 0;
356 char *auth_header = 0;
357 size_t len = 0;
358 struct curl_slist *custom_headers = 0;
359 const char *path = original_path;
361 ce[0] = '\0';
363 if (!(c = create_curl_handle())) {
364 ret = errno = ENOMEM;
365 perror("create_curl_handle");
366 goto cleanup;
369 len = snprintf(0, 0, "Authorization: Bearer %s", auth_token);
371 if (!(auth_header = malloc(len + 1))) {
372 ret = errno;
373 perror(L("malloc"));
374 goto cleanup;
377 sprintf(auth_header, "Authorization: Bearer %s", auth_token);
379 if (!(custom_headers = curl_slist_append(custom_headers,
380 auth_header))) {
381 if (!errno) {
382 errno = ENOMEM;
385 perror(L("curl_slist_append"));
386 goto cleanup;
389 curl_easy_setopt(c, CURLOPT_HTTPHEADER, custom_headers);
390 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, curl_to_yajl);
391 curl_easy_setopt(c, CURLOPT_WRITEDATA, (void *) &ctyc);
392 curl_easy_setopt(c, CURLOPT_ERRORBUFFER, &ce);
393 curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, extract_next_link);
394 curl_easy_setopt(c, CURLOPT_HEADERDATA, (void *) &next_uri);
396 do {
397 next_uri = 0;
398 ct = (kve_y_ctx) { .m = m, .field_names = field_names,
399 .enclosing_id = enclosing_id };
401 if (!(y = yajl_alloc(&kve_callbacks, 0, (void *) &ct))) {
402 ret = errno = ENOMEM;
403 perror(L("yajl_alloc()"));
404 goto cleanup;
407 ctyc = (curl_to_yajl_ctx) { .yh = y };
408 curl_easy_setopt(c, CURLOPT_URL, path);
409 curl_ret = curl_easy_perform(c);
410 curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &http);
411 yajl_complete_parse(y);
413 if (y) {
414 yajl_free(y);
415 y = 0;
418 free(free_after_next_perform);
419 free(ct.field0);
420 ct.field0 = 0;
422 if (ct.otherfields) {
423 for (j = 1; field_names[j]; ++j) {
424 free(ct.otherfields[j - 1]);
428 free(ct.otherfields);
429 ct.otherfields = 0;
431 if (curl_ret != CURLE_OK ||
432 http / 100 != 2) {
433 if (ct.error_message) {
434 fprintf(stderr, "Error: %s\n",
435 ct.error_message);
436 ret = EACCES;
437 } else {
438 fprintf(stderr, "%s: %s (http %ld)\n\n%s\n", L(
439 "libcurl"), curl_easy_strerror(
440 curl_ret), http,
441 ce);
442 ret = EINVAL;
445 break;
448 path = next_uri;
449 free_after_next_perform = next_uri;
450 } while (next_uri);
452 cleanup:
453 free(ct.error_message);
455 if (c) {
456 curl_easy_cleanup(c);
459 curl_slist_free_all(custom_headers);
460 free(auth_header);
462 if (y) {
463 yajl_free(y);
466 free(next_uri);
468 return ret;
471 static int kve_yc_end_map(void *ctx)
473 kve_y_ctx *c = (kve_y_ctx *) ctx;
474 int mret = 0;
476 if (c->exhausted_enclosing_id) {
477 return 1;
480 if (c->depth == c->enclosing_id_depth) {
481 c->exhausted_enclosing_id = 1;
484 if (c->depth-- != c->enclosing_id_depth + 1) {
485 return 1;
488 if (!c->field0) {
489 return 1;
492 mret = map_add(c->m, c->field0, c->otherfields);
493 c->field0 = 0;
494 c->otherfields = 0;
496 return mret >= 0;
499 static int kve_handle(kve_y_ctx *c, const char *s, size_t l)
501 int truncated_l = (l > 70) ? 70 : l;
502 char *dup;
503 size_t j = 0;
505 if (c->saw_id_key &&
506 c->enclosing_id &&
507 !c->enclosing_id_depth &&
508 l == strlen(c->enclosing_id) &&
509 !strncmp(s, c->enclosing_id, l)) {
510 c->enclosing_id_depth = c->depth;
512 return 0;
515 if (c->enclosing_id &&
516 (c->exhausted_enclosing_id ||
517 !c->enclosing_id_depth)) {
518 return 0;
521 if (c->idx_of_next_entry < 0 &&
522 !c->saw_message_key) {
523 return 0;
526 dup = malloc(truncated_l + 1);
528 if (!dup) {
529 perror(L("malloc"));
531 return -1;
534 snprintf(dup, truncated_l + 1, "%s", s);
536 if (c->saw_message_key) {
537 c->error_message = dup;
539 return 0;
542 switch (c->idx_of_next_entry) {
543 case 0:
544 c->field0 = dup;
545 break;
546 default:
548 if (!c->otherfields) {
549 if (!c->m->num_values_per_entry) {
550 for (j = 0; c->field_names[j]; ++j) {
553 c->m->num_values_per_entry = j - 1;
556 if (!(c->otherfields = calloc(
557 c->m->num_values_per_entry,
558 sizeof *c->field_names))) {
559 return -1;
563 c->otherfields[c->idx_of_next_entry - 1] = dup;
564 break;
567 c->idx_of_next_entry = -1;
569 return 0;
572 static int kve_yc_boolean(void *ctx, int val)
574 if (val) {
575 if (kve_handle((kve_y_ctx *) ctx, "true", 4)) {
576 return 0;
578 } else {
579 if (kve_handle((kve_y_ctx *) ctx, "false", 5)) {
580 return 0;
584 return 1;
587 int kve_yc_map_key(void *ctx, const unsigned char *key, size_t len)
589 kve_y_ctx *c = (kve_y_ctx *) ctx;
590 const char *s = (const char *) key;
591 size_t j;
593 c->saw_message_key = (!(strncmp(s, "message", 7)) &&
594 c->depth == 2);
595 c->saw_id_key = c->enclosing_id &&
596 len == 2 &&
597 (!(strncmp(s, "id", 2))) &&
598 c->depth == 1;
600 if (!strncmp(s, c->field_names[0], len)) {
601 c->idx_of_next_entry = 0;
603 return 1;
604 } else {
605 for (j = 1; c->field_names[j]; ++j) {
606 if (!strncmp((const char *) key, c->field_names[j],
607 len)) {
608 c->idx_of_next_entry = j;
610 return 1;
615 c->idx_of_next_entry = -1;
617 return 1;
620 static int kve_yc_number(void *ctx, const char *nval, size_t len)
622 if (kve_handle((kve_y_ctx *) ctx, nval, len)) {
623 return 0;
626 return 1;
629 static int kve_yc_start_map(void *ctx)
631 kve_y_ctx *c = (kve_y_ctx *) ctx;
633 c->depth++;
635 return 1;
638 static int kve_yc_string(void *ctx, const unsigned char *val, size_t len)
640 kve_y_ctx *c = (kve_y_ctx *) ctx;
641 const char *s = (const char *) val;
643 if (kve_handle(c, s, len)) {
644 return 0;
647 return 1;
650 int make_gse_post(grading_standard_entry *cutoffs, size_t cutoffs_num, struct
651 curl_httppost **out_post)
653 struct curl_httppost *post = 0;
654 struct curl_httppost *postend = 0;
655 int ret = EINVAL;
656 size_t j = 0;
658 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME, "title",
659 CURLFORM_PTRCONTENTS, "Grading Scheme",
660 CURLFORM_END)) {
661 ret = errno ? errno : ENOMEM;
662 errno = ret;
663 perror(L("curl_formadd"));
664 goto cleanup;
667 for (j = 0; j < cutoffs_num; ++j) {
668 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
669 "grading_scheme_entry[][name]",
670 CURLFORM_PTRCONTENTS,
671 cutoffs[j].name, CURLFORM_END)) {
672 ret = errno ? errno : ENOMEM;
673 errno = ret;
674 perror(L("curl_formadd"));
675 goto cleanup;
678 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
679 "grading_scheme_entry[][value]",
680 CURLFORM_PTRCONTENTS,
681 cutoffs[j].value, CURLFORM_END)) {
682 ret = errno ? errno : ENOMEM;
683 errno = ret;
684 perror(L("curl_formadd"));
685 goto cleanup;
689 ret = 0;
690 cleanup:
692 if (ret) {
693 curl_formfree(post);
694 post = 0;
697 *out_post = post;
699 return ret;
702 int make_course_post(const char *hfg, const char *aagw, int
703 disable_grading_standard, const char *start_date, const
704 char *end_date, const
705 char *gse_id, struct curl_httppost **out_post)
707 struct curl_httppost *post = 0;
708 struct curl_httppost *postend = 0;
709 int ret = EINVAL;
711 if (hfg) {
712 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
713 "course[hide_final_grades]",
714 CURLFORM_PTRCONTENTS, hfg,
715 CURLFORM_END)) {
716 ret = errno ? errno : ENOMEM;
717 errno = ret;
718 perror(L("curl_formadd"));
719 goto cleanup;
723 if (aagw) {
724 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
725 "course[apply_assignment_group_weights]",
726 CURLFORM_PTRCONTENTS,
727 aagw, CURLFORM_END)) {
728 ret = errno ? errno : ENOMEM;
729 errno = ret;
730 perror(L("curl_formadd"));
731 goto cleanup;
735 if (disable_grading_standard) {
736 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
737 "course[grading_standard_id]",
738 CURLFORM_PTRCONTENTS, "",
739 CURLFORM_END)) {
740 ret = errno ? errno : ENOMEM;
741 errno = ret;
742 perror(L("curl_formadd"));
743 goto cleanup;
747 if (start_date) {
748 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
749 "course[start_at]", CURLFORM_PTRCONTENTS,
750 start_date,
751 CURLFORM_END)) {
752 ret = errno ? errno : ENOMEM;
753 errno = ret;
754 perror(L("curl_formadd"));
755 goto cleanup;
759 if (end_date) {
760 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
761 "course[end_at]", CURLFORM_PTRCONTENTS,
762 end_date,
763 CURLFORM_END)) {
764 ret = errno ? errno : ENOMEM;
765 errno = ret;
766 perror(L("curl_formadd"));
767 goto cleanup;
771 if (gse_id) {
772 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
773 "course[grading_standard_id]",
774 CURLFORM_PTRCONTENTS, gse_id,
775 CURLFORM_END)) {
776 ret = errno ? errno : ENOMEM;
777 errno = ret;
778 perror(L("curl_formadd"));
779 goto cleanup;
783 ret = 0;
784 cleanup:
786 if (ret) {
787 curl_formfree(post);
788 post = 0;
791 *out_post = post;
793 return ret;
796 struct curl_httppost * make_agroup_post(const char *name, const char *weight,
797 const char *drop_lowest, const
798 char *drop_highest)
800 struct curl_httppost *post = 0;
801 struct curl_httppost *postend = 0;
803 if (name) {
804 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME, "name",
805 CURLFORM_PTRCONTENTS, name, CURLFORM_END)) {
806 errno = errno ? errno : ENOMEM;
807 perror(L("curl_formadd"));
808 goto error;
812 if (weight) {
813 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
814 "group_weight", CURLFORM_PTRCONTENTS, weight,
815 CURLFORM_END)) {
816 errno = errno ? errno : ENOMEM;
817 perror(L("curl_formadd"));
818 goto error;
822 if (drop_lowest) {
823 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
824 "rules[drop_lowest]", CURLFORM_PTRCONTENTS,
825 drop_lowest,
826 CURLFORM_END)) {
827 errno = errno ? errno : ENOMEM;
828 perror(L("curl_formadd"));
829 goto error;
833 if (drop_highest) {
834 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
835 "rules[drop_highest]", CURLFORM_PTRCONTENTS,
836 drop_highest,
837 CURLFORM_END)) {
838 errno = errno ? errno : ENOMEM;
839 perror(L("curl_formadd"));
840 goto error;
844 return post;
845 error:
846 curl_formfree(post);
848 return 0;
851 struct curl_httppost * make_assignment_post(const char *name, const
852 char *max_points, const
853 char *due_date, const
854 char *group_id)
856 struct curl_httppost *post = 0;
857 struct curl_httppost *postend = 0;
859 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
860 "assignment[submission_types][]", CURLFORM_PTRCONTENTS,
861 "on_paper",
862 CURLFORM_END)) {
863 errno = errno ? errno : ENOMEM;
864 perror(L("curl_formadd"));
865 goto error;
868 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
869 "assignment[notify_of_update]", CURLFORM_PTRCONTENTS,
870 "false",
871 CURLFORM_END)) {
872 errno = errno ? errno : ENOMEM;
873 perror(L("curl_formadd"));
874 goto error;
877 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
878 "assignment[grading_type]", CURLFORM_PTRCONTENTS,
879 "points",
880 CURLFORM_END)) {
881 errno = errno ? errno : ENOMEM;
882 perror(L("curl_formadd"));
883 goto error;
886 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
887 "assignment[published]", CURLFORM_PTRCONTENTS, "true",
888 CURLFORM_END)) {
889 errno = errno ? errno : ENOMEM;
890 perror(L("curl_formadd"));
891 goto error;
894 if (name) {
895 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
896 "assignment[name]", CURLFORM_PTRCONTENTS, name,
897 CURLFORM_END)) {
898 errno = errno ? errno : ENOMEM;
899 perror(L("curl_formadd"));
900 goto error;
904 if (max_points) {
905 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
906 "assignment[points_possible]",
907 CURLFORM_PTRCONTENTS, max_points,
908 CURLFORM_END)) {
909 errno = errno ? errno : ENOMEM;
910 perror(L("curl_formadd"));
911 goto error;
915 if (due_date) {
916 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
917 "assignment[due_at]", CURLFORM_PTRCONTENTS,
918 due_date,
919 CURLFORM_END)) {
920 errno = errno ? errno : ENOMEM;
921 perror(L("curl_formadd"));
922 goto error;
926 if (group_id) {
927 if (curl_formadd(&post, &postend, CURLFORM_PTRNAME,
928 "assignment[assignment_group_id]",
929 CURLFORM_PTRCONTENTS,
930 group_id, CURLFORM_END)) {
931 errno = errno ? errno : ENOMEM;
932 perror(L("curl_formadd"));
933 goto error;
937 return post;
938 error:
939 curl_formfree(post);
941 return 0;
944 struct curl_httppost * make_update_grade_post(char ***csv, size_t col, size_t
945 rows)
947 struct curl_httppost *post = 0;
948 struct curl_httppost *postend = 0;
949 size_t j = 0;
950 char *param_name = 0;
951 size_t param_name_len = 0;
952 size_t len = 0;
954 for (j = 1; j < rows; ++j) {
955 if (!csv[j] ||
956 !csv[j][col] ||
957 !csv[j][col][0] ||
958 !csv[j][1] ||
959 !csv[j][1][0]) {
960 continue;
963 if (!strcasecmp(csv[j][col], "ex")) {
964 len = snprintf(0, 0, "grade_data[%s][excuse]",
965 csv[j][1]);
967 if (len > param_name_len) {
968 param_name_len = len + 1;
970 if (!(param_name = realloc(param_name,
971 param_name_len))) {
972 perror(L("realloc"));
973 goto error;
977 sprintf(param_name, "grade_data[%s][excuse]",
978 csv[j][1]);
980 if (curl_formadd(&post, &postend, CURLFORM_COPYNAME,
981 param_name, CURLFORM_PTRCONTENTS,
982 "true",
983 CURLFORM_END)) {
984 errno = errno ? errno : ENOMEM;
985 perror(L("curl_formadd"));
986 goto error;
988 } else {
989 len = snprintf(0, 0, "grade_data[%s][posted_grade]",
990 csv[j][1]);
992 if (len > param_name_len) {
993 param_name_len = len + 1;
995 if (!(param_name = realloc(param_name,
996 param_name_len))) {
997 perror(L("realloc"));
998 goto error;
1002 sprintf(param_name, "grade_data[%s][posted_grade]",
1003 csv[j][1]);
1005 if (curl_formadd(&post, &postend, CURLFORM_COPYNAME,
1006 param_name, CURLFORM_PTRCONTENTS,
1007 csv[j][col],
1008 CURLFORM_END)) {
1009 errno = errno ? errno : ENOMEM;
1010 perror(L("curl_formadd"));
1011 goto error;
1016 goto done;
1017 error:
1018 curl_formfree(post);
1019 post = 0;
1020 done:
1021 free(param_name);
1023 return post;
1026 int map_add(map *m, char *k, char **vs)
1028 size_t j = 0;
1029 size_t l = 0;
1030 entry *e = 0;
1031 void *newmem = 0;
1032 size_t hash = map_hash(k);
1034 if (!k ||
1035 !m) {
1036 errno = EINVAL;
1038 return -1;
1041 for (j = 0; j < m->es[hash]; ++j) {
1042 e = &m->e[hash][j];
1044 if (!(strcmp(k, e->k))) {
1045 free(k);
1047 if (e->vs) {
1048 for (l = 0; l < m->num_values_per_entry; ++l) {
1049 free(e->vs[l]);
1053 free(e->vs);
1054 e->vs = vs;
1056 return 0;
1060 if (!(newmem = realloc(m->e[hash], (m->es[hash] + 1) *
1061 sizeof *m->e[hash]))) {
1062 goto malloc_err;
1065 m->e[hash] = newmem;
1066 m->e[hash][m->es[hash]] = (entry) { .k = k, .vs = vs };
1067 m->es[hash]++;
1069 return 0;
1070 malloc_err:
1071 errno = ENOMEM;
1073 return -1;
1076 void map_clean(map *m)
1078 size_t j = 0;
1079 size_t k = 0;
1080 size_t l = 0;
1081 entry *e = 0;
1083 if (!m) {
1084 return;
1087 for (j = 0; j < BUCKET_NUM; ++j) {
1088 for (k = 0; k < m->es[j]; ++k) {
1089 e = &m->e[j][k];
1090 free(e->k);
1092 if (e->vs) {
1093 for (l = 0; l < m->num_values_per_entry; ++l) {
1094 free(e->vs[l]);
1098 free(e->vs);
1101 free(m->e[j]);
1104 *m = (map) { 0 };
1107 static int map_cmp_key(const void *a, const void *b)
1109 const char *s = *((const char **) a);
1110 const char *t = *((const char **) b);
1111 long long m = strtoll(s, 0, 0);
1112 long long n = strtoll(t, 0, 0);
1114 return (m < n) ? -1 : ((m > n) ? 1 : strcmp(s, t));
1117 char ** map_get(map *m, char *k)
1119 size_t j = 0;
1120 entry *e = 0;
1121 size_t hash = map_hash(k);
1123 if (!m ||
1124 !k) {
1125 return 0;
1128 for (j = 0; j < m->es[hash]; ++j) {
1129 e = &m->e[hash][j];
1131 if (!(strcmp(k, e->k))) {
1132 return e->vs;
1136 return 0;
1139 void map_get_keys(map *m, char ***out_k, size_t *out_num)
1141 size_t num_keys = 0;
1142 size_t j = 0;
1143 size_t k = 0;
1144 size_t l = 0;
1146 for (j = 0; j < BUCKET_NUM; ++j) {
1147 num_keys += m->es[j];
1150 if (!(*out_k = malloc((sizeof **out_k) * num_keys))) {
1151 *out_num = 0;
1153 return;
1156 for (j = 0; j < BUCKET_NUM; ++j) {
1157 for (k = 0; k < m->es[j]; ++k) {
1158 (*out_k)[l++] = m->e[j][k].k;
1162 qsort(*out_k, num_keys, sizeof **out_k, map_cmp_key);
1163 *out_num = num_keys;
1166 static size_t map_hash(const char *s)
1168 const char *p = 0;
1169 uint_fast8_t j;
1170 size_t hash = 5381;
1172 if (!s) {
1173 return 0;
1176 for (p = s, j = 0; *p &&
1177 j < 15; ++p, ++j) {
1178 hash = hash * 33 ^ *p;
1181 return hash % BUCKET_NUM;
1184 void print_esc_0x22(const char *s)
1186 const char *p = s;
1188 /* Quote as per RFC 4180 */
1189 while (*p) {
1190 if (*p == '"') {
1191 putchar('"');
1192 putchar('"');
1193 } else {
1194 putchar(*p);
1197 ++p;
1201 static int process_char_csv(int c, csv_parse_state *s, char **cell,
1202 size_t *cell_sz, size_t *cell_pos, char ****csv,
1203 char *reading_header,
1204 size_t *record_len, size_t *y, size_t *x)
1206 void *newmem;
1208 if (*cell_pos + 1 >= *cell_sz) {
1209 if (!(newmem = realloc(*cell, (*cell_sz + 64) *
1210 sizeof **cell))) {
1211 perror(L("realloc"));
1212 *s = CPS_INVALID;
1214 return -1;
1217 *cell = newmem;
1218 *cell_sz += 64;
1221 switch (*s) {
1222 case CPS_START_OF_CELL:
1224 if (c == EOF) {
1225 *s = CPS_END_OF_INPUT;
1226 } else if (c == '"') {
1227 *s = CPS_IN_QUOTED_CELL;
1228 } else if (c == ',') {
1229 *s = CPS_JUST_ENDED_CELL;
1230 (*cell)[*cell_pos] = '\0';
1231 } else if (c == '\n') {
1232 *s = CPS_JUST_ENDED_LINE;
1233 (*cell)[*cell_pos] = '\0';
1234 } else {
1235 (*cell)[(*cell_pos)++] = (char) c;
1236 *s = CPS_IN_NONQUOTED_CELL;
1239 break;
1240 case CPS_IN_QUOTED_CELL:
1242 if (c == EOF) {
1243 *s = CPS_INVALID;
1244 } else if (c == '"') {
1245 *s = CPS_JUST_SAW_0x22;
1246 } else {
1247 (*cell)[(*cell_pos)++] = (char) c;
1250 break;
1251 case CPS_IN_NONQUOTED_CELL:
1253 if (c == EOF) {
1254 *s = CPS_END_OF_INPUT;
1255 (*cell)[*cell_pos] = '\0';
1256 } else if (c == ',') {
1257 *s = CPS_JUST_ENDED_CELL;
1258 (*cell)[*cell_pos] = '\0';
1259 } else if (c == '\n') {
1260 *s = CPS_JUST_ENDED_LINE;
1261 (*cell)[*cell_pos] = '\0';
1262 } else if (c == '"') {
1263 *s = CPS_INVALID;
1264 } else {
1265 (*cell)[(*cell_pos)++] = (char) c;
1268 break;
1269 case CPS_JUST_SAW_0x22:
1271 if (c == EOF) {
1272 *s = CPS_END_OF_INPUT;
1273 } else if (c == '"') {
1274 (*cell)[(*cell_pos)++] = '"';
1275 *s = CPS_IN_QUOTED_CELL;
1276 } else if (c == ',') {
1277 *s = CPS_JUST_ENDED_CELL;
1278 (*cell)[*cell_pos] = '\0';
1279 } else if (c == '\n') {
1280 *s = CPS_JUST_ENDED_LINE;
1281 (*cell)[*cell_pos] = '\0';
1282 } else {
1283 *s = CPS_INVALID;
1286 break;
1287 case CPS_JUST_ENDED_LINE:
1288 case CPS_JUST_ENDED_CELL:
1290 if (c == EOF) {
1291 *s = CPS_END_OF_INPUT;
1292 } else if (c == '\n') {
1293 *s = CPS_JUST_ENDED_LINE;
1294 (*cell)[*cell_pos] = '\0';
1295 } else if (c == ',') {
1296 *s = CPS_JUST_ENDED_CELL;
1297 (*cell)[*cell_pos] = '\0';
1298 } else if (c == '"') {
1299 *s = CPS_IN_QUOTED_CELL;
1300 } else {
1301 (*cell)[(*cell_pos)++] = (char) c;
1302 *s = CPS_IN_NONQUOTED_CELL;
1305 break;
1306 case CPS_INVALID:
1307 case CPS_END_OF_INPUT:
1308 break;
1311 if (*reading_header &&
1312 (*s == CPS_JUST_ENDED_CELL ||
1313 *s == CPS_JUST_ENDED_LINE ||
1314 *s == CPS_END_OF_INPUT)) {
1315 (*record_len)++;
1317 if (*reading_header) {
1318 if (!(newmem = realloc((*csv)[0], (*record_len + 1) *
1319 sizeof ***csv))) {
1320 perror(L("realloc"));
1322 return -1;
1325 (*csv)[0] = newmem;
1329 if (*s == CPS_JUST_ENDED_CELL ||
1330 *s == CPS_JUST_ENDED_LINE ||
1331 *s == CPS_END_OF_INPUT) {
1332 if (*x >= *record_len) {
1333 *s = CPS_INVALID;
1335 return -1;
1338 (*csv)[*y][*x] = *cell;
1340 if (!(*cell = malloc(1 * sizeof **cell))) {
1341 perror(L("malloc"));
1343 return -1;
1346 *cell_sz = 1;
1347 *cell_pos = 0;
1350 if (*s == CPS_JUST_ENDED_LINE) {
1351 (*y)++;
1352 *x = 0;
1353 *reading_header = 0;
1355 if (!(newmem = realloc(*csv, (*y + 1) * sizeof *csv))) {
1356 perror(L("realloc"));
1358 return -1;
1361 *csv = newmem;
1363 if (!((*csv)[*y] = calloc(*record_len, sizeof ***csv))) {
1364 perror(L("calloc"));
1366 return -1;
1370 if (*s == CPS_JUST_ENDED_CELL) {
1371 (*x)++;
1374 return 0;
1377 int read_csv(FILE *f, char ****out_csv, size_t *out_rows, size_t *out_cols)
1379 size_t fread_ret = 0;
1380 csv_parse_state s = CPS_START_OF_CELL;
1381 char *cell = 0;
1382 size_t cell_sz = 1;
1383 size_t cell_pos = 0;
1384 char ***csv = 0;
1385 char reading_header = 1;
1386 size_t record_len = 0;
1387 size_t y = 0;
1388 size_t x = 0;
1389 size_t j;
1390 size_t k;
1391 char buf[READ_BUF_LEN];
1392 const char *buf_end = 0;
1393 const char *p = 0;
1395 if (!(cell = malloc(cell_sz * sizeof *cell))) {
1396 perror(L("malloc"));
1398 return -1;
1401 if (!(csv = malloc(1 * sizeof *csv))) {
1402 perror(L("malloc"));
1404 return -1;
1407 if (!(csv[0] = malloc(1 * sizeof **csv))) {
1408 perror(L("malloc"));
1410 return -1;
1413 csv[0][0] = 0;
1415 while (!feof(f) &&
1416 !ferror(f)) {
1417 fread_ret = fread(buf, sizeof *buf, READ_BUF_LEN, f);
1419 if (!fread_ret) {
1420 continue;
1423 buf_end = buf + (fread_ret - 1);
1424 p = buf;
1426 while (p <= buf_end) {
1427 if (process_char_csv(*p, &s, &cell, &cell_sz, &cell_pos,
1428 &csv, &reading_header, &record_len,
1429 &y, &x) < 0) {
1430 goto error;
1433 p++;
1437 if (s == CPS_INVALID) {
1438 goto error;
1441 y++;
1442 free(cell);
1443 *out_csv = csv;
1444 *out_rows = y;
1445 *out_cols = record_len;
1447 return 0;
1448 error:
1450 for (j = 0; j < y; ++j) {
1451 for (k = 0; k < x; ++k) {
1452 free(csv[j][k]);
1455 free(csv[j]);
1458 free(csv);
1459 free(cell);
1461 return -1;
1464 int send_and_id_scan(const char *uri, const char *auth_token, struct
1465 curl_httppost *post, const char *method, char **id)
1467 int ret = 0;
1468 CURL *c = 0;
1469 yajl_handle y = { 0 };
1470 ie_y_ctx ct = { .id_str = id };
1471 curl_to_yajl_ctx ctyc = { 0 };
1472 CURLcode curl_ret;
1473 char ce[CURL_ERROR_SIZE];
1474 long http;
1475 char *auth_header = 0;
1476 size_t len = 0;
1477 struct curl_slist *custom_headers = 0;
1479 ce[0] = '\0';
1481 if (!(c = create_curl_handle())) {
1482 ret = errno = ENOMEM;
1483 perror("create_curl_handle");
1484 goto cleanup;
1487 len = snprintf(0, 0, "Authorization: Bearer %s", auth_token);
1489 if (!(auth_header = malloc(len + 1))) {
1490 ret = errno;
1491 perror(L("malloc"));
1492 goto cleanup;
1495 sprintf(auth_header, "Authorization: Bearer %s", auth_token);
1497 if (!(custom_headers = curl_slist_append(custom_headers,
1498 auth_header))) {
1499 if (!errno) {
1500 errno = ENOMEM;
1503 perror(L("curl_slist_append"));
1504 ret = errno;
1505 goto cleanup;
1508 if (!(y = yajl_alloc(&ie_callbacks, 0, (void *) &ct))) {
1509 ret = errno = ENOMEM;
1510 perror(L("yajl_alloc()"));
1511 goto cleanup;
1514 ctyc = (curl_to_yajl_ctx) { .yh = y };
1515 curl_easy_setopt(c, CURLOPT_HTTPHEADER, custom_headers);
1516 curl_easy_setopt(c, CURLOPT_HTTPPOST, post);
1517 curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, method);
1518 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, curl_to_yajl);
1519 curl_easy_setopt(c, CURLOPT_WRITEDATA, (void *) &ctyc);
1520 curl_easy_setopt(c, CURLOPT_ERRORBUFFER, &ce);
1521 curl_easy_setopt(c, CURLOPT_URL, uri);
1522 curl_ret = curl_easy_perform(c);
1523 curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &http);
1524 yajl_complete_parse(y);
1526 if (curl_ret != CURLE_OK ||
1527 http / 100 != 2) {
1528 if (ct.error_message) {
1529 fprintf(stderr, L("Error: %s\n"), ct.error_message);
1530 ret = EACCES;
1531 } else {
1532 fprintf(stderr, "%s: %s (http %ld)\n\n%s\n", L(
1533 "libcurl"), curl_easy_strerror(
1534 curl_ret), http, ce);
1535 ret = EINVAL;
1539 cleanup:
1540 free(ct.error_message);
1542 if (c) {
1543 curl_easy_cleanup(c);
1546 curl_slist_free_all(custom_headers);
1547 free(auth_header);
1549 if (y) {
1550 yajl_free(y);
1553 return ret;