From 298ad2a0f6c6d0df87ec8f5f14850775068a249c Mon Sep 17 00:00:00 2001 From: Owen Rummage Date: Sun, 19 Jul 2026 17:22:34 -0500 Subject: [PATCH] Always return a claim code after uploads --- README.md | 6 +-- src/app/benchmark.c | 12 +----- src/app/upload.c | 91 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 74 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 313ef1a..5eab94d 100644 --- a/README.md +++ b/README.md @@ -83,12 +83,10 @@ By default, fossbench samples system activity for ten seconds before a run. It reports background CPU use, available memory, process count, and the OS kernel or build. It does not collect process names or command lines. -Uploads are optional. They are anonymous without an API token. To attach a -result to a fossbench.net account, create a benchmark client token and export -it: +Uploads are optional and anonymous. Every successful upload prints a claim +code and link that can be used to attach the result to a fossbench.net account: ```sh -export FOSSBENCH_TOKEN=fb_your_token_here ./dist/fossbench-linux-amd64 --upload ``` diff --git a/src/app/benchmark.c b/src/app/benchmark.c index f0335b0..186538d 100644 --- a/src/app/benchmark.c +++ b/src/app/benchmark.c @@ -836,13 +836,8 @@ int fossbench_run(int verbose, int upload_mode, int system_check) teardown(); { - /* Read the token from the environment. */ - const char *token = getenv("FOSSBENCH_TOKEN"); int do_upload; - if (token && token[0] == '\0') - token = NULL; - if (upload_mode == 1) { do_upload = 1; } else if (upload_mode == 2) { @@ -850,10 +845,7 @@ int fossbench_run(int verbose, int upload_mode, int system_check) printf(" Result was not uploaded.\n"); } else { char answer[16]; - if (token) - printf(" Upload this result to %s using your API token? [y/N] ", FB_API_BASE_URL); - else - printf(" Upload this result to %s? [y/N] ", FB_API_BASE_URL); + printf(" Upload this result to %s? [y/N] ", FB_API_BASE_URL); fflush(stdout); do_upload = fgets(answer, sizeof(answer), stdin) && (answer[0] == 'y' || answer[0] == 'Y'); @@ -866,7 +858,7 @@ int fossbench_run(int verbose, int upload_mode, int system_check) fprintf(stderr, " Upload support is disabled in this build.\n"); #else upload_results(&system_info, multicore_score, singlecore_score, - multi, single, duration_ms, &background, token); + multi, single, duration_ms, &background); #endif } } diff --git a/src/app/upload.c b/src/app/upload.c index 4b97d01..12be0d1 100644 --- a/src/app/upload.c +++ b/src/app/upload.c @@ -19,6 +19,30 @@ static void json_escape(const char *src, char *dst, size_t cap) dst[used] = '\0'; } +/* Extract a simple JSON string field from the upload response. */ +static int json_string_field(const char *json, const char *field, char *dst, size_t cap) +{ + char key[64]; + const char *p; + size_t used = 0; + + if (cap == 0 || snprintf(key, sizeof(key), "\"%s\"", field) < 0) + return 0; + p = strstr(json, key); + if (!p) return 0; + p += strlen(key); + while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++; + if (*p++ != ':') return 0; + while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++; + if (*p++ != '"') return 0; + while (*p && *p != '"' && used + 1 < cap) { + if (*p == '\\' && p[1]) p++; + dst[used++] = *p++; + } + dst[used] = '\0'; + return *p == '"' && used > 0; +} + #if !defined(_WIN32) /* Load the certificates included with the program. */ static int load_embedded_ca_bundle(SSL_CTX *ctx) @@ -63,7 +87,8 @@ static int is_winhttp_secure_error(DWORD err) /* Send the request with Windows networking. */ static int winhttp_post(const char *host, const char *port, const char *path, int use_tls, const char *payload, int payload_len, - const char *auth_header, int *out_status) + const char *auth_header, int *out_status, + char *response, size_t response_cap) { wchar_t whost[256], wpath[512], wheaders[700]; char header_buf[700]; @@ -119,6 +144,20 @@ static int winhttp_post(const char *host, const char *port, const char *path, goto done; } *out_status = (int)status; + if (response_cap > 0) { + size_t used = 0; + while (used + 1 < response_cap) { + DWORD available = 0, got = 0; + DWORD room = (DWORD)(response_cap - used - 1); + if (!WinHttpQueryDataAvailable(hrequest, &available) || available == 0) + break; + if (available > room) available = room; + if (!WinHttpReadData(hrequest, response + used, available, &got) || got == 0) + break; + used += got; + } + response[used] = '\0'; + } ok = 1; done: if (hrequest) WinHttpCloseHandle(hrequest); @@ -131,15 +170,15 @@ done: static int upload_results(const struct system_info *info, double score, double singlecore_score, const struct result *multi, const struct result *single, uint64_t duration_ms, - const struct background_metrics *background, const char *token) + const struct background_metrics *background) { char host[256], port[16], path[512], payload[16384]; - char auth_header[600]; + char auth_header[600], response_body[2048], claim_url[1024]; char cpu[512], model[512], os[512], compiler[256], kernel[256]; const char *base = FB_API_BASE_URL, *p, *slash, *colon; int use_tls, status = 0, payload_len; #if !defined(_WIN32) - char request[20000], response[512]; + char request[20000], response[4096]; struct addrinfo hints, *addresses = NULL, *a; SSL_CTX *tls_ctx = NULL; SSL *tls = NULL; @@ -222,15 +261,9 @@ static int upload_results(const struct system_info *info, double score, payload_len = (int)(used + 3); } + /* Every upload is anonymous and claim-based. */ auth_header[0] = '\0'; - if (token && token[0]) { - int n = snprintf(auth_header, sizeof(auth_header), - "Authorization: Bearer %s\r\n", token); - if (n < 0 || (size_t)n >= sizeof(auth_header)) { - fprintf(stderr, " upload error: API token too long\n"); - return 0; - } - } + response_body[0] = '\0'; #if !defined(_WIN32) request_len = snprintf(request, sizeof(request), "POST %s HTTP/1.1\r\nHost: %s:%s\r\nContent-Type: application/json\r\n" @@ -278,17 +311,27 @@ static int upload_results(const struct system_info *info, double score, } } { - int n = use_tls ? SSL_read(tls, response, sizeof(response) - 1) : - (int)recv(fd, response, sizeof(response) - 1, 0); - if (n <= 0) { fprintf(stderr, " upload error: no server response\n"); goto upload_failed; } - response[n] = '\0'; + size_t used = 0; + int n; + do { + n = use_tls ? SSL_read(tls, response + used, (int)(sizeof(response) - used - 1)) : + (int)recv(fd, response + used, sizeof(response) - used - 1, 0); + if (n > 0) used += (size_t)n; + } while (n > 0 && used + 1 < sizeof(response)); + if (used == 0) { fprintf(stderr, " upload error: no server response\n"); goto upload_failed; } + response[used] = '\0'; if (sscanf(response, "HTTP/%*s %d", &status) != 1) status = 0; + { + char *body = strstr(response, "\r\n\r\n"); + if (body) snprintf(response_body, sizeof(response_body), "%s", body + 4); + } } if (tls) { SSL_shutdown(tls); SSL_free(tls); } if (tls_ctx) SSL_CTX_free(tls_ctx); close(fd); #else - if (!winhttp_post(host, port, path, use_tls, payload, payload_len, auth_header, &status)) + if (!winhttp_post(host, port, path, use_tls, payload, payload_len, auth_header, + &status, response_body, sizeof(response_body))) return 0; #endif if (status == 401) { @@ -300,10 +343,16 @@ static int upload_results(const struct system_info *info, double score, return 0; } if (status < 200 || status >= 300) { fprintf(stderr, " upload failed: server returned HTTP %d\n", status); return 0; } - if (token) - printf(" Results uploaded and published to your profile (HTTP %d).\n", status); - else - printf(" Results uploaded, pending administrator review (HTTP %d).\n", status); + if (!json_string_field(response_body, "claim_url", claim_url, sizeof(claim_url))) { + fprintf(stderr, " upload failed: server did not return a claim link\n"); + return 0; + } + printf(" Results uploaded (HTTP %d).\n", status); + { + const char *claim_code = strrchr(claim_url, '/'); + printf(" Claim code: %s\n", claim_code && claim_code[1] ? claim_code + 1 : claim_url); + } + printf(" Claim your result: %s\n", claim_url); return 1; #if !defined(_WIN32)