diff options
| author | 2023-12-28 06:40:07 -0600 | |
|---|---|---|
| committer | 2023-12-28 06:40:07 -0600 | |
| commit | cfe01c9f8b7ac0d82e694323b60fc172e0c35a48 (patch) | |
| tree | 6f02144b38fcb2a4c5116e412f9ed4b0cc711728 /src/version.c | |
| parent | add cheap SHA1 implementation (diff) | |
[wip] version stuff
Diffstat (limited to 'src/version.c')
| -rw-r--r-- | src/version.c | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..b442f85 --- /dev/null +++ b/src/version.c @@ -0,0 +1,327 @@ +#include "version.h" +#include "digest/digest.h" +#include "l2su.h" +#include "macros.h" +#include "endian.h" +#include "command.h" + +#include <curl/curl.h> +#include <curl/easy.h> +#include <curl/header.h> +#include <jansson.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <alloca.h> + +const char *const l2_version__messages[] = { + "Success", + "Malformed version manifest", + "Version manifest JSON error", + "Allocation failed", + "OS error", + "An unspecified error has occurred", + NULL +}; + +#define L2_VERSION_MANIFEST_ETAG_PATH "/.l2_vermanifest_etag" +#define L2_VERSION_MANIFEST_PATH "version_manifest_v2.json" +#define L2_VERSION_MANIFEST_EXP_TIME ((time_t)120) + +unsigned l2_version__download_manifest(json_t **out_json); +unsigned l2_version__load_all_from_json(json_t *json); + +unsigned l2_version__add_remote(json_t *obj); + +unsigned l2_version_load_remote(void) +{ + json_t *vers; + return l2_version__download_manifest(&vers); +} + +unsigned l2_version_load_local(void) +{ + return VERSION_SUCCESS; +} + +char *l2_version__read_etag(time_t *lastmod) +{ + size_t dpathlen = strlen(l2_state.paths.data); + char *etagpath; + + L2_ASTRCAT2(etagpath, l2_state.paths.data, dpathlen, L2_VERSION_MANIFEST_ETAG_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_ETAG_PATH)); + + FILE *etagfp = fopen(etagpath, "rb"); + char *etag = NULL; + uint32_t buf; + uint64_t ts; + + if (!etagfp) return NULL; + if (fread(&buf, sizeof(uint32_t), 1, etagfp) < sizeof(uint32_t)) goto cleanup; + if (fread(&ts, sizeof(uint64_t), 1, etagfp) < sizeof(uint64_t)) goto cleanup; + + buf = l2_betoh32(buf); + ts = l2_betoh64(ts); + + etag = calloc(buf + 1, 1); + if (!etag) goto cleanup; + + if (fread(etag, 1, buf, etagfp) < buf) goto cleanup; + etag[buf] = '\0'; + + *lastmod = (time_t)ts; + + fclose(etagfp); + + return etag; + +cleanup: + free(etag); + fclose(etagfp); + return NULL; +} + +/* this function does not report errors on failure because its operation is not essential */ +void l2_version__write_etag(time_t modtime, const char *etag) +{ + size_t dpathlen = strlen(l2_state.paths.data); + char *etagpath; + + L2_ASTRCAT2(etagpath, l2_state.paths.data, dpathlen, L2_VERSION_MANIFEST_ETAG_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_ETAG_PATH)); + + uint32_t etaglen = l2_betoh32((uint32_t)strlen(etag)); + uint64_t mt_write = l2_betoh64((uint64_t)modtime); + + FILE *etagfp = fopen(etagpath, "wb"); + if (!etagfp) return; + + if (fwrite(&etaglen, sizeof(uint32_t), 1, etagfp) < sizeof(uint32_t)) goto cleanup; + if (fwrite(&mt_write, sizeof(uint64_t), 1, etagfp) < sizeof(uint64_t)) goto cleanup; + if (fwrite(etag, 1, etaglen, etagfp) < etaglen) goto cleanup; + +cleanup: + fclose(etagfp); +} + +int l2_version__read_manifest_file(json_t **manifest) +{ + size_t dpathlen = strlen(l2_state.paths.data); + char *manpath; + + L2_ASTRCAT2(manpath, l2_state.paths.data, dpathlen, L2_VERSION_MANIFEST_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_PATH)); + + json_error_t err; + json_t *man = json_load_file(manpath, JSON_REJECT_DUPLICATES, &err); + if (!man) { + CMD_WARN("Error loading manifest file: %s\n", err.text); + return -1; + } + + *manifest = man; + return 0; +} + +#if 0 +unsigned l2_version__download_manifest(json_t **out_versions) +{ + unsigned ret = VERSION_SUCCESS; + CURL *pc = NULL; + FILE *outfile = NULL; + void *verdata = NULL; + int en; + + struct curl_slist *headers = NULL; + + time_t lastmod; + char *etag = l2_version__read_etag(&lastmod); + + if (etag) { + if (lastmod + L2_VERSION_MANIFEST_EXP_TIME > time(NULL)) { + /* no need to redownload */ + *out_versions = NULL; + return VERSION_SUCCESS; + } + } + + pc = curl_easy_init(); + struct l2_dlbuf dlbuf; + if (!pc) return VERSION_EUNSPEC; + + l2_launcher_download_init(&dlbuf); + + if (etag) { + size_t etaglen = strlen(etag); + char *etagheader; + +#define H_IFNONEMATCH "If-None-Match: " + L2_ASTRCAT2(etagheader, H_IFNONEMATCH, L2_CSTRLEN(H_IFNONEMATCH), etag, etaglen); +#undef H_IFNONEMATCH + + headers = curl_slist_append(NULL, etagheader); + if (!headers) { + ret = VERSION_EUNSPEC; + goto cleanup; + } + + curl_easy_setopt(pc, CURLOPT_HTTPHEADER, headers); + } + + curl_easy_setopt(pc, CURLOPT_URL, L2_URL_META_VERSION_MANIFEST); + curl_easy_setopt(pc, CURLOPT_WRITEFUNCTION, l2_dlcb); + curl_easy_setopt(pc, CURLOPT_WRITEDATA, &dlbuf); + + if (curl_easy_perform(pc) != CURLE_OK) { + /* TODO: set error string */ + ret = VERSION_EUNSPEC; + goto cleanup; + } + + long rescode = 0; + curl_easy_getinfo(pc, CURLINFO_RESPONSE_CODE, &rescode); + if (rescode == 200) { + char *mfpath; + size_t datalen; + verdata = l2_launcher_download_finalize(&dlbuf, &datalen); + + L2_ASTRCAT2(mfpath, l2_state.paths.data, pathlen, L2_VERSION_MANIFEST_PATH, L2_CSTRLEN(L2_VERSION_MANIFEST_PATH)); + + outfile = fopen(mfpath, "w"); + if (!outfile) { + ret = VERSION_ERRNO; + goto cleanup; + } + + if (fwrite(verdata, 1, datalen, outfile) < datalen) { + ret = VERSION_EUNSPEC; + goto cleanup; + } + + fclose(outfile); + outfile = NULL; + } + + struct curl_header *ch; + if (curl_easy_header(pc, "ETag", 0, CURLH_HEADER | CURLH_TRAILER | CURLH_1XX, 0, &ch) == CURLHE_OK) { + l2_version__write_etag(etagpath, time(NULL), ch->value); + } + + ret = VERSION_SUCCESS; + +cleanup: + en = errno; + + if (pc) curl_easy_cleanup(pc); + if (headers) curl_slist_free_all(headers); + if (outfile) fclose(outfile); + free(verdata); + l2_launcher_download_cleanup(&dlbuf); + + errno = en; + return ret; +} +#endif + +/* parses an instance of version_manifest_v2.json */ +unsigned l2_version__load_all_from_json(json_t *json) +{ + if (!json_is_object(json)) { + return VERSION_EFORMAT; + } + + json_t *latest = json_object_get(json, "latest"); + json_t *versions = json_object_get(json, "versions"); + + if (!json_is_object(latest) || !json_is_array(versions)) { + return VERSION_EFORMAT; + } + + const char *latestrel = json_string_value(json_object_get(latest, "release")); + const char *latestsnap = json_string_value(json_object_get(latest, "snapshot")); + size_t index; + json_t *value; + unsigned res; + + if (!latestrel || !latestsnap) { + return VERSION_EFORMAT; + } + + json_array_foreach(versions, index, value) { + if ((res = l2_version__add_remote(value)) != VERSION_SUCCESS) + return res; + } + + return VERSION_SUCCESS; +} + +unsigned l2_version__add_remote(json_t *js) +{ + struct l2_version_remote *ver = NULL; + struct tm ts; + json_t *val; + + unsigned res = VERSION_SUCCESS; + + if (!json_is_object(js)) { + return VERSION_EFORMAT; + } + + ver = calloc(1, sizeof(struct l2_version_remote)); + if (!ver) return VERSION_EALLOC; + + res = VERSION_EFORMAT; + + val = json_object_get(js, "id"); + if (!json_is_string(val)) goto cleanup; + ver->id = strdup(json_string_value(val)); + + val = json_object_get(js, "type"); + if (!json_is_string(val)) goto cleanup; + ver->type = strdup(json_string_value(val)); + + val = json_object_get(js, "url"); + if (!json_is_string(val)) goto cleanup; + ver->url = strdup(json_string_value(val)); + + val = json_object_get(js, "sha1"); + if (!json_is_string(val)) goto cleanup; + if (l2_sha1_digest_from_hex(&ver->sha1, json_string_value(val)) < 0) + goto cleanup; + + val = json_object_get(js, "complianceLevel"); + if (!json_is_number(val)) goto cleanup; + ver->compliance_level = json_integer_value(val); + + val = json_object_get(js, "time"); + if (!json_is_string(val)) goto cleanup; + memset(&ts, 0, sizeof(struct tm)); + if (!l2_launcher_parse_iso_time(json_string_value(val), &ts)) goto cleanup; + ver->update_time = mktime(&ts); + + val = json_object_get(js, "releaseTime"); + if (!json_is_string(val)) goto cleanup; + memset(&ts, 0, sizeof(struct tm)); + if (!l2_launcher_parse_iso_time(json_string_value(val), &ts)) goto cleanup; + ver->release_time = mktime(&ts); + + /* add the thing to the global list */ + if (l2_state.ver_remote_tail) { + l2_state.ver_remote_tail->next = ver; + ver->prev = l2_state.ver_remote_tail; + } else { + l2_state.ver_remote_head = l2_state.ver_remote_tail = ver; + } + + return VERSION_SUCCESS; + +cleanup: + if (ver) { + free(ver->id); + free(ver->type); + free(ver->url); + } + + free(ver); + + return res; +} |
