neocities.h

a C library for interacting with Neocities' API
git clone https://github.com/tanguyandreani/neocities.h
Log | Files | Refs | README | LICENSE

neocities.h (22636B)


      1 #pragma once
      2 
      3 /*
      4  * the following constants are not to be used elsewhere than in this 
      5  * file, they are undefined at the end of this header
      6  */
      7 
      8 #define BASEURL "https://neocities.org/api/"
      9 
     10 /* POST */
     11 #define UPLOAD_URL BASEURL "upload"
     12 #define DELETE_URL BASEURL "delete"
     13 
     14 /* GET */
     15 #define LIST_URL BASEURL "list"
     16 #define INFO_URL BASEURL "info"
     17 #define KEY_URL  BASEURL "key"
     18 
     19 
     20 /* those are part of public api */
     21 
     22 #define neocities_global_init() \
     23     ((curl_global_init(CURL_GLOBAL_SSL) != 0) \
     24     ? NEOCITIES_LLVL_ERR_CURL_GLOBAL_INIT \
     25     : 0)
     26 
     27 #define neocities_global_cleanup() \
     28     curl_global_cleanup();
     29 
     30 /* to use with neocities_api[_ex] */
     31 enum neocities_action {
     32     upload,
     33     delete,
     34     list,
     35     info,
     36     key
     37 };
     38 
     39 /* this stands for LOW LEVEL ERROR */
     40 
     41 enum neocities_low_level_error {
     42     NEOCITIES_LLVL_OK = 0,
     43 
     44     NEOCITIES_LLVL_ERR_CURL_GLOBAL_INIT,
     45 
     46     /*
     47      * the following members are not to be used elsewhere than in this 
     48      * file; you can throw them in neocities_print_error_message().
     49      */
     50 
     51     NEOCITIES_LLVL_ERR_JSON_TOKENER_NEW,
     52     NEOCITIES_LLVL_ERR_CURL_EASY_INIT,
     53     NEOCITIES_LLVL_ERR_CURL_MIME_INIT,
     54     NEOCITIES_LLVL_ERR_CURL_MIME_ADDPART,
     55     NEOCITIES_LLVL_ERR_CURL_MIME_NAME,
     56     NEOCITIES_LLVL_ERR_CURL_MIME_FILEDATA,
     57     NEOCITIES_LLVL_ERR_CURL_MIME_DATA,
     58     NEOCITIES_LLVL_ERR_CURL_SLIST_APPEND,
     59     NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT,
     60     NEOCITIES_LLVL_ERR_CURL_EASY_PERFORM,
     61     NEOCITIES_LLVL_ERR_UNSUPPORTED_DELETE,
     62     NEOCITIES_LLVL_ERR_BAD_ACTION,
     63     NEOCITIES_LLVL_ERR_CURL_BUF_INIT,
     64     NEOCITIES_LLVL_ERR_JSON_TOKENER_PARSE,
     65     NEOCITIES_LLVL_ERR_RECEIVED_UNSUPPORTED_JSON,
     66     NEOCITIES_LLVL_ERR_RECEIVED_INVALID_JSON,
     67     NEOCITIES_LLVL_ERR_MALLOC_FAIL,
     68     NEOCITIES_LLVL_ERR_EXPECTED_STRING,
     69     NEOCITIES_LLVL_ERR_EXPECTED_BOOL,
     70     NEOCITIES_LLVL_ERR_EXPECTED_INT,
     71     NEOCITIES_LLVL_ERR_EXPECTED_ARRAY,
     72     NEOCITIES_LLVL_ERR_EXPECTED_STRING_OR_NULL,
     73     NEOCITIES_LLVL_ERR_EXPECTED_OBJECT,
     74     NEOCITIES_LLVL_ERR_RECEIVED_SOMETHING_ELSE
     75 };
     76 
     77 /*
     78  * neocities_*_ are structs that holds all the useful data
     79  *
     80  * see neocities_res for a more general type
     81  */
     82 
     83 struct neocities_info_ {
     84     char *sitename;             /* default: NULL */
     85     int32_t hits;               /* default: -1 */
     86     int32_t views;
     87     time_t created_at;          /* default: 0 */
     88     time_t last_updated;
     89     char *domain;
     90 
     91     /* you're not allowed to have more tags anyway */
     92     char *tags[20];
     93 };
     94 
     95 struct neocities_list_ {
     96     struct neocities_file_ *files;
     97     int length;
     98 };
     99 
    100 struct neocities_file_ {
    101     char *path;
    102     int is_directory;
    103     int size;
    104     time_t updated_at;
    105 };
    106 
    107 /*
    108  * you might want to use those in your code
    109  */
    110 
    111 enum neocities_api_level_error {
    112     SITE_NOT_FOUND,
    113     UNSUPPORTED_ERROR
    114 };
    115 
    116 struct neocities_error_ {
    117     enum neocities_api_level_error type;
    118 };
    119 
    120 /*
    121  * NEOCITIES_NO_TYPE_YET works for successful request that didn't have an
    122  * info or list field such as the upload call
    123  */
    124 
    125 enum neocities_res_data {
    126     NEOCITIES_NO_TYPE_YET,
    127     NEOCITIES_INFO,
    128     NEOCITIES_LIST,
    129     NEOCITIES_ERROR = -1
    130 };
    131 
    132 typedef struct neocities_res_ {
    133     int result;
    134     enum neocities_res_data type;
    135 
    136     union {
    137         struct neocities_list_ list;
    138         struct neocities_info_ info;
    139         struct neocities_error_ error;
    140     } data;
    141 } neocities_res;
    142 
    143 /*
    144  * this is an internal structure that shouldn't be used elsewhere.
    145  */
    146 
    147 typedef struct curl_buffer_ {
    148     char *buf;
    149 
    150     int size;
    151     int pos;
    152 } curl_buffer;
    153 
    154 int curl_buffer_init(curl_buffer * curl_buf, size_t size)
    155 {
    156     curl_buf->buf = malloc(sizeof(char) * size);
    157 
    158     if (curl_buf->buf == NULL)
    159         return 1;
    160 
    161     curl_buf->size = size;
    162     curl_buf->pos = 0;
    163 
    164     return 0;
    165 }
    166 
    167 void curl_buffer_destroy(curl_buffer * curl_buf)
    168 {
    169     free(curl_buf->buf);
    170     curl_buf->buf = NULL;
    171 
    172     curl_buf->size = 0;
    173     curl_buf->pos = 0;
    174 
    175     return;
    176 }
    177 
    178 void curl_buffer_dump(curl_buffer * curl_buf)
    179 {
    180     int i = 0;
    181 
    182     while (i < curl_buf->pos) {
    183         fputc(curl_buf->buf[i], stderr);
    184         i++;
    185     }
    186 
    187     return;
    188 }
    189 
    190 size_t write_data(void *buffer, size_t size, size_t nmemb,
    191                   curl_buffer * curl_buf)
    192 {
    193     int i = 0;
    194     while (i < nmemb) {
    195 
    196         if (curl_buf->pos >= curl_buf->size) {
    197 
    198             curl_buf->size += nmemb;
    199 
    200             curl_buf->buf =
    201                 realloc(curl_buf->buf, sizeof(char) * curl_buf->size);
    202 
    203             if (curl_buf->buf == NULL)
    204                 return curl_buf->size;
    205 
    206         }
    207 
    208         curl_buf->buf[curl_buf->pos] = *(char *) (buffer + i);
    209 
    210         (curl_buf->pos)++;
    211         i++;
    212     }
    213 
    214     return i;
    215 }
    216 
    217 /*
    218  * perform an api call and parse the resulting json into a json_object
    219  */
    220 
    221 enum neocities_low_level_error neocities_api(const char *apikey,
    222                                              enum neocities_action action,
    223                                              const char *params,
    224                                              json_object ** jobj)
    225 {
    226     curl_buffer curl_buf;
    227 
    228     struct curl_slist *headers = NULL;
    229 
    230     CURL *curl;
    231 
    232     curl_mime *form = NULL;
    233     curl_mimepart *field = NULL;
    234 
    235     json_tokener *tok = json_tokener_new();
    236 
    237     char url_with_get_params[500];
    238 
    239     char auth_bearer[22 + 32 + 1] = "Authorization: Bearer ";
    240     strcat(auth_bearer, apikey);
    241 
    242     if (tok == NULL)
    243         return NEOCITIES_LLVL_ERR_JSON_TOKENER_NEW;
    244 
    245     curl = curl_easy_init();
    246 
    247     if (curl == NULL)
    248         return NEOCITIES_LLVL_ERR_CURL_EASY_INIT;
    249 
    250     if (action == upload) {
    251 
    252         form = curl_mime_init(curl);
    253 
    254         if (form == NULL)
    255             return NEOCITIES_LLVL_ERR_CURL_MIME_INIT;
    256 
    257         field = curl_mime_addpart(form);
    258 
    259         if (field == NULL)
    260             return NEOCITIES_LLVL_ERR_CURL_MIME_ADDPART;
    261 
    262         if (curl_mime_name(field, params) != CURLE_OK)
    263             return NEOCITIES_LLVL_ERR_CURL_MIME_NAME;
    264 
    265         if (curl_mime_filedata(field, params) != CURLE_OK)
    266             return NEOCITIES_LLVL_ERR_CURL_MIME_FILEDATA;
    267 
    268         /* seems useless :( */
    269 
    270         /*field = curl_mime_addpart(form);
    271 
    272         if (field == NULL)
    273             return NEOCITIES_LLVL_ERR_CURL_MIME_ADDPART;
    274 
    275         if (curl_mime_name(field, "filename") != CURLE_OK)
    276             return NEOCITIES_LLVL_ERR_CURL_MIME_NAME;
    277 
    278         if (curl_mime_data(field, params, CURL_ZERO_TERMINATED) != CURLE_OK)
    279             return NEOCITIES_LLVL_ERR_CURL_MIME_DATA;*/
    280 
    281         field = NULL;
    282 
    283         headers = curl_slist_append(headers, "Expect:");
    284 
    285         if (headers == NULL)
    286             return NEOCITIES_LLVL_ERR_CURL_SLIST_APPEND;
    287 
    288         if (curl_easy_setopt(curl, CURLOPT_MIMEPOST, form) != CURLE_OK)
    289             return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT;
    290 
    291         if (curl_easy_setopt(curl, CURLOPT_URL, UPLOAD_URL) != CURLE_OK)
    292             return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT;
    293 
    294     } else if (action == delete) {
    295 
    296         return NEOCITIES_LLVL_ERR_UNSUPPORTED_DELETE;
    297 
    298     } else if (action == info || action == list || action == key) {
    299 
    300         memset(&url_with_get_params[0], '\0', 500);
    301 
    302         /* appends p to our url string */
    303 #define ap(p) \
    304             strcat(url_with_get_params, p)
    305 
    306         if (action == key) {
    307 
    308             ap(KEY_URL);
    309 
    310         } else if (action == info) {
    311 
    312             ap(INFO_URL);
    313 
    314             if (strcmp(params, "") != 0)
    315                 ap("?sitename=");
    316 
    317         } else if (action == list) {
    318 
    319             ap(LIST_URL);
    320 
    321             if (strcmp(params, "") != 0)
    322                 ap("?path=");
    323 
    324         }
    325 
    326         ap(params);
    327 
    328 #undef ap
    329 
    330         if (curl_easy_setopt(curl, CURLOPT_URL, url_with_get_params) !=
    331             CURLE_OK)
    332             return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT;
    333 
    334     } else {
    335 
    336         return NEOCITIES_LLVL_ERR_BAD_ACTION;
    337 
    338     }
    339 
    340     if (curl_buffer_init(&curl_buf, 700) != 0)
    341         return NEOCITIES_LLVL_ERR_CURL_BUF_INIT;
    342 
    343     headers = curl_slist_append(headers, auth_bearer);
    344 
    345     if (headers == NULL)
    346         return NEOCITIES_LLVL_ERR_CURL_SLIST_APPEND;
    347 
    348     if (curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers) != CURLE_OK)
    349         return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT;
    350 
    351     if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data) != CURLE_OK)
    352         return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT;
    353 
    354     if (curl_easy_setopt
    355         (curl, CURLOPT_WRITEDATA, ((curl_buffer *) & curl_buf)))
    356         return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT;
    357 
    358     if (curl_easy_perform(curl) != CURLE_OK)
    359         return NEOCITIES_LLVL_ERR_CURL_EASY_PERFORM;
    360 
    361     curl_slist_free_all(headers);
    362     curl_mime_free(form);
    363     curl_easy_cleanup(curl);
    364 
    365     /* our function write_data set curl_buf.pos to the length of the 
    366        received data */
    367     *jobj = json_tokener_parse_ex(tok, (char *) curl_buf.buf, curl_buf.pos);
    368 
    369     /* https://groups.google.com/forum/#!topic/json-c/CMvkXKXqtWs */
    370     if (tok->char_offset != curl_buf.pos) {
    371         json_tokener_free(tok);
    372         return NEOCITIES_LLVL_ERR_RECEIVED_INVALID_JSON;
    373     }
    374 
    375     json_tokener_free(tok);
    376 
    377 #ifdef NEOCITIES_DEBUG
    378     curl_buffer_dump(&curl_buf);
    379 #endif
    380     curl_buffer_destroy(&curl_buf);
    381 
    382     if (*jobj == NULL)
    383         return NEOCITIES_LLVL_ERR_JSON_TOKENER_PARSE;
    384 
    385     if (json_object_get_type(*jobj) != json_type_object)
    386         return NEOCITIES_LLVL_ERR_EXPECTED_OBJECT;
    387 
    388     return NEOCITIES_LLVL_OK;
    389 }
    390 
    391 /*
    392  * those are functions used by neocities_destroy()
    393  */
    394 
    395 static void neocities_destroy_list(neocities_res * res)
    396 {
    397     int i = 0;
    398 
    399     res->data.list.length = -1;
    400 
    401     for (; i < res->data.list.length; i++)
    402         free(res->data.list.files[i].path);
    403 
    404     if (res->data.list.files != NULL)
    405         free(res->data.list.files);
    406 
    407     return;
    408 }
    409 
    410 static void neocities_destroy_info(neocities_res * res)
    411 {
    412     int i = 0;
    413 
    414     res->data.info.views = -1;
    415     res->data.info.hits = -1;
    416     res->data.info.created_at = (time_t)0;
    417     res->data.info.last_updated = (time_t)0;
    418 
    419     while (res->data.info.tags[i] != NULL) {
    420         free(res->data.info.tags[i]);
    421         i++;
    422     }
    423 
    424     if (res->data.info.sitename != NULL)
    425         free(res->data.info.sitename);
    426 
    427     if (res->data.info.domain != NULL)
    428         free(res->data.info.domain);
    429 
    430     return;
    431 }
    432 
    433 static void neocities_destroy_error(neocities_res * res)
    434 {
    435     res->data.error.type = -1;
    436 
    437     return;
    438 }
    439 
    440 /*
    441  * you should use this to free memory AND reset values to their defaults
    442  */
    443 
    444 void neocities_destroy(neocities_res * res)
    445 {
    446 
    447     switch (res->type) {
    448     case NEOCITIES_NO_TYPE_YET:
    449         break;
    450     case NEOCITIES_INFO:
    451         neocities_destroy_info(res);
    452         break;
    453     case NEOCITIES_LIST:
    454         neocities_destroy_list(res);
    455         break;
    456     case NEOCITIES_ERROR:
    457         neocities_destroy_error(res);
    458         break;
    459     }
    460 
    461     res->type = NEOCITIES_NO_TYPE_YET;
    462 
    463     return;
    464 }
    465 
    466 /*
    467  * i took the guess approach here, this function determines which type
    468  * is the most likely and tries to fill res fields as much as possible
    469  *
    470  * fields are reset to their default values as soon as the type is
    471  * recognized
    472  *
    473  * the function will exit in case their is a type error in the json
    474  */
    475 
    476 enum neocities_low_level_error neocities_json_to_struct(json_object * jobj,
    477                                                         neocities_res * res)
    478 {
    479 
    480     json_object_iter jobj_iter, jobj_iter_info, jobj_iter_list;
    481 
    482     int seen_result = 0;
    483 
    484     int i = 0;
    485 
    486     char *tmp_string = NULL;
    487     array_list *tmp_array = NULL;
    488     json_object *tmp_object = NULL;
    489 
    490     res->type = NEOCITIES_NO_TYPE_YET;
    491 
    492     json_object_object_foreachC(jobj, jobj_iter) {
    493 
    494 /* undefined on same level */
    495 #define is_not(jobj, type) (json_object_get_type(jobj) != type)
    496 
    497 #define key_is(s) (strcmp(jobj_iter.key, s) == 0)
    498 
    499         if (jobj_iter.key == NULL)
    500             continue;
    501 
    502         if key_is
    503             ("error_type") {
    504 
    505             res->result = -1;     // in case wasn't sent in usual order
    506             res->type = NEOCITIES_ERROR;
    507             res->data.error.type = UNSUPPORTED_ERROR;
    508 
    509             if is_not
    510                 (jobj_iter.val, json_type_string)
    511                     return NEOCITIES_LLVL_ERR_EXPECTED_STRING;
    512 
    513             tmp_string = json_object_get_string(jobj_iter.val);
    514 
    515 #define error_is(s) (tmp_string != NULL && (strcmp(tmp_string, s) == 0))
    516 
    517             if error_is
    518                 ("site_not_found")
    519                     res->data.error.type = SITE_NOT_FOUND;
    520 
    521 #undef error_is
    522 
    523             break;
    524 
    525         } else if key_is
    526             ("result") {
    527 
    528             if (seen_result == 1)
    529                 return NEOCITIES_LLVL_ERR_RECEIVED_UNSUPPORTED_JSON;
    530             else
    531                 seen_result = 1;
    532 
    533             if is_not
    534                 (jobj_iter.val, json_type_string)
    535                     return NEOCITIES_LLVL_ERR_EXPECTED_STRING;
    536 
    537             tmp_string = json_object_get_string(jobj_iter.val);
    538 
    539 #define result_is(s) (tmp_string != NULL && (strcmp(tmp_string, s) == 0))
    540 
    541             if result_is
    542                 ("success")
    543                     res->result = 0;
    544             else
    545                 res->result = -1;
    546 
    547 #undef result_is
    548 
    549         } else if (key_is("info")
    550                    && res->type == NEOCITIES_NO_TYPE_YET) {
    551 
    552             res->type = NEOCITIES_INFO;
    553 
    554             res->data.info.sitename = NULL;
    555             res->data.info.views = -1;
    556             res->data.info.hits = -1;
    557             res->data.info.created_at = (time_t)0;
    558             res->data.info.last_updated = (time_t)0;
    559             res->data.info.domain = NULL;
    560             res->data.info.tags[0] = NULL;
    561 
    562             if is_not
    563                 (jobj_iter.val, json_type_object)
    564                     return NEOCITIES_LLVL_ERR_EXPECTED_OBJECT;
    565 
    566             json_object_object_foreachC(jobj_iter.val, jobj_iter_info) {
    567 
    568 #define INFO_key_is(s) (strcmp(jobj_iter_info.key, s) == 0)
    569 
    570                 if (jobj_iter_info.key == NULL)
    571                     continue;
    572 
    573                 if INFO_key_is
    574                     ("views") {
    575 
    576                     if is_not
    577                         (jobj_iter_info.val, json_type_int)
    578                             return NEOCITIES_LLVL_ERR_EXPECTED_INT;
    579 
    580                     res->data.info.views =
    581                         json_object_get_int(jobj_iter_info.val);
    582 
    583                 } else if INFO_key_is
    584                     ("hits") {
    585 
    586                     if is_not
    587                         (jobj_iter_info.val, json_type_int)
    588                             return NEOCITIES_LLVL_ERR_EXPECTED_INT;
    589 
    590                     res->data.info.hits =
    591                         json_object_get_int(jobj_iter_info.val);
    592 
    593                 } else if INFO_key_is
    594                     ("sitename") {
    595 
    596                     if is_not
    597                         (jobj_iter_info.val, json_type_string)
    598                             return NEOCITIES_LLVL_ERR_EXPECTED_STRING;
    599 
    600                     res->data.info.sitename =
    601                         strdup(json_object_get_string(jobj_iter_info.val));
    602 
    603                     if (res->data.info.sitename == NULL)
    604                         return NEOCITIES_LLVL_ERR_MALLOC_FAIL;
    605 
    606                 } else if INFO_key_is
    607                     ("domain") {
    608 
    609                     if (is_not(jobj_iter_info.val, json_type_string)
    610                         && is_not(jobj_iter_info.val, json_type_null))
    611                         return NEOCITIES_LLVL_ERR_EXPECTED_STRING_OR_NULL;
    612 
    613                     tmp_string = json_object_get_string(jobj_iter_info.val);
    614 
    615                     if (tmp_string != NULL) {
    616                         res->data.info.domain = strdup(tmp_string);
    617 
    618                         if (res->data.info.domain == NULL)
    619                             return NEOCITIES_LLVL_ERR_MALLOC_FAIL;
    620                     }
    621 
    622                 } else if INFO_key_is
    623                     ("created_at") {
    624 
    625                     if is_not
    626                         (jobj_iter_info.val, json_type_string)
    627                             return NEOCITIES_LLVL_ERR_EXPECTED_STRING;
    628 
    629                     tmp_string = json_object_get_string(jobj_iter_info.val);
    630 
    631                     rfc5322_date_parse(tmp_string, strlen(tmp_string),
    632                                        &res->data.info.created_at, true);
    633 
    634                 } else if INFO_key_is
    635                     ("last_updated") {
    636 
    637                     if (is_not(jobj_iter_info.val, json_type_string)
    638                         && is_not(jobj_iter_info.val, json_type_null))
    639                         return NEOCITIES_LLVL_ERR_EXPECTED_STRING_OR_NULL;
    640 
    641                     tmp_string = json_object_get_string(jobj_iter_info.val);
    642 
    643                     if (tmp_string != NULL)
    644                         rfc5322_date_parse(tmp_string, strlen(tmp_string),
    645                                            &res->data.info.last_updated, true);
    646 
    647                 } else if INFO_key_is
    648                     ("tags") {
    649 
    650                     if is_not
    651                         (jobj_iter_info.val, json_type_array)
    652                             return NEOCITIES_LLVL_ERR_EXPECTED_ARRAY;
    653 
    654                     tmp_array = json_object_get_array(jobj_iter_info.val);
    655 
    656                     i = 0;
    657 
    658                     for (; i < tmp_array->length; i++) {
    659 
    660                         if is_not
    661                             ((tmp_array->array)[i], json_type_string)
    662                             return NEOCITIES_LLVL_ERR_EXPECTED_STRING;
    663 
    664                         res->data.info.tags[i] =
    665                             strdup(json_object_get_string
    666                                    ((tmp_array->array)[i]));
    667 
    668                         if (res->data.info.tags[i] == NULL)
    669                             return NEOCITIES_LLVL_ERR_MALLOC_FAIL;
    670 
    671 
    672                     }
    673 
    674                     res->data.info.tags[i] = NULL;
    675                     }
    676 #undef INFO_key_is
    677 
    678             }
    679 
    680             break;              /* return when all the fields are processed */
    681 
    682         } else if (key_is("files")
    683                    && res->type == NEOCITIES_NO_TYPE_YET) {
    684 
    685             res->type = NEOCITIES_LIST;
    686 
    687             res->data.list.files = NULL;
    688             res->data.list.length = 0;
    689 
    690             if is_not
    691                 (jobj_iter.val, json_type_array)
    692                     return NEOCITIES_LLVL_ERR_EXPECTED_ARRAY;
    693 
    694             res->data.list.length = json_object_array_length(jobj_iter.val);
    695 
    696             res->data.list.files =
    697                 malloc(sizeof(struct neocities_file_) * res->data.list.length);
    698 
    699             if (res->data.list.files == NULL)
    700                 return NEOCITIES_LLVL_ERR_MALLOC_FAIL;
    701 
    702             tmp_array = json_object_get_array(jobj_iter.val);
    703 
    704             for (i = 0; i < res->data.list.length; i++) {
    705 
    706                 (res->data.list.files)[i].path = NULL;
    707                 (res->data.list.files)[i].is_directory = -1;
    708                 (res->data.list.files)[i].size = -1;
    709                 (res->data.list.files)[i].updated_at = (time_t)0;
    710 
    711 #define LIST_key_is(s) (strcmp(jobj_iter_list.key, s) == 0)
    712 
    713                 json_object_object_foreachC(tmp_array->array[i],
    714                                             jobj_iter_list) {
    715 
    716                     if (jobj_iter_list.key == NULL)
    717                         continue;
    718 
    719                     if LIST_key_is
    720                         ("path") {
    721 
    722                         if is_not
    723                             (jobj_iter_list.val, json_type_string)
    724                                 return NEOCITIES_LLVL_ERR_EXPECTED_STRING;
    725 
    726                         (res->data.list.files)[i].path =
    727                             strdup(json_object_get_string(jobj_iter_list.val));
    728 
    729                         if ((res->data.list.files)[i].path == NULL)
    730                             return NEOCITIES_LLVL_ERR_MALLOC_FAIL;
    731 
    732                     } else if LIST_key_is
    733                         ("updated_at") {
    734 
    735                         if is_not
    736                             (jobj_iter_list.val, json_type_string)
    737                                 return NEOCITIES_LLVL_ERR_EXPECTED_STRING;
    738 
    739                         tmp_string = json_object_get_string
    740                             (jobj_iter_list.val);
    741 
    742                         if (tmp_string == NULL)
    743                             continue;
    744 
    745                         rfc5322_date_parse(tmp_string, strlen(tmp_string),
    746                                            &(res->data.list.
    747                                              files)[i].updated_at, true);
    748 
    749                     } else if LIST_key_is
    750                         ("size") {
    751 
    752                         if is_not
    753                             (jobj_iter_list.val, json_type_int)
    754                                 return NEOCITIES_LLVL_ERR_EXPECTED_INT;
    755 
    756                         (res->data.list.files)[i].size =
    757                             json_object_get_int(jobj_iter_list.val);
    758 
    759                     } else if LIST_key_is
    760                         ("is_directory") {
    761 
    762                         if is_not
    763                             (jobj_iter_list.val, json_type_boolean)
    764                                 return NEOCITIES_LLVL_ERR_EXPECTED_BOOL;
    765 
    766                         /* TRUE is defined by libjson-c */
    767                         (res->data.list.files)[i].is_directory =
    768                             (json_object_get_boolean(jobj_iter_list.val) ==
    769                              ((json_bool)1) ? 1 : 0);
    770 
    771                         }
    772 
    773                     /*else {
    774 
    775                        return NEOCITIES_LLVL_ERR_UNSUPPORTED_JSON;
    776 
    777                        } */
    778 
    779                 }
    780 
    781 #undef LIST_key_is
    782 
    783             }                   /* for each file in list */
    784 
    785             break;              /* return when all the files are processed */
    786 
    787         }
    788         /* if key_is() ... */
    789 #undef key_is
    790 #undef is_not
    791 
    792     }                           /* for each json object */
    793 
    794     return NEOCITIES_LLVL_OK;
    795 }
    796 
    797 /* simple function used for convenience */
    798 
    799 enum neocities_res_data action_to_res_data_type(enum neocities_action action)
    800 {
    801     switch (action) {
    802     case upload:
    803         return NEOCITIES_NO_TYPE_YET;
    804     case list:
    805         return NEOCITIES_LIST;
    806     case info:
    807         return NEOCITIES_INFO;
    808     }
    809 }
    810 
    811 /* the function you probably looked for */
    812 
    813 enum neocities_low_level_error neocities_api_ex(const char *apikey,
    814                                                 enum neocities_action
    815                                                 action, const char *params,
    816                                                 neocities_res * res)
    817 {
    818     int err;
    819 
    820     json_object *jobj = NULL;
    821 
    822     if ((err =
    823          neocities_api(apikey, action, params, &jobj)) != NEOCITIES_LLVL_OK)
    824         return err;
    825 
    826     err = neocities_json_to_struct(jobj, res);
    827 
    828     if (res->result == 0 && res->type != action_to_res_data_type(action))
    829         return NEOCITIES_LLVL_ERR_RECEIVED_SOMETHING_ELSE;
    830 
    831     json_object_put(jobj);
    832 
    833     return err;
    834 }
    835 
    836 #undef UPLOAD_URL
    837 #undef DELETE_URL
    838 
    839 #undef LIST_URL
    840 #undef INFO_URL
    841 #undef KEY_URL
    842 
    843 #undef BASEURL