RFC 5321 is clear that the local-part of an email address (the part before the at sign) is case sensitive, and this has been the case since the original RFC 821. It directs us that "the local-part MUST be interpreted and assigned semantics only by the host specified in the domain part of the address." Since we are not that party, it's not correct for us to compare them case insensitively. However, we do still want to compare the domain parts case insensitively, so let's add a helper that downcases the domain and then compare byte by byte. Similarly, it's not possible for us to correctly case-fold text in a locale-insensitive way, so our handling of personal names has also been subject to bugs. Additionally, we've never handled non-ASCII characters correctly, which means that our previous comparisons really only worked well for a fraction of the people on the planet. Since our code wasn't right and it's basically impossible to compare personal names without regard to case, let's also switch our matching of names to be byte by byte. Signed-off-by: brian m. carlson <sandals@xxxxxxxxxxxxxxxxxxxx> --- mailmap.c | 27 ++++++++++++++++++++++++--- t/t4203-mailmap.sh | 4 ++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/mailmap.c b/mailmap.c index d3287b409a..5c52dbb7e0 100644 --- a/mailmap.c +++ b/mailmap.c @@ -64,7 +64,22 @@ static void free_mailmap_entry(void *p, const char *s) */ static int namemap_cmp(const char *a, const char *b) { - return strcasecmp(a, b); + return strcmp(a, b); +} + +/* + * Lowercases the domain (and only the domain) part of an email address. The + * local-part, which is defined by RFC 5321 to be case sensitive, is not + * affected. + */ +static char *lowercase_email(char *s) +{ + char *end = strchrnul(s, '@'); + while (*end) { + *end = tolower(*end); + end++; + } + return s; } static void add_mapping(struct string_list *map, @@ -74,9 +89,13 @@ static void add_mapping(struct string_list *map, struct mailmap_entry *me; struct string_list_item *item; + lowercase_email(new_email); + if (old_email == NULL) { old_email = new_email; new_email = NULL; + } else { + lowercase_email(old_email); } item = string_list_insert(map, old_email); @@ -300,7 +319,7 @@ static struct string_list_item *lookup_prefix(struct string_list *map, * real location of the key if one exists. */ while (0 <= --i && i < map->nr) { - int cmp = strncasecmp(map->items[i].string, string, len); + int cmp = strncmp(map->items[i].string, string, len); if (cmp < 0) /* * "i" points at a key definitely below the prefix; @@ -323,6 +342,7 @@ int map_user(struct mailmap *map, const char **email, size_t *emaillen, const char **name, size_t *namelen) { + char *searchable_email = xstrndup(*email, *emaillen); struct string_list_item *item; struct mailmap_entry *me; @@ -330,7 +350,8 @@ int map_user(struct mailmap *map, (int)*namelen, debug_str(*name), (int)*emaillen, debug_str(*email)); - item = lookup_prefix(map->mailmap, *email, *emaillen); + item = lookup_prefix(map->mailmap, searchable_email, *emaillen); + free(searchable_email); if (item != NULL) { me = (struct mailmap_entry *)item->util; if (me->namemap.nr) { diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh index 32e849504c..df4a0e03cc 100755 --- a/t/t4203-mailmap.sh +++ b/t/t4203-mailmap.sh @@ -187,7 +187,7 @@ nick1 (1): EOF -test_expect_failure 'name entry after email entry, case-sensitive local-part' ' +test_expect_success 'name entry after email entry, case-sensitive local-part' ' mkdir -p internal_mailmap && echo "<bugs@xxxxxxxxxx> <bugs@xxxxxxxxxx>" >internal_mailmap/.mailmap && echo "Internal Guy <BUGS@xxxxxxxxxx>" >>internal_mailmap/.mailmap && @@ -195,7 +195,7 @@ test_expect_failure 'name entry after email entry, case-sensitive local-part' ' test_cmp expect actual ' -test_expect_failure 'name entry after email entry, case-sensitive personal name' ' +test_expect_success 'name entry after email entry, case-sensitive personal name' ' mkdir -p internal_mailmap && echo "<bugs@xxxxxxxxxx> <bugs@xxxxxxxxxx>" >internal_mailmap/.mailmap && echo "Nick1 <bugs@xxxxxxxxxx> NICK1 <bugs@xxxxxxxxxx>" >internal_mailmap/.mailmap &&