refs.c: allow listing and deleting badly named refs
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index fe1352a344e68b7c9ef63f4626314e6ba3e15f62..e7000f2cb202ea41b429b1bd68f3a05fe2f0a1f9 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -187,8 +187,8 @@ struct ref_dir {
 
 /*
  * Bit values for ref_entry::flag.  REF_ISSYMREF=0x01,
- * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see
- * refs.h.
+ * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are
+ * public values; see refs.h.
  */
 
 /*
@@ -196,16 +196,16 @@ struct ref_dir {
  * the correct peeled value for the reference, which might be
  * null_sha1 if the reference is not a tag or if it is broken.
  */
-#define REF_KNOWS_PEELED 0x08
+#define REF_KNOWS_PEELED 0x10
 
 /* ref_entry represents a directory of references */
-#define REF_DIR 0x10
+#define REF_DIR 0x20
 
 /*
  * Entry has not yet been read from disk (used only for REF_DIR
  * entries representing loose references)
  */
-#define REF_INCOMPLETE 0x20
+#define REF_INCOMPLETE 0x40
 
 /*
  * A ref_entry represents either a reference or a "subdirectory" of
@@ -274,6 +274,39 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)
        return dir;
 }
 
+/*
+ * Check if a refname is safe.
+ * For refs that start with "refs/" we consider it safe as long they do
+ * not try to resolve to outside of refs/.
+ *
+ * For all other refs we only consider them safe iff they only contain
+ * upper case characters and '_' (like "HEAD" AND "MERGE_HEAD", and not like
+ * "config").
+ */
+static int refname_is_safe(const char *refname)
+{
+       if (starts_with(refname, "refs/")) {
+               char *buf;
+               int result;
+
+               buf = xmalloc(strlen(refname) + 1);
+               /*
+                * Does the refname try to escape refs/?
+                * For example: refs/foo/../bar is safe but refs/foo/../../bar
+                * is not.
+                */
+               result = !normalize_path_copy(buf, refname + strlen("refs/"));
+               free(buf);
+               return result;
+       }
+       while (*refname) {
+               if (!isupper(*refname) && *refname != '_')
+                       return 0;
+               refname++;
+       }
+       return 1;
+}
+
 static struct ref_entry *create_ref_entry(const char *refname,
                                          const unsigned char *sha1, int flag,
                                          int check_name)
@@ -284,6 +317,8 @@ static struct ref_entry *create_ref_entry(const char *refname,
        if (check_name &&
            check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
                die("Reference has invalid format: '%s'", refname);
+       if (!check_name && !refname_is_safe(refname))
+               die("Reference has invalid name: '%s'", refname);
        len = strlen(refname) + 1;
        ref = xmalloc(sizeof(struct ref_entry) + len);
        hashcpy(ref->u.value.sha1, sha1);
@@ -1111,7 +1146,13 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
 
                refname = parse_ref_line(refline, sha1);
                if (refname) {
-                       last = create_ref_entry(refname, sha1, REF_ISPACKED, 1);
+                       int flag = REF_ISPACKED;
+
+                       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+                               hashclr(sha1);
+                               flag |= REF_BAD_NAME | REF_ISBROKEN;
+                       }
+                       last = create_ref_entry(refname, sha1, flag, 0);
                        if (peeled == PEELED_FULLY ||
                            (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
                                last->flag |= REF_KNOWS_PEELED;
@@ -1249,8 +1290,13 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
                                hashclr(sha1);
                                flag |= REF_ISBROKEN;
                        }
+                       if (check_refname_format(refname.buf,
+                                                REFNAME_ALLOW_ONELEVEL)) {
+                               hashclr(sha1);
+                               flag |= REF_BAD_NAME | REF_ISBROKEN;
+                       }
                        add_entry_to_dir(dir,
-                                        create_ref_entry(refname.buf, sha1, flag, 1));
+                                        create_ref_entry(refname.buf, sha1, flag, 0));
                }
                strbuf_setlen(&refname, dirnamelen);
        }
@@ -1369,10 +1415,10 @@ static struct ref_entry *get_packed_ref(const char *refname)
  * A loose ref file doesn't exist; check for a packed ref.  The
  * options are forwarded from resolve_safe_unsafe().
  */
-static const char *handle_missing_loose_ref(const char *refname,
-                                           int resolve_flags,
-                                           unsigned char *sha1,
-                                           int *flags)
+static int resolve_missing_loose_ref(const char *refname,
+                                    int resolve_flags,
+                                    unsigned char *sha1,
+                                    int *flags)
 {
        struct ref_entry *entry;
 
@@ -1385,14 +1431,15 @@ static const char *handle_missing_loose_ref(const char *refname,
                hashcpy(sha1, entry->u.value.sha1);
                if (flags)
                        *flags |= REF_ISPACKED;
-               return refname;
+               return 0;
        }
        /* The reference is not a packed reference, either. */
        if (resolve_flags & RESOLVE_REF_READING) {
-               return NULL;
+               errno = ENOENT;
+               return -1;
        } else {
                hashclr(sha1);
-               return refname;
+               return 0;
        }
 }
 
@@ -1403,13 +1450,29 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
        ssize_t len;
        char buffer[256];
        static char refname_buffer[256];
+       int bad_name = 0;
 
        if (flags)
                *flags = 0;
 
        if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
-               errno = EINVAL;
-               return NULL;
+               if (flags)
+                       *flags |= REF_BAD_NAME;
+
+               if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
+                   !refname_is_safe(refname)) {
+                       errno = EINVAL;
+                       return NULL;
+               }
+               /*
+                * dwim_ref() uses REF_ISBROKEN to distinguish between
+                * missing refs and refs that were present but invalid,
+                * to complain about the latter to stderr.
+                *
+                * We don't know whether the ref exists, so don't set
+                * REF_ISBROKEN yet.
+                */
+               bad_name = 1;
        }
        for (;;) {
                char path[PATH_MAX];
@@ -1435,11 +1498,17 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
                 */
        stat_ref:
                if (lstat(path, &st) < 0) {
-                       if (errno == ENOENT)
-                               return handle_missing_loose_ref(refname,
-                                               resolve_flags, sha1, flags);
-                       else
+                       if (errno != ENOENT)
+                               return NULL;
+                       if (resolve_missing_loose_ref(refname, resolve_flags,
+                                                     sha1, flags))
                                return NULL;
+                       if (bad_name) {
+                               hashclr(sha1);
+                               if (flags)
+                                       *flags |= REF_ISBROKEN;
+                       }
+                       return refname;
                }
 
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
@@ -1512,6 +1581,11 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
                                errno = EINVAL;
                                return NULL;
                        }
+                       if (bad_name) {
+                               hashclr(sha1);
+                               if (flags)
+                                       *flags |= REF_ISBROKEN;
+                       }
                        return refname;
                }
                if (flags)
@@ -1527,8 +1601,13 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
                if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
                        if (flags)
                                *flags |= REF_ISBROKEN;
-                       errno = EINVAL;
-                       return NULL;
+
+                       if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
+                           !refname_is_safe(buf)) {
+                               errno = EINVAL;
+                               return NULL;
+                       }
+                       bad_name = 1;
                }
        }
 }
@@ -2160,18 +2239,16 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
        int missing = 0;
        int attempts_remaining = 3;
 
-       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
-               errno = EINVAL;
-               return NULL;
-       }
-
        lock = xcalloc(1, sizeof(struct ref_lock));
        lock->lock_fd = -1;
 
        if (mustexist)
                resolve_flags |= RESOLVE_REF_READING;
-       if (flags & REF_NODEREF && flags & REF_DELETING)
-               resolve_flags |= RESOLVE_REF_NO_RECURSE;
+       if (flags & REF_DELETING) {
+               resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
+               if (flags & REF_NODEREF)
+                       resolve_flags |= RESOLVE_REF_NO_RECURSE;
+       }
 
        refname = resolve_ref_unsafe(refname, resolve_flags,
                                     lock->old_sha1, &type);
@@ -3519,6 +3596,13 @@ int ref_transaction_update(struct ref_transaction *transaction,
        if (have_old && !old_sha1)
                die("BUG: have_old is true but old_sha1 is NULL");
 
+       if (!is_null_sha1(new_sha1) &&
+           check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+               strbuf_addf(err, "refusing to update ref with bad name %s",
+                           refname);
+               return -1;
+       }
+
        update = add_update(transaction, refname);
        hashcpy(update->new_sha1, new_sha1);
        update->flags = flags;
@@ -3544,6 +3628,12 @@ int ref_transaction_create(struct ref_transaction *transaction,
        if (!new_sha1 || is_null_sha1(new_sha1))
                die("BUG: create ref with null new_sha1");
 
+       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+               strbuf_addf(err, "refusing to create ref with bad name %s",
+                           refname);
+               return -1;
+       }
+
        update = add_update(transaction, refname);
 
        hashcpy(update->new_sha1, new_sha1);