fix --git asset pick: reject cross-platform filenames (android/win/darwin); verify bare binary is native after download; fall back to os-named asset (linux/darwin) e.g. fzf

这个提交包含在:
JGZYES
2026-09-01 22:04:21 +08:00
父节点 eeccdd1650
当前提交 91275cf107
修改 4 个文件,包含 114 行新增10 行删除
+33
查看文件
@@ -128,6 +128,28 @@ static int verify_checksums(const char *url, const char *path, const char *name)
return 0;
}
/* A bare (no-dot) release asset on Unix must actually be usable on this
* platform — e.g. fzf publishes a bare 'fzf' for Android, which must not be
* installed on Linux. Uses `file`; be permissive where it isn't available. */
static int native_binary_ok(const char *path, PmmOS os) {
#ifdef _WIN32
(void)path; (void)os; return 1; /* `file` isn't present on Windows */
#else
if (os != OS_LINUX && os != OS_MACOS) return 1;
char cmd[1400], out[512] = "";
snprintf(cmd, sizeof(cmd), "file -b \"%s\" 2>/dev/null", path);
FILE *f = popen(cmd, "r");
if (!f) return 1;
if (fgets(out, sizeof(out), f) == NULL) out[0] = 0;
pclose(f);
if (!out[0]) return 1; /* `file` missing: allow */
if (strstr(out, "Android") || strstr(out, "for Android")) return 0; /* android ELF */
if (strstr(out, "PE32")) return 0; /* Windows binary */
if (strstr(out, "Mach-O")) return 0; /* macOS binary */
return 1;
#endif
}
int install_file(const char *url, const char *name) {
PmmOS os = pmm_detect_os();
char cache[1024], path[1200], cmd[2600];
@@ -168,6 +190,17 @@ int install_file(const char *url, const char *name) {
}
const char *bname = base_name(name);
/* A bare binary (no extension) must really be a native executable for this
* OS; otherwise refuse so the caller can fall back to the os-named asset. */
if (strchr(bname, '.') == NULL) {
printf("pmm: verifying %s is a native %s binary...\n", bname, pmm_os_name(os));
if (!native_binary_ok(path, os)) {
fprintf(stderr, "pmm: %s is not a usable %s binary; will fall back\n",
bname, pmm_os_name(os));
remove(path);
return -1;
}
}
char dest[1024];
pmm_install_dir(dest, sizeof(dest));
+17 -1
查看文件
@@ -184,16 +184,32 @@ static int cmd_install_git(int argc, char **argv, const char *flag) {
if (!asset && host == HOST_AUTO)
fprintf(stderr, "pmm: could not reach a known release API for %s "
"(tried gitea/gitlab/github shapes)\n", repo);
repo_close(ctx);
if (!asset) {
if (host != HOST_AUTO)
fprintf(stderr, "pmm: no suitable %s asset found in latest release of %s\n",
pmm_os_name(os), repo);
repo_close(ctx);
return 1;
}
printf("pmm: %s -> %s (tag %s)\n", pmm_os_name(os), asset->name, asset->tag);
int rc = install_file(asset->url, asset->name);
/* fallback: the first pick may have been a cross-platform/wrong asset
* (e.g. fzf's Android binary named without an OS hint). Re-fetch a clearly
* os-named one and install that instead. */
if (rc != 0 && host != HOST_AUTO) {
const char *sub = (os == OS_LINUX) ? "linux" : (os == OS_MACOS) ? "darwin" : NULL;
if (sub) {
ReleaseAsset *fb = repo_asset_matching(ctx, os, sub);
if (fb && strcmp(fb->name, asset->name) != 0) {
printf("pmm: retrying with %s (%s)\n", fb->name, pmm_os_name(os));
rc = install_file(fb->url, fb->name);
repo_asset_free(fb);
}
}
}
repo_asset_free(asset);
repo_close(ctx);
return rc == 0 ? 0 : 1;
}
+60 -9
查看文件
@@ -168,6 +168,38 @@ static int has_ext(const char *name, const char *ext) {
}
static int asset_is_junk(const char *name);
/* Case-insensitive substring test (avoids relying on non-standard strcasestr). */
static int ci_contains(const char *hay, const char *needle) {
size_t nl = strlen(needle);
if (!nl) return 0;
for (; *hay; hay++) {
size_t i;
for (i = 0; i < nl && hay[i]; i++)
if (tolower((unsigned char)hay[i]) != tolower((unsigned char)needle[i])) break;
if (i == nl) return 1;
}
return 0;
}
/* True if the asset filename clearly belongs to ANOTHER platform (e.g. an
* "android"/"windows"/"darwin" tar.gz must not be chosen for Linux). */
static int name_hints_other_os(const char *name, PmmOS os) {
if (!name) return 0;
if (os == OS_LINUX)
return ci_contains(name,"android") || ci_contains(name,"windows") ||
ci_contains(name,"win32") || ci_contains(name,"darwin") ||
ci_contains(name,"macos") || ci_contains(name,"osx");
if (os == OS_WINDOWS)
return ci_contains(name,"linux") || ci_contains(name,"darwin") ||
ci_contains(name,"android") || ci_contains(name,"macos") ||
ci_contains(name,"osx");
if (os == OS_MACOS)
return ci_contains(name,"linux") || ci_contains(name,"windows") ||
ci_contains(name,"win32") || ci_contains(name,"android");
return 0;
}
int asset_matches_os(const char *filename, PmmOS os) {
static const char *win_exts[] = { ".exe", ".msi", ".zip", ".7z", NULL };
static const char *linux_exts[] = { ".deb", ".rpm", ".appimage", ".tar.gz", ".tgz", ".tar.xz", ".tar.bz2", ".pkg.tar.zst", NULL };
@@ -179,6 +211,9 @@ int asset_matches_os(const char *filename, PmmOS os) {
case OS_MACOS: exts = mac_exts; break;
default: return 0;
}
/* Never pick an asset whose name names a different platform (fzf publishes
* xxxx-android_<arch>.tar.gz; a Linux user must not get that). */
if (name_hints_other_os(filename, os)) return 0;
for (int i = 0; exts[i]; i++)
if (has_ext(filename, exts[i])) return 1;
/* A bare (no-dot) binary counts on Linux/macOS — e.g. a release asset named
@@ -210,19 +245,20 @@ static ReleaseAsset *asset_from_json(const JsonValue *a, const char *tag) {
return ra;
}
static ReleaseAsset *pick_from_assets_array(const JsonValue *assets, const char *tag, PmmOS os) {
static ReleaseAsset *pick_from_assets_array(const JsonValue *assets, const char *tag, PmmOS os, const char *name_sub) {
for (int i = 0; i < assets->count; i++) {
JsonValue *a = json_at(assets, i);
if (!a) continue;
const char *name = json_str(a, "name");
if (!name || asset_is_junk(name)) continue;
if (!asset_matches_os(name, os)) continue;
if (name_sub && !ci_contains(name, name_sub)) continue;
return asset_from_json(a, tag);
}
return NULL;
}
static ReleaseAsset *fetch_latest(const char *url, PmmHost host, PmmOS os) {
static ReleaseAsset *fetch_latest(const char *url, PmmHost host, PmmOS os, const char *name_sub) {
int status = 0;
char *body = http_get(url, &status);
if (!body || (status != 200 && status != 0)) {
@@ -248,6 +284,7 @@ static ReleaseAsset *fetch_latest(const char *url, PmmHost host, PmmOS os) {
const char *name = json_str(a, "name");
if (!name) name = json_str(a, "direct_asset_url");
if (!name || asset_is_junk(name) || !asset_matches_os(name, os)) continue;
if (name_sub && !ci_contains(name, name_sub)) continue;
found = calloc(1, sizeof(ReleaseAsset));
found->name = strdup(name);
found->url = strdup_or(json_str(a, "direct_asset_url"), json_str(a, "url") ? json_str(a, "url") : "");
@@ -255,7 +292,7 @@ static ReleaseAsset *fetch_latest(const char *url, PmmHost host, PmmOS os) {
}
}
} else if (assets) {
found = pick_from_assets_array(assets, tag, os);
found = pick_from_assets_array(assets, tag, os, name_sub);
}
json_free(root);
@@ -276,20 +313,20 @@ ReleaseAsset *repo_latest_asset(RepoContext *ctx, PmmOS os) {
ReleaseAsset *a = NULL;
if (origin) {
snprintf(url, sizeof(url), "%s/api/v1/repos/%s/releases/latest", origin, norm);
if ((a = fetch_latest(url, HOST_GITEA, os))) { ctx->host = HOST_GITEA; return a; }
if ((a = fetch_latest(url, HOST_GITEA, os, NULL))) { ctx->host = HOST_GITEA; return a; }
char *enc = url_encode(norm);
snprintf(url, sizeof(url), "%s/api/v4/projects/%s/releases/permalink/latest", origin, enc);
free(enc);
if ((a = fetch_latest(url, HOST_GITLAB, os))) { ctx->host = HOST_GITLAB; return a; }
if ((a = fetch_latest(url, HOST_GITLAB, os, NULL))) { ctx->host = HOST_GITLAB; return a; }
if (strstr(origin, "github.com")) {
snprintf(url, sizeof(url), "https://api.github.com/repos/%s/releases/latest", norm);
if ((a = fetch_latest(url, HOST_GITHUB, os))) { ctx->host = HOST_GITHUB; return a; }
if ((a = fetch_latest(url, HOST_GITHUB, os, NULL))) { ctx->host = HOST_GITHUB; return a; }
}
} else {
snprintf(url, sizeof(url), "https://api.github.com/repos/%s/releases/latest", norm);
if ((a = fetch_latest(url, HOST_GITHUB, os))) { ctx->host = HOST_GITHUB; return a; }
if ((a = fetch_latest(url, HOST_GITHUB, os, NULL))) { ctx->host = HOST_GITHUB; return a; }
snprintf(url, sizeof(url), "https://gitlab.com/api/v4/projects/%s/releases/permalink/latest", url_encode(norm));
if ((a = fetch_latest(url, HOST_GITLAB, os))) { ctx->host = HOST_GITLAB; return a; }
if ((a = fetch_latest(url, HOST_GITLAB, os, NULL))) { ctx->host = HOST_GITLAB; return a; }
}
return NULL;
}
@@ -302,7 +339,21 @@ ReleaseAsset *repo_latest_asset(RepoContext *ctx, PmmOS os) {
snprintf(url, sizeof(url), "%s/releases/latest", ctx->api_url);
break;
}
return fetch_latest(url, ctx->host, os);
return fetch_latest(url, ctx->host, os, NULL);
}
/* (Re)fetch the latest release and return the first asset for this OS whose
* name contains `name_sub` (e.g. "linux"). Used as a fallback when the first
* pick turns out to be a cross-platform/wrong binary. Caller frees via
* repo_asset_free. */
ReleaseAsset *repo_asset_matching(RepoContext *ctx, PmmOS os, const char *name_sub) {
if (!ctx || !ctx->api_url || !name_sub) return NULL;
char url[2048];
if (ctx->host == HOST_GITLAB)
snprintf(url, sizeof(url), "%s/releases/permalink/latest", ctx->api_url);
else
snprintf(url, sizeof(url), "%s/releases/latest", ctx->api_url);
return fetch_latest(url, ctx->host, os, name_sub);
}
void repo_asset_free(ReleaseAsset *a) {
+4
查看文件
@@ -28,6 +28,10 @@ RepoContext *repo_open(PmmHost host, const char *repo, const char *mirror_api_ba
* release. Returns NULL if none found. Caller frees via repo_asset_free. */
ReleaseAsset *repo_latest_asset(RepoContext *ctx, PmmOS os);
/* Fallback: re-fetch the latest release and return the first os-matching asset
* whose name contains `name_sub` (e.g. "linux"). Caller frees via repo_asset_free. */
ReleaseAsset *repo_asset_matching(RepoContext *ctx, PmmOS os, const char *name_sub);
void repo_asset_free(ReleaseAsset *a);
void repo_close(RepoContext *ctx);