diff options
Diffstat (limited to 'refs.c')
-rw-r--r-- | refs.c | 137 |
1 files changed, 109 insertions, 28 deletions
@@ -63,7 +63,7 @@ static unsigned char refname_disposition[256] = { * not legal. It is legal if it is something reasonable to have under * ".git/refs/"; We do not like it if: * - * - any path component of it begins with ".", or + * - it begins with ".", or * - it has double dots "..", or * - it has ASCII control characters, or * - it has ":", "?", "[", "\", "^", "~", SP, or TAB anywhere, or @@ -71,31 +71,63 @@ static unsigned char refname_disposition[256] = { * - it ends with a "/", or * - it ends with ".lock", or * - it contains a "@{" portion + * + * When sanitized is not NULL, instead of rejecting the input refname + * as an error, try to come up with a usable replacement for the input + * refname in it. */ -static int check_refname_component(const char *refname, int *flags) +static int check_refname_component(const char *refname, int *flags, + struct strbuf *sanitized) { const char *cp; char last = '\0'; + size_t component_start = 0; /* garbage - not a reasonable initial value */ + + if (sanitized) + component_start = sanitized->len; for (cp = refname; ; cp++) { int ch = *cp & 255; unsigned char disp = refname_disposition[ch]; + + if (sanitized && disp != 1) + strbuf_addch(sanitized, ch); + switch (disp) { case 1: goto out; case 2: - if (last == '.') - return -1; /* Refname contains "..". */ + if (last == '.') { /* Refname contains "..". */ + if (sanitized) + /* collapse ".." to single "." */ + strbuf_setlen(sanitized, sanitized->len - 1); + else + return -1; + } break; case 3: - if (last == '@') - return -1; /* Refname contains "@{". */ + if (last == '@') { /* Refname contains "@{". */ + if (sanitized) + sanitized->buf[sanitized->len-1] = '-'; + else + return -1; + } break; case 4: - return -1; + /* forbidden char */ + if (sanitized) + sanitized->buf[sanitized->len-1] = '-'; + else + return -1; + break; case 5: - if (!(*flags & REFNAME_REFSPEC_PATTERN)) - return -1; /* refspec can't be a pattern */ + if (!(*flags & REFNAME_REFSPEC_PATTERN)) { + /* refspec can't be a pattern */ + if (sanitized) + sanitized->buf[sanitized->len-1] = '-'; + else + return -1; + } /* * Unset the pattern flag so that we only accept @@ -109,26 +141,48 @@ static int check_refname_component(const char *refname, int *flags) out: if (cp == refname) return 0; /* Component has zero length. */ - if (refname[0] == '.') - return -1; /* Component starts with '.'. */ + + if (refname[0] == '.') { /* Component starts with '.'. */ + if (sanitized) + sanitized->buf[component_start] = '-'; + else + return -1; + } if (cp - refname >= LOCK_SUFFIX_LEN && - !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN)) - return -1; /* Refname ends with ".lock". */ + !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN)) { + if (!sanitized) + return -1; + /* Refname ends with ".lock". */ + while (strbuf_strip_suffix(sanitized, LOCK_SUFFIX)) { + /* try again in case we have .lock.lock */ + } + } return cp - refname; } -int check_refname_format(const char *refname, int flags) +static int check_or_sanitize_refname(const char *refname, int flags, + struct strbuf *sanitized) { int component_len, component_count = 0; - if (!strcmp(refname, "@")) + if (!strcmp(refname, "@")) { /* Refname is a single character '@'. */ - return -1; + if (sanitized) + strbuf_addch(sanitized, '-'); + else + return -1; + } while (1) { + if (sanitized && sanitized->len) + strbuf_complete(sanitized, '/'); + /* We are at the start of a path component. */ - component_len = check_refname_component(refname, &flags); - if (component_len <= 0) + component_len = check_refname_component(refname, &flags, + sanitized); + if (sanitized && component_len == 0) + ; /* OK, omit empty component */ + else if (component_len <= 0) return -1; component_count++; @@ -138,13 +192,29 @@ int check_refname_format(const char *refname, int flags) refname += component_len + 1; } - if (refname[component_len - 1] == '.') - return -1; /* Refname ends with '.'. */ + if (refname[component_len - 1] == '.') { + /* Refname ends with '.'. */ + if (sanitized) + ; /* omit ending dot */ + else + return -1; + } if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2) return -1; /* Refname has only one component. */ return 0; } +int check_refname_format(const char *refname, int flags) +{ + return check_or_sanitize_refname(refname, flags, NULL); +} + +void sanitize_refname_component(const char *refname, struct strbuf *out) +{ + if (check_or_sanitize_refname(refname, REFNAME_ALLOW_ONELEVEL, out)) + BUG("sanitizing refname '%s' check returned error", refname); +} + int refname_is_safe(const char *refname) { const char *rest; @@ -309,7 +379,7 @@ static int filter_refs(const char *refname, const struct object_id *oid, enum peel_status peel_object(const struct object_id *name, struct object_id *oid) { - struct object *o = lookup_unknown_object(name->hash); + struct object *o = lookup_unknown_object(name); if (o->type == OBJ_NONE) { int type = oid_object_info(the_repository, name, NULL); @@ -1702,7 +1772,7 @@ int resolve_gitlink_ref(const char *submodule, const char *refname, struct ref_store_hash_entry { - struct hashmap_entry ent; /* must be the first member! */ + struct hashmap_entry ent; struct ref_store *refs; @@ -1711,11 +1781,16 @@ struct ref_store_hash_entry }; static int ref_store_hash_cmp(const void *unused_cmp_data, - const void *entry, const void *entry_or_key, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *keydata) { - const struct ref_store_hash_entry *e1 = entry, *e2 = entry_or_key; - const char *name = keydata ? keydata : e2->name; + const struct ref_store_hash_entry *e1, *e2; + const char *name; + + e1 = container_of(eptr, const struct ref_store_hash_entry, ent); + e2 = container_of(entry_or_key, const struct ref_store_hash_entry, ent); + name = keydata ? keydata : e2->name; return strcmp(e1->name, name); } @@ -1726,7 +1801,7 @@ static struct ref_store_hash_entry *alloc_ref_store_hash_entry( struct ref_store_hash_entry *entry; FLEX_ALLOC_STR(entry, name, name); - hashmap_entry_init(entry, strhash(name)); + hashmap_entry_init(&entry->ent, strhash(name)); entry->refs = refs; return entry; } @@ -1745,12 +1820,15 @@ static struct ref_store *lookup_ref_store_map(struct hashmap *map, const char *name) { struct ref_store_hash_entry *entry; + unsigned int hash; if (!map->tablesize) /* It's initialized on demand in register_ref_store(). */ return NULL; - entry = hashmap_get_from_hash(map, strhash(name), name); + hash = strhash(name); + entry = hashmap_get_entry_from_hash(map, hash, name, + struct ref_store_hash_entry, ent); return entry ? entry->refs : NULL; } @@ -1793,10 +1871,13 @@ static void register_ref_store_map(struct hashmap *map, struct ref_store *refs, const char *name) { + struct ref_store_hash_entry *entry; + if (!map->tablesize) hashmap_init(map, ref_store_hash_cmp, NULL, 0); - if (hashmap_put(map, alloc_ref_store_hash_entry(name, refs))) + entry = alloc_ref_store_hash_entry(name, refs); + if (hashmap_put(map, &entry->ent)) BUG("%s ref_store '%s' initialized twice", type, name); } |