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
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.
25 #include <sys/types.h>
28 #include <curl/curl.h>
29 #include <yajl_parse.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)
43 CPS_IN_NONQUOTED_CELL
,
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
,
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
= {
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
= {
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
= {
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
, /* */
96 create_curl_handle(void)
98 CURL
*c
= curl_easy_init();
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);
115 curl_to_devnull(char *ptr
, size_t size
, size_t nmemb
, void *ctx
)
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
;
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
138 if (!c
->skipped_chars
) {
149 c
->skipped_chars
= 1;
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
,
161 fprintf(stderr
, "yajl: %s\n", ye
);
162 yajl_free_error(c
->yh
, ye
);
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
;
177 char *terminus
= buffer
+ (total
- 1);
180 for (p
= buffer
; p
< terminus
- 13; ++p
) {
183 } else if (!strncmp(p
, ">; rel=\"next\"", 13) &&
185 if (!(dup
= strndup(lt
+ 1, (p
- lt
) - 1))) {
189 free(*storage_location
);
190 *storage_location
= dup
;
200 return get_config_file_contents("token");
204 get_config_file_contents(const char *config_file_name
)
206 char *xdg_config_home
= getenv("XDG_CONFIG_HOME");
207 char *home
= getenv("HOME");
211 char *extra_newline
= 0;
214 char buf
[LINE_BUF_LEN
];
216 size_t bytes_read
= 0;
217 uint_fast8_t eof
= 0;
219 if (!xdg_config_home
||
220 !xdg_config_home
[0]) {
222 fprintf(stderr
, "You don't have a $HOME. I give up.\n");
226 len
= snprintf(0, 0, "%s/.config/nci/%s", home
,
235 if (!(path
= malloc(len
+ 1))) {
240 sprintf(path
, "%s/.config/nci/%s", home
, config_file_name
);
242 len
= snprintf(0, 0, "%s/nci/%s", xdg_config_home
,
251 if (!(path
= malloc(len
+ 1))) {
256 sprintf(path
, "%s/nci/%s", xdg_config_home
, config_file_name
);
259 if (!(f
= fopen(path
, "r"))) {
261 fprintf(stderr
, "Could not open %s\n", path
);
266 if (buf
+ (LINE_BUF_LEN
- 1) <= b
) {
268 "File %s is too long - refusing to use it\n",
273 bytes_read
= fread(b
, 1, (buf
+ (LINE_BUF_LEN
- 1) - b
), f
);
287 fprintf(stderr
, "File %s is empty - it shouldn't be\n", path
);
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))) {
300 sprintf(contents
, "%s", buf
);
302 if ((extra_newline
= strchr(contents
, '\n'))) {
303 *extra_newline
= '\0';
320 return get_config_file_contents("site");
324 ie_yc_number(void *ctx
, const char *val
, size_t len
)
326 return ie_yc_string(ctx
, (const unsigned char *) val
, len
);
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) &&
336 c
->saw_message_key
= !strncmp((const char *) key
, "message", 7);
342 ie_yc_string(void *ctx
, const unsigned char *val
, size_t len
)
344 ie_y_ctx
*c
= (ie_y_ctx
*) ctx
;
347 if (!c
->saw_id_key
&&
348 !c
->saw_message_key
) {
357 if (!(dup
= strndup((const char *) val
, len
))) {
358 perror(L("strndup"));
367 c
->error_message
= dup
;
374 key_value_extract(const char *original_path
, const char *auth_token
, const
375 char **field_names
, char *enclosing_id
, map
*m
)
379 yajl_handle y
= { 0 };
380 kve_y_ctx ct
= { 0 };
382 curl_to_yajl_ctx ctyc
= { 0 };
384 char ce
[CURL_ERROR_SIZE
];
386 char *free_after_next_perform
= 0;
388 char *auth_header
= 0;
390 struct curl_slist
*custom_headers
= 0;
391 const char *path
= original_path
;
395 if (!(c
= create_curl_handle())) {
396 ret
= errno
= ENOMEM
;
397 perror(L("create_curl_handle"));
401 len
= snprintf(0, 0, "Authorization: Bearer %s", auth_token
);
404 ret
= errno
= EOVERFLOW
;
409 if (!(auth_header
= malloc(len
+ 1))) {
415 sprintf(auth_header
, "Authorization: Bearer %s", auth_token
);
417 if (!(custom_headers
= curl_slist_append(custom_headers
,
423 perror(L("curl_slist_append"));
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
);
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"));
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
);
456 free(free_after_next_perform
);
460 if (ct
.otherfields
) {
461 for (j
= 1; field_names
[j
]; ++j
) {
462 free(ct
.otherfields
[j
- 1]);
466 free(ct
.otherfields
);
469 if (curl_ret
!= CURLE_OK
||
471 if (ct
.error_message
) {
472 fprintf(stderr
, L("Error: HTTP %ld: %s\n"),
473 http
, ct
.error_message
);
476 fprintf(stderr
, "%s: %s (http %ld)\n\n%s\n", L(
477 "libcurl"), curl_easy_strerror(
487 free_after_next_perform
= next_uri
;
491 free(ct
.error_message
);
494 curl_easy_cleanup(c
);
497 curl_slist_free_all(custom_headers
);
510 kve_yc_end_map(void *ctx
)
512 kve_y_ctx
*c
= (kve_y_ctx
*) ctx
;
515 if (c
->exhausted_enclosing_id
) {
519 if (c
->depth
== c
->enclosing_id_depth
) {
520 c
->exhausted_enclosing_id
= 1;
523 if (c
->depth
-- != c
->enclosing_id_depth
+ 1) {
531 mret
= map_add(c
->m
, c
->field0
, c
->otherfields
);
539 kve_handle(kve_y_ctx
*c
, const char *s
, size_t l
)
541 int truncated_l
= (l
> 70) ? 70 : l
;
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
;
555 if (c
->enclosing_id
&&
556 (c
->exhausted_enclosing_id
||
557 !c
->enclosing_id_depth
)) {
561 if (c
->idx_of_next_entry
< 0 &&
562 !c
->saw_message_key
) {
566 /* Need not check for overflow; truncated_l <= 70 */
567 dup
= malloc(truncated_l
+ 1);
575 snprintf(dup
, truncated_l
+ 1, "%s", s
);
577 if (c
->saw_message_key
) {
578 c
->error_message
= dup
;
583 switch (c
->idx_of_next_entry
) {
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
))) {
604 c
->otherfields
[c
->idx_of_next_entry
- 1] = dup
;
608 c
->idx_of_next_entry
= -1;
614 kve_yc_boolean(void *ctx
, int val
)
617 if (kve_handle((kve_y_ctx
*) ctx
, "true", 4)) {
621 if (kve_handle((kve_y_ctx
*) ctx
, "false", 5)) {
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
;
636 c
->saw_message_key
= (!(strncmp(s
, "message", 7)) &&
638 c
->saw_id_key
= c
->enclosing_id
&&
640 (!(strncmp(s
, "id", 2))) &&
643 if (!strncmp(s
, c
->field_names
[0], len
)) {
644 c
->idx_of_next_entry
= 0;
648 for (j
= 1; c
->field_names
[j
]; ++j
) {
649 if (!strncmp((const char *) key
, c
->field_names
[j
],
651 c
->idx_of_next_entry
= j
;
658 c
->idx_of_next_entry
= -1;
664 kve_yc_number(void *ctx
, const char *nval
, size_t len
)
666 if (kve_handle((kve_y_ctx
*) ctx
, nval
, len
)) {
674 kve_yc_start_map(void *ctx
)
676 kve_y_ctx
*c
= (kve_y_ctx
*) ctx
;
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
)) {
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;
705 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
, "title",
706 CURLFORM_PTRCONTENTS
, "Grading Scheme",
708 ret
= errno
? errno
: ENOMEM
;
710 perror(L("curl_formadd"));
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
;
721 perror(L("curl_formadd"));
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
;
731 perror(L("curl_formadd"));
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;
760 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
761 "course[hide_final_grades]",
762 CURLFORM_PTRCONTENTS
, hfg
,
764 ret
= errno
? errno
: ENOMEM
;
766 perror(L("curl_formadd"));
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
;
778 perror(L("curl_formadd"));
783 if (disable_grading_standard
) {
784 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
785 "course[grading_standard_id]",
786 CURLFORM_PTRCONTENTS
, "",
788 ret
= errno
? errno
: ENOMEM
;
790 perror(L("curl_formadd"));
796 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
797 "course[start_at]", CURLFORM_PTRCONTENTS
,
800 ret
= errno
? errno
: ENOMEM
;
802 perror(L("curl_formadd"));
808 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
809 "course[end_at]", CURLFORM_PTRCONTENTS
,
812 ret
= errno
? errno
: ENOMEM
;
814 perror(L("curl_formadd"));
820 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
821 "course[grading_standard_id]",
822 CURLFORM_PTRCONTENTS
, gse_id
,
824 ret
= errno
? errno
: ENOMEM
;
826 perror(L("curl_formadd"));
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;
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"));
861 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
862 "group_weight", CURLFORM_PTRCONTENTS
, weight
,
864 errno
= errno
? errno
: ENOMEM
;
865 perror(L("curl_formadd"));
871 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
872 "rules[drop_lowest]", CURLFORM_PTRCONTENTS
,
875 errno
= errno
? errno
: ENOMEM
;
876 perror(L("curl_formadd"));
882 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
883 "rules[drop_highest]", CURLFORM_PTRCONTENTS
,
886 errno
= errno
? errno
: ENOMEM
;
887 perror(L("curl_formadd"));
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
,
910 errno
= errno
? errno
: ENOMEM
;
911 perror(L("curl_formadd"));
915 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
916 "assignment[notify_of_update]", CURLFORM_PTRCONTENTS
,
919 errno
= errno
? errno
: ENOMEM
;
920 perror(L("curl_formadd"));
924 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
925 "assignment[grading_type]", CURLFORM_PTRCONTENTS
,
928 errno
= errno
? errno
: ENOMEM
;
929 perror(L("curl_formadd"));
933 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
934 "assignment[published]", CURLFORM_PTRCONTENTS
, "true",
936 errno
= errno
? errno
: ENOMEM
;
937 perror(L("curl_formadd"));
942 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
943 "assignment[name]", CURLFORM_PTRCONTENTS
, name
,
945 errno
= errno
? errno
: ENOMEM
;
946 perror(L("curl_formadd"));
952 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
953 "assignment[points_possible]",
954 CURLFORM_PTRCONTENTS
, max_points
,
956 errno
= errno
? errno
: ENOMEM
;
957 perror(L("curl_formadd"));
963 if (curl_formadd(&post
, &postend
, CURLFORM_PTRNAME
,
964 "assignment[due_at]", CURLFORM_PTRCONTENTS
,
967 errno
= errno
? errno
: ENOMEM
;
968 perror(L("curl_formadd"));
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"));
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;
997 char *param_name
= 0;
998 size_t param_name_len
= 0;
1001 for (j
= 1; j
< rows
; ++j
) {
1010 if (!strcasecmp(csv
[j
][col
], "ex")) {
1011 len
= snprintf(0, 0, "grade_data[%s][excuse]",
1014 if (len
> param_name_len
) {
1015 if (len
+ 1 < len
) {
1021 param_name_len
= len
+ 1;
1023 if (!(param_name
= realloc(param_name
,
1025 perror(L("realloc"));
1030 sprintf(param_name
, "grade_data[%s][excuse]",
1033 if (curl_formadd(&post
, &postend
, CURLFORM_COPYNAME
,
1034 param_name
, CURLFORM_PTRCONTENTS
,
1037 errno
= errno
? errno
: ENOMEM
;
1038 perror(L("curl_formadd"));
1042 len
= snprintf(0, 0, "grade_data[%s][posted_grade]",
1045 if (len
> param_name_len
) {
1046 if (len
+ 1 < len
) {
1052 param_name_len
= len
+ 1;
1054 if (!(param_name
= realloc(param_name
,
1056 perror(L("realloc"));
1061 sprintf(param_name
, "grade_data[%s][posted_grade]",
1064 if (curl_formadd(&post
, &postend
, CURLFORM_COPYNAME
,
1065 param_name
, CURLFORM_PTRCONTENTS
,
1068 errno
= errno
? errno
: ENOMEM
;
1069 perror(L("curl_formadd"));
1077 curl_formfree(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 };
1099 curl_to_yajl_ctx ctyc
= { 0 };
1100 yajl_handle y
= { 0 };
1101 fu_pt1_y_ctx ct1
= { 0 };
1103 char ce
[CURL_ERROR_SIZE
];
1105 char *auth_header
= 0;
1106 struct curl_slist
*custom_headers
= 0;
1111 /* Get file information */
1112 if (stat(path
, &fstat
)) {
1115 fprintf(stderr
, "invalid path: %s\n", path
);
1119 len
= snprintf(0, 0, "%zu", (size_t) fstat
.st_size
);
1121 if (len
+ 1 < len
) {
1122 ret
= errno
= EOVERFLOW
;
1127 if (!(filesize
= malloc(len
+ 1))) {
1129 perror(L("malloc"));
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"));
1142 len
= snprintf(0, 0, "Authorization: Bearer %s", auth_token
);
1144 if (len
+ 1 < len
) {
1145 ret
= errno
= EOVERFLOW
;
1150 if (!(auth_header
= malloc(len
+ 1))) {
1152 perror(L("malloc"));
1156 sprintf(auth_header
, "Authorization: Bearer %s", auth_token
);
1158 if (!(custom_headers
= curl_slist_append(custom_headers
,
1164 perror(L("curl_slist_append"));
1168 if (!(y
= yajl_alloc(&fu_pt1_callbacks
, 0, (void *) &ct1
))) {
1169 ret
= errno
= ENOMEM
;
1170 perror(L("yajl_alloc"));
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
;
1185 perror(L("curl_formadd"));
1189 if (curl_formadd(&post1
, &postend
, CURLFORM_PTRNAME
, "size",
1190 CURLFORM_PTRCONTENTS
, filesize
, CURLFORM_END
)) {
1191 ret
= errno
? errno
: ENOMEM
;
1193 perror(L("curl_formadd"));
1202 if (curl_formadd(&post1
, &postend
, CURLFORM_PTRNAME
,
1203 "parent_folder_path", CURLFORM_PTRCONTENTS
,
1206 ret
= errno
? errno
: ENOMEM
;
1208 perror(L("curl_formadd"));
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
||
1221 if (ct1
.error_message
) {
1222 fprintf(stderr
, L("Error: %s (http %ld) for %s\n"),
1223 ct1
.error_message
, http
, uri
);
1227 fprintf(stderr
, "%s: %s (http %ld)\n\n%s\n", L(
1228 "libcurl"), curl_easy_strerror(
1229 curl_ret
), http
, ce
);
1235 if (!ct1
.upload_url
) {
1237 "Error: Canvas did not reply with upload url\n"));
1243 if (!(c2
= create_curl_handle())) {
1244 ret
= errno
= ENOMEM
;
1245 perror(L("create_curl_handle"));
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
);
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
;
1262 perror(L("curl_formadd"));
1267 if (curl_formadd(&post2
, &postend
, CURLFORM_PTRNAME
, "file",
1268 CURLFORM_FILECONTENT
, path
, CURLFORM_END
)) {
1269 ret
= errno
? errno
: ENOMEM
;
1271 perror(L("curl_formadd"));
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
);
1286 /* Step 3 is in the FOLLOW_REDIRS of step 2. It switches to GET correctly. */
1291 curl_easy_cleanup(c1
);
1295 curl_easy_cleanup(c2
);
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
);
1312 curl_formfree(post2
);
1314 curl_slist_free_all(custom_headers
);
1321 fu_pt1_yc_end_map(void *ctx
)
1323 fu_pt1_y_ctx
*c
= (fu_pt1_y_ctx
*) ctx
;
1326 if (c
->depth
== c
->depth_of_upload_params
) {
1327 c
->in_upload_params
= 0;
1336 fu_pt1_handle_val(fu_pt1_y_ctx
*c
, const char *s
, size_t l
)
1340 if (c
->next_val_is_url
) {
1341 c
->next_val_is_url
= 0;
1343 if (!(dup
= malloc(l
+ 1))) {
1344 perror(L("malloc"));
1349 snprintf(dup
, l
+ 1, "%s", s
);
1350 free(c
->upload_url
);
1351 c
->upload_url
= dup
;
1356 if (!c
->in_upload_params
) {
1367 free(c
->upload_params_vals
[c
->upload_params_len
- 1]);
1369 if (!(dup
= malloc(l
+ 1))) {
1370 perror(L("malloc"));
1375 snprintf(dup
, l
+ 1, "%s", s
);
1376 c
->upload_params_vals
[c
->upload_params_len
- 1] = dup
;
1382 fu_pt1_yc_boolean(void *ctx
, int val
)
1385 if (fu_pt1_handle_val((fu_pt1_y_ctx
*) ctx
, "true", 4)) {
1389 if (fu_pt1_handle_val((fu_pt1_y_ctx
*) ctx
, "false", 5)) {
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
;
1404 size_t new_len
= c
->upload_params_len
+ 1;
1406 if (!c
->in_upload_params
) {
1408 !strncmp(s
, "upload_params", len
)) {
1409 c
->in_upload_params
= 1;
1410 c
->depth_of_upload_params
= c
->depth
+ 1;
1416 !strncmp(s
, "upload_url", len
)) {
1417 c
->next_val_is_url
= 1;
1425 if (new_len
< c
->upload_params_len
) {
1432 if (new_len
* (sizeof *(c
->upload_params_keys
)) / new_len
!=
1433 sizeof *(c
->upload_params_keys
)) {
1440 if (!(new_keys
= realloc(c
->upload_params_keys
, new_len
*
1441 (sizeof *(c
->upload_params_keys
))))) {
1442 perror(L("realloc"));
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"));
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"));
1467 snprintf(c
->upload_params_keys
[new_len
- 1], len
+ 1, "%s", s
);
1468 c
->upload_params_keys
[new_len
- 1][len
] = '\0';
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
)) {
1484 fu_pt1_yc_start_map(void *ctx
)
1486 fu_pt1_y_ctx
*c
= (fu_pt1_y_ctx
*) ctx
;
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
)) {
1507 map_add(map
*m
, char *k
, char **vs
)
1513 size_t hash
= map_hash(k
);
1522 for (j
= 0; j
< m
->es
[hash
]; ++j
) {
1525 if (!(strcmp(k
, e
->k
))) {
1529 for (l
= 0; l
< m
->num_values_per_entry
; ++l
) {
1541 if (m
->es
[hash
] + 1 < m
->es
[hash
] ||
1542 m
->es
[hash
] + 1 >= SIZE_MAX
/ (sizeof *m
->e
[hash
])) {
1548 if (!(newmem
= realloc(m
->e
[hash
], (m
->es
[hash
] + 1) *
1549 sizeof *m
->e
[hash
]))) {
1553 m
->e
[hash
] = newmem
;
1554 m
->e
[hash
][m
->es
[hash
]] = (entry
) { .k
= k
, .vs
= vs
};
1576 for (j
= 0; j
< BUCKET_NUM
; ++j
) {
1577 for (k
= 0; k
< m
->es
[j
]; ++k
) {
1582 for (l
= 0; l
< m
->num_values_per_entry
; ++l
) {
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
));
1608 map_get(map
*m
, char *k
)
1612 size_t hash
= map_hash(k
);
1619 for (j
= 0; j
< m
->es
[hash
]; ++j
) {
1622 if (!(strcmp(k
, e
->k
))) {
1631 map_get_keys(map
*m
, char ***out_k
, size_t *out_num
)
1633 size_t num_keys
= 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
))) {
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
;
1659 map_hash(const char *s
)
1669 for (p
= s
, j
= 0; *p
&&
1671 hash
= hash
* 33 ^ *p
;
1674 return hash
% BUCKET_NUM
;
1678 print_esc_0x22(const char *s
)
1682 /* Quote as per RFC 4180 */
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
,
1699 size_t *y
, size_t *x
)
1703 if (*cell_pos
+ 1 >= *cell_sz
) {
1704 if (*cell_sz
+ 64 < *cell_sz
||
1705 (*cell_sz
+ 64) >= SIZE_MAX
/ (sizeof **cell
)) {
1713 if (!(newmem
= realloc(*cell
, (*cell_sz
+ 64) *
1715 perror(L("realloc"));
1726 case CPS_START_OF_CELL
:
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';
1739 (*cell
)[(*cell_pos
)++] = (char) c
;
1740 *s
= CPS_IN_NONQUOTED_CELL
;
1744 case CPS_IN_QUOTED_CELL
:
1748 } else if (c
== '"') {
1749 *s
= CPS_JUST_SAW_0x22
;
1751 (*cell
)[(*cell_pos
)++] = (char) c
;
1755 case CPS_IN_NONQUOTED_CELL
:
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
== '"') {
1769 (*cell
)[(*cell_pos
)++] = (char) c
;
1773 case CPS_JUST_SAW_0x22
:
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';
1791 case CPS_JUST_ENDED_LINE
:
1792 case CPS_JUST_ENDED_CELL
:
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
;
1805 (*cell
)[(*cell_pos
)++] = (char) c
;
1806 *s
= CPS_IN_NONQUOTED_CELL
;
1811 case CPS_END_OF_INPUT
:
1815 if (*reading_header
&&
1816 (*s
== CPS_JUST_ENDED_CELL
||
1817 *s
== CPS_JUST_ENDED_LINE
||
1818 *s
== CPS_END_OF_INPUT
)) {
1821 if (*reading_header
) {
1822 if (*record_len
+ 1 < *record_len
||
1823 (*record_len
+ 1) >= SIZE_MAX
/ (sizeof ***csv
)) {
1830 if (!(newmem
= realloc((*csv
)[0], (*record_len
+ 1) *
1832 perror(L("realloc"));
1841 if (*s
== CPS_JUST_ENDED_CELL
||
1842 *s
== CPS_JUST_ENDED_LINE
||
1843 *s
== CPS_END_OF_INPUT
) {
1844 if (*x
>= *record_len
) {
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"));
1863 if (*s
== CPS_JUST_ENDED_LINE
) {
1866 *reading_header
= 0;
1869 (*y
+ 1) >= SIZE_MAX
/ (sizeof *csv
)) {
1876 if (!(newmem
= realloc(*csv
, (*y
+ 1) * sizeof *csv
))) {
1877 perror(L("realloc"));
1884 if (!((*csv
)[*y
] = calloc(*record_len
, sizeof ***csv
))) {
1885 perror(L("calloc"));
1891 if (*s
== CPS_JUST_ENDED_CELL
) {
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
;
1905 size_t cell_pos
= 0;
1907 char reading_header
= 1;
1908 size_t record_len
= 0;
1913 char buf
[READ_BUF_LEN
];
1914 const char *buf_end
= 0;
1917 if (!(cell
= calloc(cell_sz
, sizeof *cell
))) {
1918 perror(L("calloc"));
1923 if (!(csv
= calloc(1, sizeof *csv
))) {
1924 perror(L("calloc"));
1929 if (!(csv
[0] = calloc(1, sizeof **csv
))) {
1930 perror(L("malloc"));
1937 fread_ret
= fread(buf
, sizeof *buf
, READ_BUF_LEN
, f
);
1943 buf_end
= buf
+ (fread_ret
- 1);
1946 while (p
<= buf_end
) {
1947 if (process_char_csv(*p
, &s
, &cell
, &cell_sz
, &cell_pos
,
1948 &csv
, &reading_header
, &record_len
,
1957 if (s
== CPS_INVALID
) {
1965 *out_cols
= record_len
;
1970 for (j
= 0; j
< y
; ++j
) {
1971 for (k
= 0; k
< x
; ++k
) {
1985 send_and_id_scan(const char *uri
, const char *auth_token
, struct
1986 curl_httppost
*post
, const char *method
, char **id
)
1990 yajl_handle y
= { 0 };
1991 ie_y_ctx ct
= { .id_str
= id
};
1992 curl_to_yajl_ctx ctyc
= { 0 };
1994 char ce
[CURL_ERROR_SIZE
];
1996 char *auth_header
= 0;
1998 struct curl_slist
*custom_headers
= 0;
2002 if (!(c
= create_curl_handle())) {
2003 ret
= errno
= ENOMEM
;
2004 perror(L("create_curl_handle"));
2008 len
= snprintf(0, 0, "Authorization: Bearer %s", auth_token
);
2010 if (len
+ 1 < len
) {
2011 ret
= errno
= EOVERFLOW
;
2016 if (!(auth_header
= malloc(len
+ 1))) {
2018 perror(L("malloc"));
2022 sprintf(auth_header
, "Authorization: Bearer %s", auth_token
);
2024 if (!(custom_headers
= curl_slist_append(custom_headers
,
2030 perror(L("curl_slist_append"));
2035 if (!(y
= yajl_alloc(&ie_callbacks
, 0, (void *) &ct
))) {
2036 ret
= errno
= ENOMEM
;
2037 perror(L("yajl_alloc"));
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
||
2055 if (ct
.error_message
) {
2056 fprintf(stderr
, L("Error: %s (http %ld) for %s\n"),
2057 ct
.error_message
, http
, uri
);
2060 fprintf(stderr
, "%s: %s (http %ld)\n\n%s\n", L(
2061 "libcurl"), curl_easy_strerror(
2062 curl_ret
), http
, ce
);
2068 free(ct
.error_message
);
2071 curl_easy_cleanup(c
);
2074 curl_slist_free_all(custom_headers
);