neocities.h

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

commit 8fcb275b3130da83ec5ca43b548f8d0cbc0bc1e8
parent a40a6603f0a8d90db93111326f30160e5a4e8174
Author: Tanguy Andreani <dev@tanguy.space>
Date:   Fri, 22 Feb 2019 06:45:02 +0100

Break (probably): productive friday

Diffstat:
MMakefile | 10+++++-----
Adeps.txt | 3+++
Adtparser.c | 565+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adtparser.h | 21+++++++++++++++++++++
Mexample.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mneocities.h | 452++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
6 files changed, 1069 insertions(+), 61 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,11 +1,11 @@ CC=gcc # clang FLAGS=-lcurl -ljson-c -I/usr/include/json-c -DEBUG=-DNEOCITIES_DEBUG +DEBUG=-g -DNEOCITIES_DEBUG all: example -example: example.c - $(CC) -o $(<:.c=) $< $(FLAGS) $(DEBUG) +example: example.c neocities.h dtparser.c dtparser.h + $(CC) -o $(<:.c=) dtparser.c $< $(FLAGS) $(DEBUG) clean: - rm -f ./example- \ No newline at end of file + rm -rf ./example+ \ No newline at end of file diff --git a/deps.txt b/deps.txt @@ -0,0 +1,2 @@ +libjson-c-dev +libcurl-dev+ \ No newline at end of file diff --git a/dtparser.c b/dtparser.c @@ -0,0 +1,565 @@ +/* + * dtparser + * + * + * dtparser is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + * + * Copyright (c) 2017 Partha Susarla <mail@spartha.org> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include "dtparser.h" + +#define EOB (-99999) /* End Of Buffer */ +static const char special[256] = { + [' '] = 1, + ['\t'] = 1, + ['\r'] = 1, + ['\n'] = 1, +}; + +static const char separators[256] = { + [' '] = 1, + [','] = 1, + ['-'] = 1, + ['+'] = 1, + [':'] = 1, + ['.'] = 1, +}; + +static const char * const monthnames[12] = { + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" +}; + +static const char * const weekdays[7] = { + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat" +}; + +enum { + Alpha = 1, + UAlpha = 2, + LAlpha = 4, + Digit = 8, + TZSign = 16, +}; + +static const long charset[257] = { + ['0' + 1 ... '9' + 1] = Digit, + ['A' + 1 ... 'Z' + 1] = Alpha | UAlpha, + ['a' + 1 ... 'z' + 1] = Alpha | LAlpha, + ['+' + 1] = TZSign, + ['-' + 1] = TZSign +}; + +struct tbuf { + const char *str; + size_t len; + size_t offset; +}; + + +/* + * Returns the GMT offset of the struct tm 'tm', obtained from 'time'. + * (from Cyrus-imapd) + */ +int gmtoff_of(struct tm *tm, time_t time) +{ + struct tm local, gmt; + struct tm *gtm; + long offset; + + local = *tm; + gtm = gmtime(&time); + gmt = *gtm; + + /* Assume we are never more than 24 hours away. */ + offset = local.tm_yday - gmt.tm_yday; + if (offset > 1) { + offset = -24; + } else if (offset < -1) { + offset = 24; + } else { + offset *= 24; + } + + /* Scale in the hours and minutes; ignore seconds. */ + offset += local.tm_hour - gmt.tm_hour; + offset *= 60; + offset += local.tm_min - gmt.tm_min; + + /* Restore the data in the struct 'tm' points to */ + *tm = local; + return offset * 60; +} + +static inline int get_next_char(struct tbuf *buf) +{ + int c; + + if (buf->offset < buf->len) { + buf->offset++; + c = buf->str[buf->offset]; + return c; + } + + return EOB; +} + +static inline int get_current_char(struct tbuf *buf) +{ + size_t offset = buf->offset; + + if (offset < buf->len) + return buf->str[offset]; + else + return EOB; +} + +static inline int get_previous_char(struct tbuf *buf) +{ + int offset = buf->offset; + + offset--; + if (offset >= 0) + return buf->str[offset]; + else + return EOB; +} + +/* + TODO: Support comments as per RFC. + */ +static int skip_ws(struct tbuf *buf, int skipcomment __attribute__((unused))) +{ + int c = buf->str[buf->offset]; + + while (c != EOB) { + if (special[c]) { + c = get_next_char(buf); + continue; + } + + break; + } + + return 1; +} + +#define MAX_BUF_LEN 32 +static int get_next_token(struct tbuf *buf, char **str, int *len) +{ + int c, ret = 1; + long ch; + static char cache[MAX_BUF_LEN]; + + memset(cache, 1, MAX_BUF_LEN); + + c = get_current_char(buf); + if (c == EOB) { + ret = 0; + goto failed; + } + + *len = 0; + for (;;) { + if (special[c] || separators[c]) + break; + + ch = charset[c + 1]; + if (!(ch & (Alpha | Digit))) + break; + + if (*len >= MAX_BUF_LEN) + break; + + cache[*len] = c; + *len += 1; + + c = get_next_char(buf); + if (c == EOB) { + ret = 0; + break; + } + } + +failed: + *str = cache; + + return ret; +} + +static inline int to_int(char *str, int len) +{ + int i, num = 0; + + for (i = 0; i < len; i++) { + if (charset[str[i] + 1] & Digit) + num = num * 10 + (str[i] - '0'); + else { + num = -9999; + break; + } + } + + return num; +} + +static inline int to_upper_str_in_place(char **str, int len) +{ + int i; + + for (i = 0; i < len; i++) { + int c = str[0][i]; + if (charset[c + 1] & LAlpha) + str[0][i] = str[0][i] - 32; + } + + return 1; +} + +static inline int to_upper(char ch) +{ + if (charset[ch + 1] & LAlpha) + ch = ch - 32; + + return ch; +} + +static inline int to_lower(char ch) +{ + if (charset[ch + 1] & UAlpha) + ch = ch + 32; + + return ch; +} + +static int compute_tzoffset(char *str, int len, int sign) +{ + int offset = 0; + + if (len == 1) { /* Military timezone */ + int ch; + ch = to_upper(str[0]); + if (ch < 'J') + return (str[0] - 'A' + 1) * 60; + if (ch == 'J') + return 0; + if (ch <= 'M') + return (str[0] - 'A') * 60;; + if (ch < 'Z') + return ('M' - str[0]) * 60; + + return 0; + } + + if (len == 2 && + to_upper(str[0]) == 'U' && + to_upper(str[1]) == 'T') { /* Universal Time zone (UT) */ + return 0; + } + + if (len == 3) { + char *p; + + if (to_upper(str[2]) != 'T') + return 0; + + p = strchr("AECMPYHB", to_upper(str[0])); + if (!p) + return 0; + offset = (strlen(p) - 12) * 60; + + if (to_upper(str[1]) == 'D') + return offset + 60; + if (to_upper(str[1]) == 'S') + return offset; + } + + if (len == 4) { /* The number timezone offset */ + int i; + + for (i = 0; i < len; i++) { + if (!(charset[str[i] + 1] & Digit)) + return 0; + } + + offset = ((str[0] - '0') * 10 + (str[1] - '0')) * 60 + + (str[2] - '0') * 10 + + (str[3] - '0'); + + return (sign == '+') ? offset : -offset; + } + + return 0; +} + +/* + date-time = [ ([FWS] day-name) "," ] + ([FWS] 1*2DIGIT FWS) + month + (FWS 4*DIGIT FWS) + 2DIGIT ":" 2DIGIT [ ":" 2DIGIT ] + (FWS ( "+" / "-" ) 4DIGIT) + [CFWS] + + day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" + month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" / + "Sep" / "Oct" / "Nov" / "Dec" + */ + +static int tokenise_and_create_tm(struct tbuf *buf, struct tm *tm, + int *tz_offset, bool usetime) +{ + long ch; + int c, i, len; + char *str_token = NULL; + + /* Skip leading WS, if any */ + if (skip_ws(buf, 0) != 1) + return -1; + + c = get_current_char(buf); + if (c == EOB) + goto failed; + + ch = charset[c + 1]; + if (ch & Alpha) { /* Most likely a weekday at the start. */ + if (!get_next_token(buf, &str_token, &len)) + goto failed; + + /* We might have a weekday token here, which we should skip*/ + if (len != 3) + goto failed; + + /* The weekday is foll wed by a ',', consume that. */ + if (get_current_char(buf) == ',') + get_next_char(buf); + else + goto failed; + + if (skip_ws(buf, 0) != 1) + return -1; + } + + /** DATE **/ + /* date (1 or 2 digits) */ + if (!get_next_token(buf, &str_token, &len)) + goto failed; + + if (len < 1 || len > 2 || !(charset[str_token[0] + 1] & Digit)) + goto failed; + + tm->tm_mday = to_int(str_token, len); + if (tm->tm_mday == -9999) + goto failed; + + /* month name */ + get_next_char(buf); /* Consume a character, either a '-' or ' ' */ + + if (!get_next_token(buf, &str_token, &len) || + len != 3 || + !(charset[str_token[0] + 1] & Alpha)) + goto failed; + + str_token[0] = to_upper(str_token[0]); + str_token[1] = to_lower(str_token[1]); + str_token[2] = to_lower(str_token[2]); + for (i = 0; i < 12; i++) { + if (memcmp(monthnames[i], str_token, 3) == 0) { + tm->tm_mon = i; + break; + } + } + if (i == 12) + goto failed; + + /* year 2, 4 or >4 digits */ + get_next_char(buf); /* Consume a character, either a '-' or ' ' */ + + if (!get_next_token(buf, &str_token, &len)) + goto failed; + + tm->tm_year = to_int(str_token, len); + if (tm->tm_year == -9999) + goto failed; + + if (len == 2) { + /* A 2 digit year */ + if (tm->tm_year < 70) + tm->tm_year += 100; + } else { + if (tm->tm_year < 1900) + goto failed; + tm->tm_year -= 1900; + } + + if (!usetime) { + *tz_offset = 0; + goto done; + } + + /** TIME **/ + if (skip_ws(buf, 0) != 1) + return -1; + + /* hour */ + if (!get_next_token(buf, &str_token, &len)) + goto failed; + + if (len < 1 || len > 2 || !(charset[str_token[0] + 1] & Digit)) + goto failed; + + tm->tm_hour = to_int(str_token, len); + if (tm->tm_hour == -9999) + goto failed; + + /* minutes */ + if (get_current_char(buf) == ':' || + get_current_char(buf) == '.') + get_next_char(buf); /* Consume ':'/'.' */ + else + goto failed; /* Something is broken */ + + if (!get_next_token(buf, &str_token, &len)) + goto failed; + + if (len < 1 || len > 2 || !(charset[str_token[0] + 1] & Digit)) + goto failed; + + tm->tm_min = to_int(str_token, len); + if (tm->tm_min == -9999) + goto failed; + + + /* seconds[optional] */ + if (get_current_char(buf) == ':' || + get_current_char(buf) == '.') { + get_next_char(buf); /* Consume ':' */ + + if (!get_next_token(buf, &str_token, &len)) + goto failed; + + if (len < 1 || len > 2 || !(charset[str_token[0] + 1] & Digit)) + goto failed; + + tm->tm_sec = to_int(str_token, len); + if (tm->tm_sec == -9999) + goto failed; + + } + + /* timezone */ + if (skip_ws(buf, 0) != 1) + return -1; + + c = get_current_char(buf); /* the '+' or '-' in the timezone */ + get_next_char(buf); /* consume '+' or '-' */ + + if (!get_next_token(buf, &str_token, &len)) { + *tz_offset = 0; + } else { + *tz_offset = compute_tzoffset(str_token, len, c); + } + +done: + /* dst */ + tm->tm_isdst = -1; + return buf->offset; + +failed: + return -1; +} + +/* + rfc5322_date_parse(): + Given a date time string in RFC 5322 format, this function + parses and converts it into time_t format. + + On Success: Returns the number of characters from the date string parsed + On Failure: Returns -1 + */ +int rfc5322_date_parse(const char *str, size_t len, time_t *t, bool usetime) +{ + struct tbuf buf; + struct tm tm; + time_t tmp_time; + int tzone_offset; + + if (!str) + goto baddate; + + memset(&tm, 0, sizeof(struct tm)); + *t = 0; + + buf.str = str; + buf.len = len; + buf.offset = 0; + + if (tokenise_and_create_tm(&buf, &tm, &tzone_offset, usetime) == -1) + goto baddate; + + if (usetime) + tmp_time = timegm(&tm); + else + tmp_time = mktime(&tm); + + if (tmp_time == -1) + goto baddate; + + *t = tmp_time - tzone_offset * 60; + + return buf.offset; + +baddate: + return -1; +} + + +/* + rfc5322_date_create(): + Given a `time_t` date, this function creates a RFC5322 compliant date + string. + */ +int rfc5322_date_create(time_t date, char *buf, size_t len) +{ + struct tm *tm = localtime(&date); + long gmtoff = gmtoff_of(tm, date); + int gmtnegative = 0; + + if (gmtoff < 0) { + gmtoff = -gmtoff; + gmtnegative = 1; + } + + gmtoff /= 60; + + + return snprintf(buf, len, + "%s, %02d %s %04d %02d:%02d:%02d %c%02lu%02lu", + weekdays[tm->tm_wday], + tm->tm_mday, monthnames[tm->tm_mon], tm->tm_year + 1900, + tm->tm_hour, tm->tm_min, tm->tm_sec, + gmtnegative ? '-' : '+', gmtoff/60, gmtoff%60); +} diff --git a/dtparser.h b/dtparser.h @@ -0,0 +1,21 @@ +/* + * dtparser + * + * + * dtparser is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + * + * Copyright (c) 2017 Partha Susarla <mail@spartha.org> + */ + +#ifndef _RFC5322_DT_PARSER_H +#define _RFC5322_DT_PARSER_H + +#include <time.h> +#include <sys/time.h> +#include <stdbool.h> + +int rfc5322_date_parse(const char *str, size_t len, time_t *t, bool usetime); +int rfc5322_date_create(time_t date, char *buf, size_t len); + +#endif /* _RF5322_DT_PARSER_H */ diff --git a/example.c b/example.c @@ -5,23 +5,86 @@ #include <curl/curl.h> #include <json.h> +#include "dtparser.h" + #include "neocities.h" +#define CURL_ERROR NEOCITIES_LLVL_ERR_CURL_GLOBAL_INIT +#define OK NEOCITIES_LLVL_OK + int main(void) { - json_object *jobj; - int err; + neocities_res res; + + int i, err; + + char date[33] = { 0 }; if (curl_global_init(CURL_GLOBAL_SSL) != 0) - return NEOCITIES_ERR_CURL_GLOBAL_INIT; + return CURL_ERROR; - if ((err = - neocities_api(APIKEY, INFO, "lainzine", &jobj)) != NEOCITIES_OK) + if ((err = neocities_api_ex(APIKEY, LIST, "", &res)) != OK) return err; - printf("%s\n", - json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PRETTY)); - // fflush(stdout); + puts("## Listing 5 files ##"); + + printf("success: %d\n", res.result); + + for (i = 0; i < 5; i++) { + rfc5322_date_create(res.data.list.files[i].updated_at, date, 32); + printf("path: %s\n last_update: %s\n is_dir: %d\n size: %d\n", + res.data.list.files[i].path, date, + res.data.list.files[i].is_directory, + res.data.list.files[i].size); + } + + neocities_destroy_list(&res); + + if ((err = neocities_api_ex(APIKEY, INFO, "", &res)) != OK) + return err; + + printf("## Site infos (%s) ##\n", res.data.info.sitename); + + printf("success: %d, views: %d, hits: %d, domain: %s\n", res.result, + res.data.info.views, res.data.info.hits, res.data.info.domain); + + puts("tags:"); + + i = 0; + while (res.data.info.tags[i] != NULL) { + printf("- %s\n", res.data.info.tags[i]); + i++; + } + + rfc5322_date_create(res.data.info.created_at, date, 32); + printf("created_at: %s\n", date); + + rfc5322_date_create(res.data.info.last_updated, date, 32); + printf("last_updated: %s\n", date); + + neocities_destroy_info(&res); + + puts("## Failing deliberately ##"); + + if ((err = neocities_api_ex(APIKEY, INFO, "donotexist", &res)) != OK) { + printf("result: %d, error_type: %d\n", res.result, + res.data.error.type); + neocities_destroy_error(&res); + } else { + neocities_destroy_info(&res); + } + + puts("## Uploading a file ##"); + + if ((err = neocities_api_ex(APIKEY, UPLOAD, "deps.txt", &res)) != OK) { + printf("result: %d, error_type: %d\n", res.result, + res.data.error.type); + + // not mandatory if res only used once + neocities_destroy_error(&res); + } else { + printf("result: %d\n", res.result); + } curl_global_cleanup(); diff --git a/neocities.h b/neocities.h @@ -14,23 +14,33 @@ #define INFO BASEURL "info" #define KEY BASEURL "key" -enum neocities_error { - NEOCITIES_OK, - NEOCITIES_ERR_CURL_GLOBAL_INIT, - NEOCITIES_ERR_JSON_TOKENER_NEW, - NEOCITIES_ERR_CURL_EASY_INIT, - NEOCITIES_ERR_CURL_MIME_INIT, - NEOCITIES_ERR_CURL_MIME_ADDPART, - NEOCITIES_ERR_CURL_MIME_NAME, - NEOCITIES_ERR_CURL_MIME_FILEDATA, - NEOCITIES_ERR_CURL_MIME_DATA, - NEOCITIES_ERR_CURL_SLIST_APPEND, - NEOCITIES_ERR_CURL_EASY_SETOPT, - NEOCITIES_ERR_UNSUPPORTED_DELETE, - NEOCITIES_ERR_BAD_ACTION, - NEOCITIES_ERR_NEOCITIES_INIT, - NEOCITIES_ERR_CURL_EASY_PERFORM, - NEOCITIES_ERR_JSON_TOKENER_PARSE +enum neocities_low_level_error { + NEOCITIES_LLVL_OK, + NEOCITIES_LLVL_ERR_CURL_GLOBAL_INIT, + NEOCITIES_LLVL_ERR_JSON_TOKENER_NEW, + NEOCITIES_LLVL_ERR_CURL_EASY_INIT, + NEOCITIES_LLVL_ERR_CURL_MIME_INIT, + NEOCITIES_LLVL_ERR_CURL_MIME_ADDPART, + NEOCITIES_LLVL_ERR_CURL_MIME_NAME, + NEOCITIES_LLVL_ERR_CURL_MIME_FILEDATA, + NEOCITIES_LLVL_ERR_CURL_MIME_DATA, + NEOCITIES_LLVL_ERR_CURL_SLIST_APPEND, + NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT, + NEOCITIES_LLVL_ERR_UNSUPPORTED_DELETE, + NEOCITIES_LLVL_ERR_BAD_ACTION, + NEOCITIES_LLVL_ERR_NEOCITIES_INIT, + NEOCITIES_LLVL_ERR_CURL_EASY_PERFORM, + NEOCITIES_LLVL_ERR_JSON_TOKENER_PARSE, + NEOCITIES_LLVL_ERR_RECEIVED_UNSUPPORTED_JSON, + NEOCITIES_LLVL_ERR_RECEIVED_INVALID_JSON, + NEOCITIES_LLVL_ERR_NO_SUCCESS, + NEOCITIES_LLVL_ERR_MALLOC_FAIL +}; + +/* Not prefixed for convenience */ +enum neocities_api_level_error { + SITE_NOT_FOUND, + UNSUPPORTED_ERROR }; typedef struct Neocities_ { @@ -40,6 +50,51 @@ typedef struct Neocities_ { int pos; } Neocities; +struct neocities_info_ { + char *sitename; /* default: NULL */ + int32_t hits; /* default: -1 */ + int32_t views; + time_t created_at; /* default: 0 */ + time_t last_updated; + char *domain; + + /* you're not allowed to have more tags anyway */ + char *tags[20]; +}; + +struct neocities_list_ { + struct neocities_file_ *files; + int length; +}; + +struct neocities_file_ { + char *path; + int is_directory; + int size; + time_t updated_at; +}; + +struct neocities_error_ { + int type; +}; + +typedef struct neocities_res_ { + int result; + + union { + struct neocities_list_ list; + struct neocities_info_ info; + struct neocities_error_ error; + } data; +} neocities_res; + +enum neocities_res_data { + NEOCITIES_NO_TYPE_YET, + NEOCITIES_INFO_STRUCT, + NEOCITIES_LIST_STRUCT, + NEOCITIES_ERROR_STRUCT +}; + int neocities_init(Neocities * neocities, size_t size) { neocities->buf = malloc(sizeof(char) * size); @@ -83,11 +138,15 @@ size_t write_data(void *buffer, size_t size, size_t nmemb, while (i < nmemb) { if (neocities->pos >= neocities->size) { + neocities->size += nmemb; + neocities->buf = realloc(neocities->buf, sizeof(char) * neocities->size); + if (neocities->buf == NULL) return neocities->size; + } neocities->buf[neocities->pos] = *(char *) (buffer + i); @@ -99,8 +158,10 @@ size_t write_data(void *buffer, size_t size, size_t nmemb, return i; } -enum neocities_error neocities_api(const char *apikey, const char *action, - const char *params, json_object ** obj) +enum neocities_low_level_error neocities_api(const char *apikey, + const char *action, + const char *params, + json_object ** jobj) { Neocities neocities; @@ -119,120 +180,415 @@ enum neocities_error neocities_api(const char *apikey, const char *action, strcat(auth_bearer, apikey); if (tok == NULL) - return NEOCITIES_ERR_JSON_TOKENER_NEW; + return NEOCITIES_LLVL_ERR_JSON_TOKENER_NEW; curl = curl_easy_init(); if (curl == NULL) - return NEOCITIES_ERR_CURL_EASY_INIT; + return NEOCITIES_LLVL_ERR_CURL_EASY_INIT; - if (strcmp(action, UPLOAD) == 0) { + if (action != NULL && strcmp(action, UPLOAD) == 0) { form = curl_mime_init(curl); if (form == NULL) - return NEOCITIES_ERR_CURL_MIME_INIT; + return NEOCITIES_LLVL_ERR_CURL_MIME_INIT; field = curl_mime_addpart(form); if (field == NULL) - return NEOCITIES_ERR_CURL_MIME_ADDPART; + return NEOCITIES_LLVL_ERR_CURL_MIME_ADDPART; if (curl_mime_name(field, "sendfile") != CURLE_OK) - return NEOCITIES_ERR_CURL_MIME_NAME; + return NEOCITIES_LLVL_ERR_CURL_MIME_NAME; if (curl_mime_filedata(field, params) != CURLE_OK) - return NEOCITIES_ERR_CURL_MIME_FILEDATA; + return NEOCITIES_LLVL_ERR_CURL_MIME_FILEDATA; field = curl_mime_addpart(form); if (field == NULL) - return NEOCITIES_ERR_CURL_MIME_ADDPART; + return NEOCITIES_LLVL_ERR_CURL_MIME_ADDPART; if (curl_mime_name(field, "filename") != CURLE_OK) - return NEOCITIES_ERR_CURL_MIME_NAME; + return NEOCITIES_LLVL_ERR_CURL_MIME_NAME; if (curl_mime_data(field, params, CURL_ZERO_TERMINATED) != CURLE_OK) - return NEOCITIES_ERR_CURL_MIME_DATA; + return NEOCITIES_LLVL_ERR_CURL_MIME_DATA; field = NULL; headers = curl_slist_append(headers, "Expect:"); if (headers == NULL) - return NEOCITIES_ERR_CURL_SLIST_APPEND; + return NEOCITIES_LLVL_ERR_CURL_SLIST_APPEND; if (curl_easy_setopt(curl, CURLOPT_MIMEPOST, form) != CURLE_OK) - return NEOCITIES_ERR_CURL_EASY_SETOPT; + return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT; if (curl_easy_setopt(curl, CURLOPT_URL, action) != CURLE_OK) - return NEOCITIES_ERR_CURL_EASY_SETOPT; + return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT; - } else if (strcmp(action, DELETE) == 0) { + } else if (action != NULL && strcmp(action, DELETE) == 0) { - return NEOCITIES_ERR_UNSUPPORTED_DELETE; + return NEOCITIES_LLVL_ERR_UNSUPPORTED_DELETE; - } else if (strcmp(action, INFO) == 0 || strcmp(action, LIST) == 0 - || strcmp(action, KEY) == 0) { + } else if (action != NULL + && (strcmp(action, INFO) == 0 || strcmp(action, LIST) == 0 + || strcmp(action, KEY) == 0)) { strcpy(&url_with_get_params[0], action); if (strcmp(action, INFO) == 0 && strcmp(params, "") != 0) + strcat(url_with_get_params, "?sitename="); + else if (strcmp(action, LIST) == 0 && strcmp(params, "") != 0) + strcat(url_with_get_params, "?path="); strcat(url_with_get_params, params); if (curl_easy_setopt(curl, CURLOPT_URL, url_with_get_params) != CURLE_OK) - return NEOCITIES_ERR_CURL_EASY_SETOPT; + return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT; } else { - return NEOCITIES_ERR_BAD_ACTION; + return NEOCITIES_LLVL_ERR_BAD_ACTION; } if (neocities_init(&neocities, 700) != 0) - return NEOCITIES_ERR_NEOCITIES_INIT; + return NEOCITIES_LLVL_ERR_NEOCITIES_INIT; headers = curl_slist_append(headers, auth_bearer); if (headers == NULL) - return NEOCITIES_ERR_CURL_SLIST_APPEND; + return NEOCITIES_LLVL_ERR_CURL_SLIST_APPEND; if (curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers) != CURLE_OK) - return NEOCITIES_ERR_CURL_EASY_SETOPT; + return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT; if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data) != CURLE_OK) - return NEOCITIES_ERR_CURL_EASY_SETOPT; + return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT; if (curl_easy_setopt (curl, CURLOPT_WRITEDATA, ((Neocities *) & neocities))) - return NEOCITIES_ERR_CURL_EASY_SETOPT; + return NEOCITIES_LLVL_ERR_CURL_EASY_SETOPT; if (curl_easy_perform(curl) != CURLE_OK) - return NEOCITIES_ERR_CURL_EASY_PERFORM; + return NEOCITIES_LLVL_ERR_CURL_EASY_PERFORM; curl_slist_free_all(headers); curl_mime_free(form); curl_easy_cleanup(curl); - *obj = + *jobj = json_tokener_parse_ex(tok, (char *) neocities.buf, neocities.pos); + /* https://groups.google.com/forum/#!topic/json-c/CMvkXKXqtWs */ + if (tok->char_offset != neocities.pos) { + json_tokener_free(tok); + return NEOCITIES_LLVL_ERR_RECEIVED_INVALID_JSON; + } + + json_tokener_free(tok); + #ifdef NEOCITIES_DEBUG neocities_dump(&neocities); #endif neocities_destroy(&neocities); - if (*obj == NULL) - return NEOCITIES_ERR_JSON_TOKENER_PARSE; + if (*jobj == NULL) + return NEOCITIES_LLVL_ERR_JSON_TOKENER_PARSE; + + if (json_object_get_type(*jobj) != json_type_object) + return NEOCITIES_LLVL_ERR_RECEIVED_UNSUPPORTED_JSON; + + return NEOCITIES_LLVL_OK; +} + +void neocities_destroy_list(neocities_res * res) +{ + int i = 0; + + for (; i < res->data.list.length; i++) + free(res->data.list.files[i].path); + + if (res->data.list.files != NULL) + free(res->data.list.files); + + return; +} + +void neocities_destroy_info(neocities_res * res) +{ + int i = 0; + + while (res->data.info.tags[i] != NULL) { + free(res->data.info.tags[i]); + i++; + } + + if (res->data.info.sitename != NULL) + free(res->data.info.sitename); + + if (res->data.info.domain != NULL) + free(res->data.info.domain); + + return; +} + +void neocities_destroy_error(neocities_res *res) { + res->data.error.type = -1; + + return; +} + +enum neocities_low_level_error neocities_json_to_struct(json_object * jobj, + neocities_res * res) +{ + + json_object_iter jobj_iter, jobj_iter_info, jobj_iter_list; + + int seen_result = 0; + + int i = 0; + + char *tmp_string = NULL; + array_list *tmp_array = NULL; + json_object *tmp_object = NULL; + + enum neocities_res_data current_type = NEOCITIES_NO_TYPE_YET; + + json_object_object_foreachC(jobj, jobj_iter) { + + if (jobj_iter.key != NULL + && strcmp(jobj_iter.key, "error_type") == 0) { + + tmp_string = json_object_get_string(jobj_iter.val); + + if (tmp_string != NULL && strcmp(tmp_string, + "site_not_found") == 0) + + res->data.error.type = SITE_NOT_FOUND; + + else + + res->data.error.type = UNSUPPORTED_ERROR; + + + } else if (jobj_iter.key != NULL + && strcmp(jobj_iter.key, "result") == 0) { + + if (seen_result == 1) + return NEOCITIES_LLVL_ERR_RECEIVED_UNSUPPORTED_JSON; + else + seen_result = 1; + + tmp_string = json_object_get_string(jobj_iter.val); + + if (tmp_string != NULL && strcmp + (json_object_get_string(jobj_iter.val), "success") != 0) { + + res->result = -1; + + continue; + + } else { + + res->result = 0; + + } + + } else if (strcmp(jobj_iter.key, "info") == 0 + && current_type == NEOCITIES_NO_TYPE_YET) { + + current_type = NEOCITIES_INFO_STRUCT; + + res->data.info.sitename = NULL; + res->data.info.views = -1; + res->data.info.hits = -1; + res->data.info.created_at = (time_t) 0; + res->data.info.last_updated = (time_t) 0; + res->data.info.domain = NULL; + res->data.info.tags[0] = NULL; + + json_object_object_foreachC(jobj_iter.val, jobj_iter_info) { + + if (strcmp(jobj_iter_info.key, "views") == 0) { + + res->data.info.views = + json_object_get_int(jobj_iter_info.val); + + } else if (strcmp(jobj_iter_info.key, "hits") == 0) { + + res->data.info.hits = + json_object_get_int(jobj_iter_info.val); + + } else if (strcmp(jobj_iter_info.key, "sitename") == 0) { + + res->data.info.sitename = + strdup(json_object_get_string(jobj_iter_info.val)); + + if (res->data.info.sitename == NULL) + return NEOCITIES_LLVL_ERR_MALLOC_FAIL; + + } else if (strcmp(jobj_iter_info.key, "domain") == 0) { + + res->data.info.domain = + strdup(json_object_get_string(jobj_iter_info.val)); + + if (res->data.info.domain == NULL) + return NEOCITIES_LLVL_ERR_MALLOC_FAIL; + + } else if (strcmp(jobj_iter_info.key, "created_at") == 0) { + + tmp_string = json_object_get_string + (jobj_iter_info.val); + + rfc5322_date_parse(tmp_string, strlen(tmp_string), + &res->data.info.created_at, true); + + } else if (strcmp(jobj_iter_info.key, "last_updated") == 0) { + + tmp_string = json_object_get_string + (jobj_iter_info.val); + + rfc5322_date_parse(tmp_string, strlen(tmp_string), + &res->data.info.last_updated, true); + + } else if (strcmp(jobj_iter_info.key, "tags") == 0) { + + tmp_array = json_object_get_array(jobj_iter_info.val); + + for (i = 0; i < tmp_array->length; i++) { + + res->data.info.tags[i] = + strdup(json_object_get_string + ((tmp_array->array)[i])); + + if (res->data.info.tags[i] == NULL) + return NEOCITIES_LLVL_ERR_MALLOC_FAIL; + + + } + + res->data.info.tags[i] = NULL; + } + + } + + break; + + } else if (strcmp(jobj_iter.key, "files") == 0 + && current_type == NEOCITIES_NO_TYPE_YET) { + + current_type = NEOCITIES_LIST_STRUCT; + + res->data.list.files = NULL; + res->data.list.length = 0; + res->data.list.length = + json_object_array_length(jobj_iter.val); + + res->data.list.files = + malloc(sizeof(struct neocities_file_) * + res->data.list.length); + + if (res->data.list.files == NULL) + return NEOCITIES_LLVL_ERR_MALLOC_FAIL; + + tmp_array = json_object_get_array(jobj_iter.val); + + for (i = 0; i < res->data.list.length; i++) { + + (res->data.list.files)[i].path = NULL; + (res->data.list.files)[i].is_directory = -1; + (res->data.list.files)[i].size = -1; + (res->data.list.files)[i].updated_at = (time_t) 0; + + json_object_object_foreachC(tmp_array->array[i], + jobj_iter_list) { + + if (strcmp(jobj_iter_list.key, "path") == 0) { + + (res->data.list.files)[i].path = + strdup(json_object_get_string + (jobj_iter_list.val)); + + if ((res->data.list.files)[i].path == NULL) + return NEOCITIES_LLVL_ERR_MALLOC_FAIL; + + + } else if (strcmp(jobj_iter_list.key, "updated_at") == + 0) { + + tmp_string = json_object_get_string + (jobj_iter_list.val); + + rfc5322_date_parse(tmp_string, strlen(tmp_string), + &(res->data.list.files)[i]. + updated_at, true); + + } else if (strcmp(jobj_iter_list.key, "size") == 0) { + + (res->data.list.files)[i].size = + json_object_get_int(jobj_iter_list.val); + + } else if (strcmp(jobj_iter_list.key, "is_directory") + == 0) { + + (res->data.list.files)[i].is_directory = + (json_object_get_boolean(jobj_iter_list.val) == + TRUE ? 1 : 0); + + } else { + + /* ignore ipfs thing */ + + continue; + } + + } + + } + + break; + } else { + + if (res->result == 0) // UPLOAD + + return NEOCITIES_LLVL_OK; + + return NEOCITIES_LLVL_ERR_RECEIVED_UNSUPPORTED_JSON; + + } + } + + return NEOCITIES_LLVL_OK; +} + +enum neocities_low_level_error neocities_api_ex(const char *apikey, + const char *action, + const char *params, + neocities_res * res) +{ + int err; + + json_object *jobj = NULL; + + if ((err = + neocities_api(apikey, action, params, &jobj)) != NEOCITIES_LLVL_OK) + return err; + + err = neocities_json_to_struct(jobj, res); + + json_object_put(jobj); - return NEOCITIES_OK; + return err; } #endif