/* Copyright (c) 2026 Juhapekka Piiroinen * All Rights Reserved. * AmoosedWeb project: https://amoosed.com */ #include #include #include #include #include #include /* Usage: native-c-site [--external INDEX_FILE] [--member MEMBER_FILE] ORIGIN * [RESOURCE_PATH] ABSOLUTE_OUTPUT_PATH * The origin is trusted by the caller. This preview accepts HTTPS and * loopback HTTP for local development. The output must not already exist. * INDEX_FILE contains an AMWKEY1 text file or packed ICO. MEMBER_FILE is an * AMWMEMBER1 text file; its edition ID is checked against the verified site. */ static void clear_bytes(uint8_t* bytes, size_t length) { volatile uint8_t* p = bytes; while (length--) *p++ = 0; } static int read_credential(const char* path, uint8_t** out, size_t* length) { FILE* file = fopen(path, "rb"); if (!file) return 0; if (fseek(file, 0, SEEK_END) != 0) { fclose(file); return 0; } long size = ftell(file); if (size <= 0 || size > 4 * 1024 * 1024 || fseek(file, 0, SEEK_SET) != 0) { fclose(file); return 0; } uint8_t* bytes = (uint8_t*)malloc((size_t)size); if (!bytes) { fclose(file); return 0; } int complete = fread(bytes, 1, (size_t)size, file) == (size_t)size; fclose(file); if (!complete) { clear_bytes(bytes, (size_t)size); free(bytes); return 0; } *out = bytes; *length = (size_t)size; return 1; } static int base64_digit(uint8_t c) { if (c >= 'A' && c <= 'Z') return c - 'A'; if (c >= 'a' && c <= 'z') return c - 'a' + 26; if (c >= '0' && c <= '9') return c - '0' + 52; if (c == '-') return 62; if (c == '_') return 63; return -1; } static int member_id_char(uint8_t c, int first) { int letter = c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'; int digit = c >= '0' && c <= '9'; return letter || digit || (!first && (c == '.' || c == '_' || c == '-')); } static int decode_member_file(const uint8_t* file, size_t length, uint8_t key[32], char id[65]) { static const char prefix[] = "AMWMEMBER1:"; uint8_t packet[101] = {0}; size_t size = 0; uint32_t value = 0; int bits = 0, valid = 0; if (length && file[length - 1] == '\n') length--; if (length && file[length - 1] == '\r') length--; if (length <= sizeof(prefix) - 1 || length > sizeof(prefix) - 1 + 135 || memcmp(file, prefix, sizeof(prefix) - 1) != 0) goto done; file += sizeof(prefix) - 1; length -= sizeof(prefix) - 1; if (length < 51 || length > 135) goto done; for (size_t i = 0; i < length; i++) { int digit = base64_digit(file[i]); if (digit < 0) goto done; value = (value << 6) | (uint32_t)digit; bits += 6; if (bits >= 8) { bits -= 8; if (size == sizeof(packet)) goto done; packet[size++] = (uint8_t)((value >> bits) & 255u); } } if (bits == 6 || (value & ((1u << bits) - 1u)) != 0 || size < 38 || memcmp(packet, "AMM1", 4) != 0 || packet[4] == 0 || packet[4] > 64 || size != 37u + packet[4]) goto done; for (size_t i = 0; i < packet[4]; i++) if (!member_id_char(packet[37 + i], i == 0)) goto done; memcpy(key, packet + 5, 32); memcpy(id, packet + 37, packet[4]); id[packet[4]] = '\0'; valid = 1; done: clear_bytes(packet, sizeof(packet)); return valid; } static void usage(void) { fprintf(stderr, "Usage: native-c-site [--external INDEX_FILE] [--member MEMBER_FILE] ORIGIN [RESOURCE_PATH] ABSOLUTE_OUTPUT_PATH\n"); } int main(int argc, char** argv) { const char* credential_path = NULL; const char* member_path = NULL; int first = 1; while (first < argc && (strcmp(argv[first], "--external") == 0 || strcmp(argv[first], "--member") == 0)) { if (first + 1 >= argc) { usage(); return 2; } if (strcmp(argv[first], "--external") == 0) { if (credential_path) { usage(); return 2; } credential_path = argv[first + 1]; } else { if (member_path) { usage(); return 2; } member_path = argv[first + 1]; } first += 2; } if (argc - first != 2 && argc - first != 3) { usage(); return 2; } uint64_t capabilities = 0, required = 0, length = 0, written = 0; required = (credential_path ? AMW_PREVIEW_CAP_EXTERNAL_INDEX : AMW_PREVIEW_CAP_HOSTED_INDEX) | AMW_PREVIEW_CAP_BYTE_CARRIERS | (member_path ? AMW_PREVIEW_CAP_MEMBER_CONTENT : 0); if (amw_preview_abi_revision() != AMW_PREVIEW_ABI_REVISION || amw_preview_capabilities(&capabilities) != AMW_PREVIEW_OK || (capabilities & required) != required) { fprintf(stderr, "This native library lacks the required preview ABI\n"); return 1; } char error[256] = {0}; amw_preview_site* site = NULL; uint8_t* credential = NULL; size_t credential_length = 0; if (credential_path && !read_credential(credential_path, &credential, &credential_length)) { fprintf(stderr, "Could not read credential file\n"); return 1; } int32_t status = credential_path ? amw_preview_site_open_external(argv[first], (uint64_t)strlen(argv[first]), credential, (uint64_t)credential_length, &site, error, sizeof(error)) : amw_preview_site_open_hosted(argv[first], (uint64_t)strlen(argv[first]), &site, error, sizeof(error)); if (credential) { clear_bytes(credential, credential_length); free(credential); } if (status != AMW_PREVIEW_OK) { fprintf(stderr, "Index open failed (%" PRId32 "): %s\n", status, error); return 1; } char* entry = NULL; uint8_t member_key[32] = {0}; char member_id[65] = {0}; if (member_path) { uint8_t* file = NULL; size_t file_length = 0; int valid = read_credential(member_path, &file, &file_length); if (valid) valid = decode_member_file(file, file_length, member_key, member_id); if (file) { clear_bytes(file, file_length); free(file); } if (!valid) { status = AMW_PREVIEW_INVALID_ARGUMENT; strcpy(error, "Invalid member credential file"); goto failed; } uint64_t id_length = 0; status = amw_preview_site_member_key_id(site, NULL, 0, &id_length); if (status != AMW_PREVIEW_OK) goto failed; if (id_length == 0 || id_length > 64 || id_length != strlen(member_id)) { status = AMW_PREVIEW_AUTHENTICATION_FAILED; strcpy(error, "Member credential belongs to another edition"); goto failed; } char site_id[65] = {0}; status = amw_preview_site_member_key_id(site, site_id, sizeof(site_id), &id_length); if (status != AMW_PREVIEW_OK) goto failed; if (strcmp(site_id, member_id) != 0) { status = AMW_PREVIEW_AUTHENTICATION_FAILED; strcpy(error, "Member credential belongs to another edition"); goto failed; } } const char* resource = argc - first == 3 ? argv[first + 1] : NULL; if (!resource) { status = amw_preview_site_entry_path(site, NULL, 0, &length); if (status != AMW_PREVIEW_OK) goto failed; if (length == 0 || length >= SIZE_MAX) { status = AMW_PREVIEW_BAD_FORMAT; goto failed; } entry = (char*)malloc((size_t)length + 1); if (!entry) { status = AMW_PREVIEW_INTERNAL_ERROR; goto failed; } status = amw_preview_site_entry_path(site, entry, length + 1, &length); if (status != AMW_PREVIEW_OK) goto failed; resource = entry; } else length = (uint64_t)strlen(resource); status = amw_preview_site_download(site, resource, length, member_path ? member_key : NULL, member_path ? 32 : 0, argv[argc - 1], (uint64_t)strlen(argv[argc - 1]), &written, error, sizeof(error)); if (status != AMW_PREVIEW_OK) goto failed; printf("Verified %" PRIu64 " bytes from %s\n", written, resource); clear_bytes(member_key, sizeof(member_key)); free(entry); amw_preview_site_release(site); return 0; failed: fprintf(stderr, "Reader failed (%" PRId32 "): %s\n", status, error); clear_bytes(member_key, sizeof(member_key)); free(entry); amw_preview_site_release(site); return 1; }