diff options
Diffstat (limited to 'src/version.c')
| -rw-r--r-- | src/version.c | 204 |
1 files changed, 203 insertions, 1 deletions
diff --git a/src/version.c b/src/version.c index d8f264c..a5e63a0 100644 --- a/src/version.c +++ b/src/version.c @@ -21,6 +21,8 @@ const char *const l2_version__messages[] = { "Allocation failed", "OS error", "Error downloading version manifest", + "Version not found", + "Max recursion depth exceeded", NULL }; @@ -46,12 +48,183 @@ done: return r; } -unsigned l2_version_load_local(const char *name) +void l2_version__load_local(const char *id, size_t expectsz, l2_sha1_digest_t *digest, json_t **ojson); +bool l2_version__download_remote(struct l2_version_remote *remote, json_t **ojson); + +unsigned l2_version__load_or_download(const char *name, json_t **ojs) { + json_t *js = NULL; + struct l2_version_remote *remote = NULL; + for (struct l2_version_remote *cur = l2_state.ver_remote_head; cur; cur = cur->next) { + if (!strcmp(cur->id, name)) { + remote = cur; + break; + } + } + + if (remote) { + l2_version__load_local(remote->id, 0, &remote->sha1, &js); + if (!js) { + CMD_DEBUG0("local version not found, downloading"); + if (!l2_version__download_remote(remote, &js) || !js) + return VERSION_EDOWNLOAD; + } + } else { + l2_version__load_local(remote->id, 0, NULL, &js); + if (!js) return VERSION_ENOTFOUND; + } + + *ojs = js; return VERSION_SUCCESS; } +unsigned l2_version__load_recursive(const char *name, json_t *target, int depth) +{ + json_t *other = NULL; + json_t *inherit; + + if (depth <= 0) return VERSION_ERECURSE; + + unsigned res = l2_version__load_or_download(name, &other); + + if (res != VERSION_SUCCESS) return res; + l2_json_merge_objects(target, other); + + inherit = json_object_get(other, "inheritsFrom"); + if (!json_is_string(inherit)) { + json_decref(other); + return VERSION_SUCCESS; + } + + json_decref(other); + return l2_version__load_recursive(json_string_value(inherit), target, depth - 1); +} + +unsigned l2_version_load_local(const char *name, json_t **ojson) +{ + json_t *js = json_object(); + if (!js) return VERSION_EJSON; + + unsigned res = l2_version__load_recursive(name, js, 10); + *ojson = js; + return res; +} + +void l2_version__load_local(const char *id, size_t expectsz, l2_sha1_digest_t *digest, json_t **ojson) +{ + char *path; + size_t temp; + L2_ASPRINTF(path, temp, "%s/versions/%s/%s.json", l2_state.paths.data, id, id); + + FILE *ver = fopen(path, "r"); + if (!ver) return; + + int res = l2_version_check_integrity(ver, digest, expectsz); + if (res < 0) { + CMD_WARN("Error checking version file integrity for %s", id); + return; + } else if (res == 0) { + CMD_DEBUG("Version %s (%s) has bad integrity", id, path); + return; + } + + json_t *js; + json_error_t jerr; + + rewind(ver); + js = json_loadf(ver, JSON_REJECT_DUPLICATES, &jerr); + if (!js) { + CMD_WARN("JSON error loading %s: %s", id, jerr.text); + return; + } + + *ojson = js; +} + +/* downloads a remote version */ +bool l2_version__download_remote(struct l2_version_remote *remote, json_t **ojson) +{ + char *dirname; + char *fname; + size_t temp; + FILE *save = NULL; + json_t *js = NULL; + L2_ASPRINTF(dirname, temp, "%s/versions/%s", l2_state.paths.data, remote->id); + L2_ASPRINTF(fname, temp, "%s/%s.json", dirname, remote->id); + + if (l2_launcher_mkdir_parents(dirname) < 0) { + CMD_ERROR("Could not create directory for %s: %s", remote->id, strerror(errno)); + return false; + } + + CURL *cu = curl_easy_init(); + l2_sha1_state_t st; + l2_sha1_digest_t dig; + json_error_t err; + if (!cu) return false; + + void *data = NULL; + char errbuf[CURL_ERROR_SIZE]; + size_t sz; + + memset(errbuf, 0, CURL_ERROR_SIZE * sizeof(char)); + + curl_easy_setopt(cu, CURLOPT_ERRORBUFFER, errbuf); + CURLcode code = l2_launcher_download(cu, remote->url, &data, &sz); + + if (code != CURLE_OK) { + CMD_WARN("Error downloading version %s: %s: %s", remote->id, curl_easy_strerror(code), errbuf); + goto cleanup; + } + + l2_sha1_init(&st); + l2_sha1_update(&st, data, sz); + l2_sha1_finalize(&st, &dig); + + if (l2_sha1_digest_compare(&dig, &remote->sha1)) { + char d1[L2_SHA1_DIGESTLEN + 1]; + char d2[L2_SHA1_DIGESTLEN + 1]; + + l2_sha1_digest_to_hex(&dig, d1); + l2_sha1_digest_to_hex(&remote->sha1, d2); + + CMD_WARN("Downloaded file for %s has incorrect hash: expected %s, got %s!", remote->id, d2, d1); + goto cleanup; + } + + js = json_loadb(data, sz, JSON_REJECT_DUPLICATES, &err); + if (!js) { + CMD_WARN("JSON parse error reading version %s: %s", remote->id, err.text); + goto cleanup; + } + + save = fopen(fname, "w"); + if (!save) { + CMD_WARN("Error opening version file %s", fname); + goto cleanup; + } + + if (fwrite(data, sz, 1, save) < 1) { + CMD_WARN("Error saving version info to %s", fname); + goto cleanup; + } + + *ojson = js; + + curl_easy_cleanup(cu); + fclose(save); + free(data); + return true; + +cleanup: + curl_easy_cleanup(cu); + free(data); + if (save) fclose(save); + if (js) json_decref(js); + return false; +} + char *l2_version__read_etag(time_t *lastmod) { size_t dpathlen = strlen(l2_state.paths.data); @@ -386,3 +559,32 @@ cleanup: return res; } + +int l2_version_check_integrity(FILE *fp, l2_sha1_digest_t *digest, size_t sz) +{ + #define VER_READBUF_SZ (1024) + + size_t len = 0, nread; + uint8_t buf[VER_READBUF_SZ]; + l2_sha1_digest_t rdigest; + l2_sha1_state_t st; + + l2_sha1_init(&st); + + while ((nread = fread(buf, 1, VER_READBUF_SZ, fp)) > 0) { + len += nread; + l2_sha1_update(&st, buf, nread); + } + + if (ferror(fp)) return -1; + + l2_sha1_finalize(&st, &rdigest); + + if (sz > 0 && sz != len) return 0; + + if (digest) { + return !l2_sha1_digest_compare(&rdigest, digest) ? 1 : 0; + } else { + return 1; + } +} |
