git mirror - github.com/owenewans/holy - branch master
clone: https://src.holypkg.eu/holy/

file backends/pacman.c

#define _POSIX_C_SOURCE 200809L
#include "pacman.h"

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

struct keyword {
    const char *name;
    enum holy_pacman_field_kind kind;
    int list, empty;
};

static const struct keyword keywords[] = {
    {"pkgname", HOLY_PACMAN_IDENTITY, 0, 0},
    {"pkgbase", HOLY_PACMAN_IDENTITY, 0, 0},
    {"pkgver", HOLY_PACMAN_IDENTITY, 0, 0},
    {"arch", HOLY_PACMAN_IDENTITY, 0, 0},
    {"pkgdesc", HOLY_PACMAN_DISPLAY, 0, 1},
    {"url", HOLY_PACMAN_DISPLAY, 0, 1},
    {"builddate", HOLY_PACMAN_DISPLAY, 0, 0},
    {"packager", HOLY_PACMAN_DISPLAY, 0, 0},
    {"size", HOLY_PACMAN_DISPLAY, 0, 0},
    {"license", HOLY_PACMAN_DISPLAY, 1, 0},
    {"group", HOLY_PACMAN_DISPLAY, 1, 0},
    {"depend", HOLY_PACMAN_DEPEND, 1, 0},
    {"optdepend", HOLY_PACMAN_OPTIONAL, 1, 0},
    {"makedepend", HOLY_PACMAN_BUILD, 1, 0},
    {"checkdepend", HOLY_PACMAN_CHECK, 1, 0},
    {"provides", HOLY_PACMAN_PROVIDE, 1, 0},
    {"conflict", HOLY_PACMAN_CONFLICT, 1, 0},
    {"replaces", HOLY_PACMAN_REPLACE, 1, 0},
    {"backup", HOLY_PACMAN_BACKUP, 1, 0},
    {"xdata", HOLY_PACMAN_EXTRA, 1, 0}
};

static int digit(unsigned char c) { return c >= '0' && c <= '9'; }
static int alpha(unsigned char c)
{
    return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}

static int package_name(const char *name)
{
    const unsigned char *p = (const unsigned char *)name;
    if (!*p || *p == '-' || *p == '.') return 0;
    for (; *p; ++p)
        if (!alpha(*p) && !digit(*p) && !strchr("@._+-", *p)) return 0;
    return 1;
}

static int text_value(const char *text)
{
    const unsigned char *p = (const unsigned char *)text;
    while (*p) {
        unsigned value, minimum, left;
        if (*p < 0x20 || *p == 0x7f) return 0;
        if (*p < 0x80) { ++p; continue; }
        if (*p >= 0xc2 && *p <= 0xdf) { value = *p & 31; left = 1; minimum = 0x80; }
        else if (*p >= 0xe0 && *p <= 0xef) { value = *p & 15; left = 2; minimum = 0x800; }
        else if (*p >= 0xf0 && *p <= 0xf4) { value = *p & 7; left = 3; minimum = 0x10000; }
        else return 0;
        ++p;
        while (left--) {
            if ((*p & 0xc0) != 0x80) return 0;
            value = (value << 6) | (*p++ & 63);
        }
        if (value < minimum || value > 0x10ffff || (value >= 0xd800 && value <= 0xdfff)) return 0;
    }
    return 1;
}

static int version_value(const char *text, int full)
{
    const char *p = text, *colon = strchr(text, ':'), *release;
    if (!*p) return 0;
    if (colon) {
        if (colon == p) return 0;
        while (p < colon) if (!digit((unsigned char)*p++)) return 0;
        if (!*++p) return 0;
    }
    for (text = p; *p; ++p)
        if ((unsigned char)*p <= 32 || (unsigned char)*p >= 127 || strchr("/:<=>", *p)) return 0;
    if (!full) return 1;
    release = strrchr(text, '-');
    if (!release || release == text || !release[1]) return 0;
    for (p = release + 1; *p; ++p)
        if (!digit((unsigned char)*p) && *p != '.') return 0;
    return digit((unsigned char)release[1]) && digit((unsigned char)p[-1]);
}

static int relative_path(const char *path)
{
    const char *p = path;
    if (!*p || *p == '/') return 0;
    while (*p) {
        const char *end = strchr(p, '/');
        size_t n = end ? (size_t)(end - p) : strlen(p);
        if (!n || (n == 1 && *p == '.') || (n == 2 && !memcmp(p, "..", 2))) return 0;
        if (!end) return 1;
        p = end + 1;
    }
    return 0;
}

static int relation_value(struct holy_pacman_field *field)
{
    struct holy_pacman_relation *r = &field->relation;
    char *operator, *description, *colon;
    size_t length;
    r->storage = strdup(field->value);
    if (!r->storage) return 0;
    r->name = r->storage; r->comparison = "any"; r->version = "-";
    if (field->kind == HOLY_PACMAN_OPTIONAL && (description = strstr(r->storage, ": "))) {
        *description = 0; r->description = description + 2;
        if (!*r->description) return 0;
    }
    operator = strpbrk(r->storage, "<=>");
    if (operator) {
        char first = *operator, second = operator[1];
        *operator++ = 0;
        if (first == '=') r->comparison = "eq";
        else if (second == '=') { r->comparison = first == '<' ? "le" : "ge"; ++operator; }
        else r->comparison = first == '<' ? "lt" : "gt";
        r->version = operator;
        if (!version_value(r->version, 0) ||
            (field->kind == HOLY_PACMAN_PROVIDE && strcmp(r->comparison, "eq"))) return 0;
    }
    colon = strchr(r->storage, ':');
    if (colon) {
        if (operator || (field->kind != HOLY_PACMAN_DEPEND && field->kind != HOLY_PACMAN_PROVIDE)) return 0;
        *colon = 0; r->prefix = r->storage; r->name = colon + 1;
        if (!package_name(r->prefix) || !*r->name || strchr(r->name, ':') ||
            strchr(r->name, '/') || strchr(r->name, ' ') || !strstr(r->name, ".so")) return 0;
        r->kind = HOLY_PACMAN_SONAME_V2;
        return 1;
    }
    if (!package_name(r->name)) return 0;
    length = strlen(r->name);
    if (length > 3 && !strcmp(r->name + length - 3, ".so") &&
        (field->kind == HOLY_PACMAN_DEPEND || field->kind == HOLY_PACMAN_PROVIDE)) {
        const char *dash = strrchr(r->version, '-');
        r->kind = HOLY_PACMAN_SONAME_V1;
        if (!operator) return 1;
        if (strcmp(r->comparison, "eq") || !dash || dash == r->version ||
            (strcmp(dash + 1, "32") && strcmp(dash + 1, "64"))) return 0;
        r->elf_class = !strcmp(dash + 1, "32") ? 32 : 64;
    }
    return 1;
}

static int numeric(const char *text)
{
    const unsigned char *p = (const unsigned char *)text;
    uint64_t value = 0;
    if (!*p) return 0;
    for (; *p; ++p) {
        if (!digit(*p) || value > (UINT64_MAX - (*p - '0')) / 10) return 0;
        value = value * 10 + (*p - '0');
    }
    return 1;
}

void holy_pacman_free(struct holy_pacman_metadata *metadata)
{
    size_t i;
    for (i = 0; i < metadata->count; ++i) free(metadata->fields[i].relation.storage);
    free(metadata->fields); free(metadata->storage);
    memset(metadata, 0, sizeof *metadata);
}

int holy_pacman_parse(const char *bytes, size_t size,
                      struct holy_pacman_metadata *out, struct holy_pacman_error *error)
{
    char *line, *end;
    size_t number = 0, capacity = 0, extras = 0, seen[sizeof keywords / sizeof *keywords] = {0};
    memset(out, 0, sizeof *out); memset(error, 0, sizeof *error);
    error->message = "invalid PKGINFO";
    if (!size || size > 1024u * 1024u || memchr(bytes, 0, size)) return 0;
    out->storage = malloc(size + 1);
    if (!out->storage) { error->message = "allocation failed"; return 0; }
    memcpy(out->storage, bytes, size); out->storage[size] = 0;
    for (line = out->storage; line; line = end) {
        char *value, *p;
        size_t i, key_length;
        struct holy_pacman_field *field;
        end = strchr(line, '\n');
        if (end) *end++ = 0;
        error->line = ++number;
        if (strlen(line) > 65536) { error->message = "oversized line"; return 0; }
        while (*line == ' ' || *line == '\t') ++line;
        if (!*line || *line == '#') continue;
        value = strstr(line, " = ");
        if (!value) { error->message = "expected key = value"; return 0; }
        key_length = (size_t)(value - line);
        if (!key_length) return 0;
        for (p = line; p < value; ++p)
            if (!alpha((unsigned char)*p) && !digit((unsigned char)*p) && *p != '_') return 0;
        *value = 0; value += 3;
        if (!text_value(value)) { error->message = "invalid UTF-8 or control byte"; return 0; }
        for (i = 0; i < sizeof keywords / sizeof *keywords; ++i)
            if (!strcmp(line, keywords[i].name)) break;
        if (i < sizeof keywords / sizeof *keywords) {
            if (!keywords[i].list && seen[i]) { error->message = "duplicate scalar"; return 0; }
            if (!keywords[i].empty && !*value) { error->message = "empty value"; return 0; }
            seen[i] = number;
        }
        if (out->count == capacity) {
            size_t next = capacity ? capacity * 2 : 32;
            void *grown;
            if (next < capacity || next > SIZE_MAX / sizeof *out->fields) return 0;
            grown = realloc(out->fields, next * sizeof *out->fields);
            if (!grown) { error->message = "allocation failed"; return 0; }
            out->fields = grown; capacity = next;
        }
        field = &out->fields[out->count++];
        memset(field, 0, sizeof *field);
        field->key = line; field->value = value; field->line = number;
        field->kind = i < sizeof keywords / sizeof *keywords ? keywords[i].kind : HOLY_PACMAN_UNKNOWN;
        if (field->kind == HOLY_PACMAN_UNKNOWN) ++out->unknown_count;
        if (!strcmp(line, "pkgname")) {
            if (!package_name(value)) return 0;
            out->name = value;
        } else if (!strcmp(line, "pkgbase")) {
            if (!package_name(value)) return 0;
            out->base = value;
        } else if (!strcmp(line, "pkgver")) {
            if (!version_value(value, 1)) return 0;
            out->version = value;
        } else if (!strcmp(line, "arch")) {
            for (p = value; *p; ++p) if (!alpha((unsigned char)*p) && !digit((unsigned char)*p) && *p != '_') return 0;
            out->arch = value;
        } else if (!strcmp(line, "builddate") || !strcmp(line, "size")) {
            if (!numeric(value)) return 0;
        } else if (field->kind >= HOLY_PACMAN_DEPEND && field->kind <= HOLY_PACMAN_REPLACE) {
            if (!relation_value(field)) { error->message = "invalid package relation"; return 0; }
        } else if (field->kind == HOLY_PACMAN_BACKUP) {
            if (!relative_path(value)) { error->message = "unsafe backup path"; return 0; }
        } else if (field->kind == HOLY_PACMAN_EXTRA) {
            p = strchr(value, '=');
            ++extras;
            if (!p || p == value || !p[1]) return 0;
            if (!strncmp(value, "pkgtype=", 8)) {
                if (out->package_type) { error->message = "duplicate pkgtype"; return 0; }
                out->package_type = value + 8;
                if (strcmp(out->package_type, "pkg") && strcmp(out->package_type, "split") &&
                    strcmp(out->package_type, "debug") && strcmp(out->package_type, "src")) ++out->unknown_count;
            } else ++out->unknown_count;
        }
    }
    error->line = number;
    if (!out->name || !out->version || !out->arch || (extras && !out->package_type)) {
        error->message = "missing identity or pkgtype"; return 0;
    }
    error->line = 0; error->message = NULL;
    return 1;
}