From 519af01f7dcb448b59ca13bdc1a1b060484f41ec Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Sat, 2 Feb 2019 00:23:55 +0100 Subject: Use ipsw.me API to allow selection and download of any signed firmware version when using --latest --- src/Makefile.am | 2 +- src/common.c | 45 +++++++++ src/common.h | 2 + src/download.c | 6 +- src/idevicerestore.c | 106 ++++++++++++++++++- src/idevicerestore.h | 17 ++-- src/ipsw.c | 105 ++++++++++++++++--- src/ipsw.h | 3 + src/jsmn.c | 280 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/jsmn.h | 91 +++++++++++++++++ src/json_plist.c | 229 +++++++++++++++++++++++++++++++++++++++++ src/json_plist.h | 34 +++++++ 12 files changed, 892 insertions(+), 28 deletions(-) create mode 100644 src/jsmn.c create mode 100644 src/jsmn.h create mode 100644 src/json_plist.c create mode 100644 src/json_plist.h diff --git a/src/Makefile.am b/src/Makefile.am index abbef21..1e141d1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,7 @@ AM_LDADD = $(AC_LDADD) bin_PROGRAMS = idevicerestore -idevicerestore_SOURCES = idevicerestore.c common.c tss.c fls.c mbn.c img3.c img4.c ipsw.c normal.c dfu.c recovery.c restore.c asr.c fdr.c limera1n.c download.c locking.c socket.c thread.c +idevicerestore_SOURCES = idevicerestore.c common.c tss.c fls.c mbn.c img3.c img4.c ipsw.c normal.c dfu.c recovery.c restore.c asr.c fdr.c limera1n.c download.c locking.c socket.c thread.c jsmn.c json_plist.c idevicerestore_CFLAGS = $(AM_CFLAGS) idevicerestore_LDFLAGS = $(AM_LDFLAGS) idevicerestore_LDADD = $(AM_LDADD) diff --git a/src/common.c b/src/common.c index 59e37a5..c7064d9 100644 --- a/src/common.c +++ b/src/common.c @@ -34,9 +34,11 @@ #include #include #include +#include #ifdef WIN32 #include +#include #ifndef _O_EXCL #define _O_EXCL 0x0400 #endif @@ -46,6 +48,7 @@ #else #include #include +#include #endif #include "common.h" @@ -481,3 +484,45 @@ char* strsep(char** strp, const char* delim) return s; } #endif + +#ifdef WIN32 +#define BS_CC '\b' +#define my_getch getch +#else +#define BS_CC 0x7f +static int my_getch(void) +{ + struct termios oldt, newt; + int ch; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + ch = getchar(); + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + return ch; +} +#endif + +void get_user_input(char *buf, int maxlen, int secure) +{ + int len = 0; + int c; + + while ((c = my_getch())) { + if ((c == '\r') || (c == '\n')) { + break; + } else if (isprint(c)) { + if (len < maxlen-1) + buf[len++] = c; + fputc((secure) ? '*' : c, stdout); + } else if (c == BS_CC) { + if (len > 0) { + fputs("\b \b", stdout); + len--; + } + } + } + fputs("\n", stdout); + buf[len] = 0; +} diff --git a/src/common.h b/src/common.h index 2f6beb3..dd854cb 100644 --- a/src/common.h +++ b/src/common.h @@ -144,6 +144,8 @@ void idevicerestore_progress(struct idevicerestore_client_t* client, int step, d char* strsep(char** strp, const char* delim); #endif +void get_user_input(char *buf, int maxlen, int secure); + #ifdef __cplusplus } #endif diff --git a/src/download.c b/src/download.c index bd8a40f..8bae52f 100644 --- a/src/download.c +++ b/src/download.c @@ -65,7 +65,11 @@ int download_to_buffer(const char* url, char** buf, uint32_t* length) curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, (curl_write_callback)&download_write_buffer_callback); curl_easy_setopt(handle, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(handle, CURLOPT_USERAGENT, USER_AGENT_STRING); + if (strncmp(url, "https://api.ipsw.me/", 20) == 0) { + curl_easy_setopt(handle, CURLOPT_USERAGENT, USER_AGENT_STRING " idevicerestore/" PACKAGE_VERSION); + } else { + curl_easy_setopt(handle, CURLOPT_USERAGENT, USER_AGENT_STRING); + } curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(handle, CURLOPT_URL, url); diff --git a/src/idevicerestore.c b/src/idevicerestore.c index bdcc9b6..ed8e5cf 100644 --- a/src/idevicerestore.c +++ b/src/idevicerestore.c @@ -71,6 +71,7 @@ static struct option longopts[] = { { "pwn", no_argument, NULL, 'p' }, { "no-action", no_argument, NULL, 'n' }, { "cache-path", required_argument, NULL, 'C' }, + { "no-input", no_argument, NULL, 'y' }, { NULL, 0, NULL, 0 } }; @@ -99,6 +100,7 @@ void usage(int argc, char* argv[]) { printf(" \tthe on demand ipsw download is performed before exiting.\n"); printf(" -C, --cache-path DIR\tUse specified directory for caching extracted\n"); printf(" \tor other reused files.\n"); + printf(" -y, --no-input\t\tNon-interactive mode, do not ask for any input\n"); printf("\n"); printf("Homepage: <" PACKAGE_URL ">\n"); } @@ -318,10 +320,96 @@ int idevicerestore_start(struct idevicerestore_client_t* client) } if (client->flags & FLAG_LATEST) { - // update version data (from cache, or apple if too old) - load_version_data(client); + char *fwurl = NULL; + unsigned char fwsha1[20]; + unsigned char *p_fwsha1 = NULL; + plist_t signed_fws = NULL; + int res = ipsw_get_signed_firmwares(client->device->product_type, &signed_fws); + if (res < 0) { + error("ERROR: Could not fetch list of signed firmwares.\n"); + return res; + } + uint32_t count = plist_array_get_size(signed_fws); + if (count == 0) { + plist_free(signed_fws); + error("ERROR: No firmwares are currently being signed for %s (REALLY?!)\n", client->device->product_type); + return -1; + } + plist_t selected_fw = NULL; + if (client->flags & FLAG_INTERACTIVE) { + uint32_t i = 0; + info("The following firmwares are currently being signed for %s:\n", client->device->product_type); + for (i = 0; i < count; i++) { + plist_t fw = plist_array_get_item(signed_fws, i); + plist_t p_version = plist_dict_get_item(fw, "version"); + plist_t p_build = plist_dict_get_item(fw, "buildid"); + char *s_version = NULL; + char *s_build = NULL; + plist_get_string_val(p_version, &s_version); + plist_get_string_val(p_build, &s_build); + info(" [%d] %s (build %s)\n", i+1, s_version, s_build); + free(s_version); + free(s_build); + } + while (1) { + char input[64]; + printf("Select the firmware you want to restore: "); + fflush(stdout); + fflush(stdin); + get_user_input(input, 63, 0); + unsigned long selected = strtoul(input, NULL, 10); + if (selected == 0 || selected > count) { + printf("Invalid input value. Must be in range: 1..%d\n", count); + continue; + } + selected_fw = plist_array_get_item(signed_fws, (uint32_t)selected-1); + break; + } + } else { + info("NOTE: Running non-interactively, automatically selecting latest available version\n"); + selected_fw = plist_array_get_item(signed_fws, 0); + } + if (!selected_fw) { + error("ERROR: failed to select latest firmware?!\n"); + plist_free(signed_fws); + return -1; + } else { + plist_t p_version = plist_dict_get_item(selected_fw, "version"); + plist_t p_build = plist_dict_get_item(selected_fw, "buildid"); + char *s_version = NULL; + char *s_build = NULL; + plist_get_string_val(p_version, &s_version); + plist_get_string_val(p_build, &s_build); + info("Selected firmware %s (build %s)\n", s_version, s_build); + free(s_version); + free(s_build); + plist_t p_url = plist_dict_get_item(selected_fw, "url"); + plist_t p_sha1 = plist_dict_get_item(selected_fw, "sha1sum"); + char *s_sha1 = NULL; + plist_get_string_val(p_url, &fwurl); + plist_get_string_val(p_sha1, &s_sha1); + if (strlen(s_sha1) == 40) { + int i; + int v; + for (i = 0; i < 40; i+=2) { + v = 0; + sscanf(s_sha1+i, "%02x", &v); + fwsha1[i/2] = (unsigned char)v; + } + p_fwsha1 = &fwsha1[0]; + } else { + error("ERROR: unexpected size of sha1sum\n"); + } + } + plist_free(signed_fws); + + if (!fwurl || !p_fwsha1) { + error("ERROR: Missing firmware URL or SHA1\n"); + return -1; + } + char* ipsw = NULL; - int res = ipsw_download_latest_fw(client->version_data, client->device->product_type, client->cache_dir, &ipsw); + res = ipsw_download_fw(fwurl, p_fwsha1, client->cache_dir, &ipsw); if (res != 0) { if (ipsw) { free(ipsw); @@ -1063,7 +1151,13 @@ int main(int argc, char* argv[]) { return -1; } - while ((opt = getopt_long(argc, argv, "dhcesxtpli:u:nC:k", longopts, &optindex)) > 0) { + if (!isatty(fileno(stdin)) || !isatty(fileno(stdout))) { + client->flags &= ~FLAG_INTERACTIVE; + } else { + client->flags |= FLAG_INTERACTIVE; + } + + while ((opt = getopt_long(argc, argv, "dhcesxtpli:u:nC:ky", longopts, &optindex)) > 0) { switch (opt) { case 'h': usage(argc, argv); @@ -1131,6 +1225,10 @@ int main(int argc, char* argv[]) { client->cache_dir = strdup(optarg); break; + case 'y': + client->flags &= ~FLAG_INTERACTIVE; + break; + default: usage(argc, argv); return -1; diff --git a/src/idevicerestore.h b/src/idevicerestore.h index 0338dde..3eb4bfc 100644 --- a/src/idevicerestore.h +++ b/src/idevicerestore.h @@ -34,14 +34,15 @@ extern "C" { #include // the flag with value 1 is reserved for internal use only. don't use it. -#define FLAG_DEBUG 1 << 1 -#define FLAG_ERASE 1 << 2 -#define FLAG_CUSTOM 1 << 3 -#define FLAG_EXCLUDE 1 << 4 -#define FLAG_PWN 1 << 5 -#define FLAG_NOACTION 1 << 6 -#define FLAG_SHSHONLY 1 << 7 -#define FLAG_LATEST 1 << 8 +#define FLAG_DEBUG (1 << 1) +#define FLAG_ERASE (1 << 2) +#define FLAG_CUSTOM (1 << 3) +#define FLAG_EXCLUDE (1 << 4) +#define FLAG_PWN (1 << 5) +#define FLAG_NOACTION (1 << 6) +#define FLAG_SHSHONLY (1 << 7) +#define FLAG_LATEST (1 << 8) +#define FLAG_INTERACTIVE (1 << 9) struct idevicerestore_client_t; diff --git a/src/ipsw.c b/src/ipsw.c index 5b0a350..c487edd 100644 --- a/src/ipsw.c +++ b/src/ipsw.c @@ -32,6 +32,7 @@ #include "download.h" #include "common.h" #include "idevicerestore.h" +#include "json_plist.h" #define BUFSIZE 0x100000 @@ -283,6 +284,67 @@ void ipsw_close(ipsw_archive* archive) { } } +int ipsw_get_signed_firmwares(const char* product, plist_t* firmwares) +{ + char url[256]; + char *jdata = NULL; + uint32_t jsize = 0; + plist_t dict = NULL; + plist_t node = NULL; + plist_t fws = NULL; + uint32_t count = 0; + uint32_t i = 0; + + if (!product || !firmwares) { + return -1; + } + + *firmwares = NULL; + snprintf(url, sizeof(url), "https://api.ipsw.me/v3/device/%s", product); + + if (download_to_buffer(url, &jdata, &jsize) < 0) { + error("ERROR: Download from %s failed.\n", url); + return -1; + } + dict = json_to_plist(jdata); + free(jdata); + if (!dict || plist_get_node_type(dict) != PLIST_DICT) { + error("ERROR: Failed to parse json data.\n"); + plist_free(dict); + return -1; + } + + node = plist_dict_get_item(dict, product); + if (!node || plist_get_node_type(node) != PLIST_DICT) { + error("ERROR: Unexpected json data returned?!\n"); + plist_free(dict); + return -1; + } + fws = plist_dict_get_item(node, "firmwares"); + if (!fws || plist_get_node_type(fws) != PLIST_ARRAY) { + error("ERROR: Unexpected json data returned?!\n"); + plist_free(dict); + return -1; + } + + *firmwares = plist_new_array(); + count = plist_array_get_size(fws); + for (i = 0; i < count; i++) { + plist_t fw = plist_array_get_item(fws, i); + node = plist_dict_get_item(fw, "signed"); + if (node && plist_get_node_type(node) == PLIST_BOOLEAN) { + uint8_t bv = 0; + plist_get_bool_val(node, &bv); + if (bv) { + plist_array_append_item(*firmwares, plist_copy(fw)); + } + } + } + plist_free(dict); + + return 0; +} + int ipsw_get_latest_fw(plist_t version_data, const char* product, char** fwurl, unsigned char* sha1buf) { *fwurl = NULL; @@ -422,17 +484,8 @@ static int sha1_verify_fp(FILE* f, unsigned char* expected_sha1) return (memcmp(expected_sha1, tsha1, 20) == 0) ? 1 : 0; } -int ipsw_download_latest_fw(plist_t version_data, const char* product, const char* todir, char** ipswfile) +int ipsw_download_fw(const char *fwurl, unsigned char* isha1, const char* todir, char** ipswfile) { - char* fwurl = NULL; - unsigned char isha1[20]; - - *ipswfile = NULL; - - if ((ipsw_get_latest_fw(version_data, product, &fwurl, isha1) < 0) || !fwurl) { - error("ERROR: can't get URL for latest firmware\n"); - return -1; - } char* fwfn = strrchr(fwurl, '/'); if (!fwfn) { error("ERROR: can't get local filename for firmware ipsw\n"); @@ -440,8 +493,6 @@ int ipsw_download_latest_fw(plist_t version_data, const char* product, const cha } fwfn++; - info("Latest firmware is %s\n", fwfn); - char fwlfn[256]; if (todir) { sprintf(fwlfn, "%s/%s", todir, fwfn); @@ -483,7 +534,7 @@ int ipsw_download_latest_fw(plist_t version_data, const char* product, const cha res = -3; } else { remove(fwlfn); - info("Downloading latest firmware (%s)\n", fwurl); + info("Downloading firmware (%s)\n", fwurl); download_to_file(fwurl, fwlfn, 1); if (memcmp(isha1, zsha1, 20) != 0) { info("\nVerifying '%s'...\n", fwlfn); @@ -507,7 +558,6 @@ int ipsw_download_latest_fw(plist_t version_data, const char* product, const cha } } } - free(fwurl); if (res == 0) { *ipswfile = strdup(fwlfn); } @@ -518,3 +568,30 @@ int ipsw_download_latest_fw(plist_t version_data, const char* product, const cha return res; } + +int ipsw_download_latest_fw(plist_t version_data, const char* product, const char* todir, char** ipswfile) +{ + char* fwurl = NULL; + unsigned char isha1[20]; + + *ipswfile = NULL; + + if ((ipsw_get_latest_fw(version_data, product, &fwurl, isha1) < 0) || !fwurl) { + error("ERROR: can't get URL for latest firmware\n"); + return -1; + } + char* fwfn = strrchr(fwurl, '/'); + if (!fwfn) { + error("ERROR: can't get local filename for firmware ipsw\n"); + return -2; + } + fwfn++; + + info("Latest firmware is %s\n", fwfn); + + int res = ipsw_download_fw(fwurl, isha1, todir, ipswfile); + + free(fwurl); + + return res; +} diff --git a/src/ipsw.h b/src/ipsw.h index c4a1a4e..1c19e92 100644 --- a/src/ipsw.h +++ b/src/ipsw.h @@ -47,6 +47,9 @@ int ipsw_extract_build_manifest(const char* ipsw, plist_t* buildmanifest, int *t int ipsw_extract_restore_plist(const char* ipsw, plist_t* restore_plist); void ipsw_free_file(ipsw_file* file); +int ipsw_get_signed_firmwares(const char* product, plist_t* firmwares); +int ipsw_download_fw(const char *fwurl, unsigned char* isha1, const char* todir, char** ipswfile); + int ipsw_get_latest_fw(plist_t version_data, const char* product, char** fwurl, unsigned char* sha1buf); int ipsw_download_latest_fw(plist_t version_data, const char* product, const char* todir, char** ipswfile); diff --git a/src/jsmn.c b/src/jsmn.c new file mode 100644 index 0000000..ddc440d --- /dev/null +++ b/src/jsmn.c @@ -0,0 +1,280 @@ +/* + * jsmn.c + * Simple JSON parser + * + * Copyright (c) 2010 Serge A. Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return JSMN_SUCCESS; +} + +/** + * Filsl next token with JSON string. + */ +static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return JSMN_SUCCESS; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\') { + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + /* TODO */ + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, + unsigned int num_tokens) { + jsmnerr_t r; + int i; + jsmntok_t *token; + + for (; js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, tokens, num_tokens); + if (r < 0) return r; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, tokens, num_tokens); + if (r < 0) return r; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + + } + } + + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + + return JSMN_SUCCESS; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + diff --git a/src/jsmn.h b/src/jsmn.h new file mode 100644 index 0000000..376ee83 --- /dev/null +++ b/src/jsmn.h @@ -0,0 +1,91 @@ +/* + * jsmn.h + * Simple JSON parser (header file) + * + * Copyright (c) 2010 Serge A. Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef __JSMN_H_ +#define __JSMN_H_ + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_PRIMITIVE = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3 +} jsmntype_t; + +typedef enum { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3, + /* Everything was fine */ + JSMN_SUCCESS = 0 +} jsmnerr_t; + +/** + * JSON token description. + * @param type type (object, array, string etc.) + * @param start start position in JSON data string + * @param end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * a single JSON object. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, unsigned int num_tokens); + +#endif /* __JSMN_H_ */ diff --git a/src/json_plist.c b/src/json_plist.c new file mode 100644 index 0000000..e61e3cb --- /dev/null +++ b/src/json_plist.c @@ -0,0 +1,229 @@ +/* + * json_plist.c + * JSON/property list functions + * + * Copyright (c) 2013 Nikias Bassen. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include +#include + +#include +#include + +#include "json_plist.h" + +static plist_t parse_primitive(const char* js, jsmntok_t* tokens, int* index); +static plist_t parse_string(const char* js, jsmntok_t* tokens, int* index); +static plist_t parse_array(const char* js, jsmntok_t* tokens, int* index); +static plist_t parse_object(const char* js, jsmntok_t* tokens, int* index); + +static char* get_string_value(const char* js, jsmntok_t token) +{ + int len = (token.end - token.start); + char* str = malloc(len+1); + memcpy(str, js + token.start, len); + str[len] = 0; + return str; +} + +static plist_t parse_primitive(const char* js, jsmntok_t* tokens, int* index) +{ + if (tokens[*index].type != JSMN_PRIMITIVE) { + fprintf(stderr, "%s: ERROR: token type != JSMN_PRIMITIVE?!\n", __func__); + return NULL; + } + plist_t val = NULL; + char* strval = get_string_value(js, tokens[*index]); + if (strval[0] == 'f') { + val = plist_new_bool(0); + } else if (strval[0] == 't') { + val = plist_new_bool(1); + } else if ((strval[0] == '-') || ((strval[0] >= '0') && (strval[0] <= '9'))) { + val = plist_new_uint(strtoll(strval, NULL, 10)); + } else { + fprintf(stderr, "%s: WARNING: invalid primitive value '%s' encountered, will return as string\n", __func__, strval); + val = plist_new_string(strval); + } + free(strval); + (*index)++; + return val; +} + +static plist_t parse_string(const char* js, jsmntok_t* tokens, int* index) +{ + if (tokens[*index].type != JSMN_STRING) { + fprintf(stderr, "%s: ERROR: token type != JSMN_STRING?!\n", __func__); + return NULL; + } + char* str = get_string_value(js, tokens[*index]); + plist_t val = plist_new_string(str); + free(str); + (*index)++; + return val; +} + +static plist_t parse_array(const char* js, jsmntok_t* tokens, int* index) +{ + if (tokens[*index].type != JSMN_ARRAY) { + fprintf(stderr, "%s: ERROR: token type != JSMN_ARRAY?!\n", __func__); + return NULL; + } + plist_t arr = plist_new_array(); + int num_tokens = tokens[*index].size; + int num; + int j = (*index)+1; + for (num = 0; num < num_tokens; num++) { + plist_t val = NULL; + switch (tokens[j].type) { + case JSMN_OBJECT: + val = parse_object(js, tokens, &j); + break; + case JSMN_ARRAY: + val = parse_array(js, tokens, &j); + break; + case JSMN_STRING: + val = parse_string(js, tokens, &j); + break; + case JSMN_PRIMITIVE: + val = parse_primitive(js, tokens, &j); + break; + default: + break; + } + if (val) { + plist_array_append_item(arr, val); + } + } + *(index) = j; + return arr; +} + +static plist_t parse_object(const char* js, jsmntok_t* tokens, int* index) +{ + if (tokens[*index].type != JSMN_OBJECT) { + fprintf(stderr, "%s: ERROR: token type != JSMN_OBJECT?!\n", __func__); + return NULL; + } + plist_t obj = plist_new_dict(); + int num_tokens = tokens[*index].size; + int num; + int j = (*index)+1; + for (num = 0; num < num_tokens; num++) { + if (tokens[j].type == JSMN_STRING) { + char* key = get_string_value(js, tokens[j]); + plist_t val = NULL; + j++; + num++; + switch (tokens[j].type) { + case JSMN_OBJECT: + val = parse_object(js, tokens, &j); + break; + case JSMN_ARRAY: + val = parse_array(js, tokens, &j); + break; + case JSMN_STRING: + val = parse_string(js, tokens, &j); + break; + case JSMN_PRIMITIVE: + val = parse_primitive(js, tokens, &j); + break; + default: + break; + } + if (val) { + plist_dict_set_item(obj, key, val); + } + free(key); + } else { + fprintf(stderr, "%s: keys must be of type STRING\n", __func__); + return NULL; + } + } + (*index) = j; + return obj; +} + +plist_t json_to_plist(const char* json_string) +{ + jsmn_parser parser; + jsmn_init(&parser); + int maxtoks = 256; + jsmntok_t *tokens; + + if (!json_string) { + fprintf(stderr, "%s: ERROR: no JSON string given.\n", __func__); + return NULL; + } + + tokens = malloc(sizeof(jsmntok_t)*maxtoks); + if (!tokens) { + fprintf(stderr, "%s: Out of memory\n", __func__); + return NULL; + } + + int r = 0; +reparse: + r = jsmn_parse(&parser, json_string, tokens, maxtoks); + if (r == JSMN_ERROR_NOMEM) { + //printf("not enough tokens (%d), retrying...\n", maxtoks); + maxtoks+=256; + jsmntok_t* newtokens = realloc(tokens, sizeof(jsmntok_t)*maxtoks); + if (newtokens) { + tokens = newtokens; + goto reparse; + } + } + + switch(r) { + case JSMN_ERROR_NOMEM: + fprintf(stderr, "%s: ERROR: Out of memory...\n", __func__); + return NULL; + case JSMN_ERROR_INVAL: + fprintf(stderr, "%s: ERROR: Invalid character inside JSON string\n", __func__); + return NULL; + case JSMN_ERROR_PART: + fprintf(stderr, "%s: ERROR: The string is not a full JSON packet, more bytes expected\n", __func__); + return NULL; + default: + break; + } + + int startindex = 0; + plist_t plist = NULL; + switch (tokens[startindex].type) { + case JSMN_PRIMITIVE: + plist = parse_primitive(json_string, tokens, &startindex); + break; + case JSMN_STRING: + plist = parse_string(json_string, tokens, &startindex); + break; + case JSMN_ARRAY: + plist = parse_array(json_string, tokens, &startindex); + break; + case JSMN_OBJECT: + plist = parse_object(json_string, tokens, &startindex); + break; + default: + break; + } + + free(tokens); + + return plist; +} + diff --git a/src/json_plist.h b/src/json_plist.h new file mode 100644 index 0000000..d6eec0b --- /dev/null +++ b/src/json_plist.h @@ -0,0 +1,34 @@ +/* + * json_plist.h + * JSON/property list functions (header file) + * + * Copyright (c) 2013 Nikias Bassen. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef __JSON_PLIST_H +#define __JSON_PLIST_H + +#ifdef __cplusplus +extern "C" { +#endif + +plist_t json_to_plist(const char* json_string); + +#ifdef __cplusplus +} +#endif + +#endif -- cgit v1.1-32-gdbae