镜像自地址
https://github.com/JGZYES/ParlzPackageManger.git
已同步 2026-09-11 08:52:45 +08:00
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
这个提交包含在:
+33
@@ -128,6 +128,28 @@ static int verify_checksums(const char *url, const char *path, const char *name)
|
|||||||
return 0;
|
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) {
|
int install_file(const char *url, const char *name) {
|
||||||
PmmOS os = pmm_detect_os();
|
PmmOS os = pmm_detect_os();
|
||||||
char cache[1024], path[1200], cmd[2600];
|
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);
|
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];
|
char dest[1024];
|
||||||
pmm_install_dir(dest, sizeof(dest));
|
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)
|
if (!asset && host == HOST_AUTO)
|
||||||
fprintf(stderr, "pmm: could not reach a known release API for %s "
|
fprintf(stderr, "pmm: could not reach a known release API for %s "
|
||||||
"(tried gitea/gitlab/github shapes)\n", repo);
|
"(tried gitea/gitlab/github shapes)\n", repo);
|
||||||
repo_close(ctx);
|
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
if (host != HOST_AUTO)
|
if (host != HOST_AUTO)
|
||||||
fprintf(stderr, "pmm: no suitable %s asset found in latest release of %s\n",
|
fprintf(stderr, "pmm: no suitable %s asset found in latest release of %s\n",
|
||||||
pmm_os_name(os), repo);
|
pmm_os_name(os), repo);
|
||||||
|
repo_close(ctx);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("pmm: %s -> %s (tag %s)\n", pmm_os_name(os), asset->name, asset->tag);
|
printf("pmm: %s -> %s (tag %s)\n", pmm_os_name(os), asset->name, asset->tag);
|
||||||
int rc = install_file(asset->url, asset->name);
|
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_asset_free(asset);
|
||||||
|
repo_close(ctx);
|
||||||
return rc == 0 ? 0 : 1;
|
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);
|
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) {
|
int asset_matches_os(const char *filename, PmmOS os) {
|
||||||
static const char *win_exts[] = { ".exe", ".msi", ".zip", ".7z", NULL };
|
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 };
|
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;
|
case OS_MACOS: exts = mac_exts; break;
|
||||||
default: return 0;
|
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++)
|
for (int i = 0; exts[i]; i++)
|
||||||
if (has_ext(filename, exts[i])) return 1;
|
if (has_ext(filename, exts[i])) return 1;
|
||||||
/* A bare (no-dot) binary counts on Linux/macOS — e.g. a release asset named
|
/* 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;
|
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++) {
|
for (int i = 0; i < assets->count; i++) {
|
||||||
JsonValue *a = json_at(assets, i);
|
JsonValue *a = json_at(assets, i);
|
||||||
if (!a) continue;
|
if (!a) continue;
|
||||||
const char *name = json_str(a, "name");
|
const char *name = json_str(a, "name");
|
||||||
if (!name || asset_is_junk(name)) continue;
|
if (!name || asset_is_junk(name)) continue;
|
||||||
if (!asset_matches_os(name, os)) continue;
|
if (!asset_matches_os(name, os)) continue;
|
||||||
|
if (name_sub && !ci_contains(name, name_sub)) continue;
|
||||||
return asset_from_json(a, tag);
|
return asset_from_json(a, tag);
|
||||||
}
|
}
|
||||||
return NULL;
|
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;
|
int status = 0;
|
||||||
char *body = http_get(url, &status);
|
char *body = http_get(url, &status);
|
||||||
if (!body || (status != 200 && status != 0)) {
|
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");
|
const char *name = json_str(a, "name");
|
||||||
if (!name) name = json_str(a, "direct_asset_url");
|
if (!name) name = json_str(a, "direct_asset_url");
|
||||||
if (!name || asset_is_junk(name) || !asset_matches_os(name, os)) continue;
|
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 = calloc(1, sizeof(ReleaseAsset));
|
||||||
found->name = strdup(name);
|
found->name = strdup(name);
|
||||||
found->url = strdup_or(json_str(a, "direct_asset_url"), json_str(a, "url") ? json_str(a, "url") : "");
|
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) {
|
} else if (assets) {
|
||||||
found = pick_from_assets_array(assets, tag, os);
|
found = pick_from_assets_array(assets, tag, os, name_sub);
|
||||||
}
|
}
|
||||||
|
|
||||||
json_free(root);
|
json_free(root);
|
||||||
@@ -276,20 +313,20 @@ ReleaseAsset *repo_latest_asset(RepoContext *ctx, PmmOS os) {
|
|||||||
ReleaseAsset *a = NULL;
|
ReleaseAsset *a = NULL;
|
||||||
if (origin) {
|
if (origin) {
|
||||||
snprintf(url, sizeof(url), "%s/api/v1/repos/%s/releases/latest", origin, norm);
|
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);
|
char *enc = url_encode(norm);
|
||||||
snprintf(url, sizeof(url), "%s/api/v4/projects/%s/releases/permalink/latest", origin, enc);
|
snprintf(url, sizeof(url), "%s/api/v4/projects/%s/releases/permalink/latest", origin, enc);
|
||||||
free(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")) {
|
if (strstr(origin, "github.com")) {
|
||||||
snprintf(url, sizeof(url), "https://api.github.com/repos/%s/releases/latest", norm);
|
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 {
|
} else {
|
||||||
snprintf(url, sizeof(url), "https://api.github.com/repos/%s/releases/latest", norm);
|
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));
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -302,7 +339,21 @@ ReleaseAsset *repo_latest_asset(RepoContext *ctx, PmmOS os) {
|
|||||||
snprintf(url, sizeof(url), "%s/releases/latest", ctx->api_url);
|
snprintf(url, sizeof(url), "%s/releases/latest", ctx->api_url);
|
||||||
break;
|
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) {
|
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. */
|
* release. Returns NULL if none found. Caller frees via repo_asset_free. */
|
||||||
ReleaseAsset *repo_latest_asset(RepoContext *ctx, PmmOS os);
|
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_asset_free(ReleaseAsset *a);
|
||||||
void repo_close(RepoContext *ctx);
|
void repo_close(RepoContext *ctx);
|
||||||
|
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户