/*
* 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.
*/
/*
* 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
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)
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);
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;
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);
}
* 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;
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;
}
}
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];
*/
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 */
errno = EINVAL;
return NULL;
}
+ if (bad_name) {
+ hashclr(sha1);
+ if (flags)
+ *flags |= REF_ISBROKEN;
+ }
return refname;
}
if (flags)
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;
}
}
}
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);
struct string_list_item *ref_to_delete;
int i, ret, removed = 0;
+ assert(err);
+
/* Look for a packed ref */
for (i = 0; i < n; i++)
if (get_packed_ref(refnames[i]))
return 0; /* no refname exists in packed refs */
if (lock_packed_refs(0)) {
- if (err) {
- unable_to_lock_message(git_path("packed-refs"), errno,
- err);
- return -1;
- }
- unable_to_lock_error(git_path("packed-refs"), errno);
- return error("cannot delete '%s' from packed refs", refnames[i]);
+ unable_to_lock_message(git_path("packed-refs"), errno, err);
+ return -1;
}
packed = get_packed_refs(&ref_cache);
/* Write what remains */
ret = commit_packed_refs();
- if (ret && err)
+ if (ret)
strbuf_addf(err, "unable to overwrite old ref-pack file: %s",
strerror(errno));
return ret;
static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
{
+ assert(err);
+
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
/*
* loose. The loose file name is the same as the
logfd = open(logfile, oflags, 0666);
if (logfd < 0) {
- if (!(oflags & O_CREAT) && errno == ENOENT)
+ if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR))
return 0;
- if ((oflags & O_CREAT) && errno == EISDIR) {
+ if (errno == EISDIR) {
if (remove_empty_directories(logfile)) {
int save_errno = errno;
error("There are still logs under '%s'",
struct ref_transaction *ref_transaction_begin(struct strbuf *err)
{
+ assert(err);
+
return xcalloc(1, sizeof(struct ref_transaction));
}
{
struct ref_update *update;
+ assert(err);
+
if (transaction->state != REF_TRANSACTION_OPEN)
die("BUG: update called for transaction that is not open");
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;
{
struct ref_update *update;
+ assert(err);
+
if (transaction->state != REF_TRANSACTION_OPEN)
die("BUG: create called for transaction that is not open");
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);
{
struct ref_update *update;
+ assert(err);
+
if (transaction->state != REF_TRANSACTION_OPEN)
die("BUG: delete called for transaction that is not open");
struct strbuf *err)
{
int i;
+
+ assert(err);
+
for (i = 1; i < n; i++)
if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
- const char *str =
- "Multiple updates for ref '%s' not allowed.";
- if (err)
- strbuf_addf(err, str, updates[i]->refname);
-
+ strbuf_addf(err,
+ "Multiple updates for ref '%s' not allowed.",
+ updates[i]->refname);
return 1;
}
return 0;
int n = transaction->nr;
struct ref_update **updates = transaction->updates;
+ assert(err);
+
if (transaction->state != REF_TRANSACTION_OPEN)
die("BUG: commit called for transaction that is not open");
ret = (errno == ENOTDIR)
? TRANSACTION_NAME_CONFLICT
: TRANSACTION_GENERIC_ERROR;
- if (err)
- strbuf_addf(err, "Cannot lock the ref '%s'.",
- update->refname);
+ strbuf_addf(err, "Cannot lock the ref '%s'.",
+ update->refname);
goto cleanup;
}
}
if (write_ref_sha1(update->lock, update->new_sha1,
update->msg)) {
update->lock = NULL; /* freed by write_ref_sha1 */
- if (err)
- strbuf_addf(err, "Cannot update the ref '%s'.",
- update->refname);
+ strbuf_addf(err, "Cannot update the ref '%s'.",
+ update->refname);
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
struct ref_update *update = updates[i];
if (update->lock) {
- if (delete_ref_loose(update->lock, update->type, err))
+ if (delete_ref_loose(update->lock, update->type, err)) {
ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
if (!(update->flags & REF_ISPRUNING))
delnames[delnum++] = update->lock->ref_name;
}
}
- if (repack_without_refs(delnames, delnum, err))
+ if (repack_without_refs(delnames, delnum, err)) {
ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
for (i = 0; i < delnum; i++)
unlink_or_warn(git_path("logs/%s", delnames[i]));
clear_loose_ref_cache(&ref_cache);