{"thread":{"id":"18880","subject":"[PATCH] fetch: Strip usernames from url's before storing them","startedAt":"2009-04-15T12:16:56Z","lastAt":"2009-04-20T08:36:30Z","messageCount":13,"participants":["Andreas Ericsson","Michael J Gruber","Johannes Sixt","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"111355","messageId":"1239797816-24582-1-git-send-email-ae@op5.se","threadId":"18880","inReplyTo":null,"subject":"[PATCH] fetch: Strip usernames from url's before storing them","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2009-04-15T12:16:56Z","receivedAt":"2009-04-15T12:16:56Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"When pulling from a remote, the full URL including username\nis by default added to the commit message. Since it adds\nvery little value but could be used by malicious people to\nglean valid usernames (with matching hostnames), we're far\nbetter off just stripping the username before storing the\nremote URL locally.\n\nSigned-off-by: Andreas Ericsson <ae@op5.se>\n---\n builtin-fetch.c |   48 ++++++++++++++++++++++++++++++++++++++++++++++--\n 1 files changed, 46 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin-fetch.c b/builtin-fetch.c\nindex 3c998ea..47fba00 100644\n--- a/builtin-fetch.c\n+++ b/builtin-fetch.c\n@@ -289,7 +289,48 @@ static int update_local_ref(struct ref *ref,\n \t}\n }\n \n-static int store_updated_refs(const char *url, const char *remote_name,\n+/*\n+ * strip username information from the url\n+ * This will allocate a new string, or return its argument\n+ * if no stripping is necessary.\n+ *\n+ * The url's we want to catch are the following:\n+ *   ssh://[user@]host.xz[:port]/path/to/repo.git/\n+ *   [user@]host.xz:/path/to/repo.git/\n+ *   http[s]://[user[:password]@]host.xz/path/to/repo.git\n+ *\n+ * Although git doesn't currently support giving the password\n+ * to http url's on the command-line, it's easier to catch\n+ * that case too than it is to cater for it specially.\n+ */\n+static char *anonymize_url(const char *url)\n+{\n+\tchar *anon_url;\n+\tconst char *at_sign = strchr(url, '@');\n+\tsize_t prefix_len = 0;\n+\n+\tif (!at_sign)\n+\t\treturn strdup(url);\n+\n+\tif (!prefixcmp(url, \"ssh://\"))\n+\t\tprefix_len = strlen(\"ssh://\");\n+\telse if (!prefixcmp(url, \"http://\"))\n+\t\tprefix_len = strlen(\"http://\");\n+\telse if (!prefixcmp(url, \"https://\"))\n+\t\tprefix_len = strlen(\"https://\");\n+\telse if (!strchr(at_sign + 1, ':'))\n+\t\treturn strdup(url);\n+\n+\tanon_url = xcalloc(1, 1 + prefix_len +\n+\t\t\t   ((unsigned long)at_sign - (unsigned long)url));\n+\tif (prefix_len)\n+\t\tmemcpy(anon_url, url, prefix_len);\n+\tmemcpy(anon_url + prefix_len, at_sign + 1, strlen(at_sign + 1));\n+\n+\treturn anon_url;\n+}\n+\n+static int store_updated_refs(const char *raw_url, const char *remote_name,\n \t\tstruct ref *ref_map)\n {\n \tFILE *fp;\n@@ -298,11 +339,13 @@ static int store_updated_refs(const char *url, const char *remote_name,\n \tchar note[1024];\n \tconst char *what, *kind;\n \tstruct ref *rm;\n-\tchar *filename = git_path(\"FETCH_HEAD\");\n+\tchar *url, *filename = git_path(\"FETCH_HEAD\");\n \n \tfp = fopen(filename, \"a\");\n \tif (!fp)\n \t\treturn error(\"cannot open %s: %s\\n\", filename, strerror(errno));\n+\n+\turl = anonymize_url(raw_url);\n \tfor (rm = ref_map; rm; rm = rm->next) {\n \t\tstruct ref *ref = NULL;\n \n@@ -376,6 +419,7 @@ static int store_updated_refs(const char *url, const char *remote_name,\n \t\t\t\tfprintf(stderr, \" %s\\n\", note);\n \t\t}\n \t}\n+\tfree(url);\n \tfclose(fp);\n \tif (rc & 2)\n \t\terror(\"some local refs could not be updated; try running\\n\"\n-- \n1.6.3.rc0.2.g7cd31\n"},{"id":"111357","messageId":"49E5D372.1090504@drmicha.warpmail.net","threadId":"18880","inReplyTo":"1239797816-24582-1-git-send-email-ae@op5.se","subject":"Re: [PATCH] fetch: Strip usernames from url's before storing them","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2009-04-15T12:30:42Z","receivedAt":"2009-04-15T12:30:42Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Andreas Ericsson venit, vidit, dixit 15.04.2009 14:16:\n> When pulling from a remote, the full URL including username\n> is by default added to the commit message. Since it adds\n> very little value but could be used by malicious people to\n> glean valid usernames (with matching hostnames), we're far\n> better off just stripping the username before storing the\n> remote URL locally.\n\nUhm, this is for non-fast-forwards when pull uses \"merge\" and creates a\nmerge commit, right?\nFetch does not create commit messages, and pull does not either if it\nrebases. So maybe the commit message could make it clearer for lesser\ngit-educated people such as myself ;)\n\nMichael\n> Signed-off-by: Andreas Ericsson <ae@op5.se>\n> ---\n>  builtin-fetch.c |   48 ++++++++++++++++++++++++++++++++++++++++++++++--\n>  1 files changed, 46 insertions(+), 2 deletions(-)\n> \n> diff --git a/builtin-fetch.c b/builtin-fetch.c\n> index 3c998ea..47fba00 100644\n> --- a/builtin-fetch.c\n> +++ b/builtin-fetch.c\n> @@ -289,7 +289,48 @@ static int update_local_ref(struct ref *ref,\n>  \t}\n>  }\n>  \n> -static int store_updated_refs(const char *url, const char *remote_name,\n> +/*\n> + * strip username information from the url\n> + * This will allocate a new string, or return its argument\n> + * if no stripping is necessary.\n> + *\n> + * The url's we want to catch are the following:\n> + *   ssh://[user@]host.xz[:port]/path/to/repo.git/\n> + *   [user@]host.xz:/path/to/repo.git/\n> + *   http[s]://[user[:password]@]host.xz/path/to/repo.git\n> + *\n> + * Although git doesn't currently support giving the password\n> + * to http url's on the command-line, it's easier to catch\n> + * that case too than it is to cater for it specially.\n> + */\n> +static char *anonymize_url(const char *url)\n> +{\n> +\tchar *anon_url;\n> +\tconst char *at_sign = strchr(url, '@');\n> +\tsize_t prefix_len = 0;\n> +\n> +\tif (!at_sign)\n> +\t\treturn strdup(url);\n> +\n> +\tif (!prefixcmp(url, \"ssh://\"))\n> +\t\tprefix_len = strlen(\"ssh://\");\n> +\telse if (!prefixcmp(url, \"http://\"))\n> +\t\tprefix_len = strlen(\"http://\");\n> +\telse if (!prefixcmp(url, \"https://\"))\n> +\t\tprefix_len = strlen(\"https://\");\n> +\telse if (!strchr(at_sign + 1, ':'))\n> +\t\treturn strdup(url);\n> +\n> +\tanon_url = xcalloc(1, 1 + prefix_len +\n> +\t\t\t   ((unsigned long)at_sign - (unsigned long)url));\n> +\tif (prefix_len)\n> +\t\tmemcpy(anon_url, url, prefix_len);\n> +\tmemcpy(anon_url + prefix_len, at_sign + 1, strlen(at_sign + 1));\n> +\n> +\treturn anon_url;\n> +}\n> +\n> +static int store_updated_refs(const char *raw_url, const char *remote_name,\n>  \t\tstruct ref *ref_map)\n>  {\n>  \tFILE *fp;\n> @@ -298,11 +339,13 @@ static int store_updated_refs(const char *url, const char *remote_name,\n>  \tchar note[1024];\n>  \tconst char *what, *kind;\n>  \tstruct ref *rm;\n> -\tchar *filename = git_path(\"FETCH_HEAD\");\n> +\tchar *url, *filename = git_path(\"FETCH_HEAD\");\n>  \n>  \tfp = fopen(filename, \"a\");\n>  \tif (!fp)\n>  \t\treturn error(\"cannot open %s: %s\\n\", filename, strerror(errno));\n> +\n> +\turl = anonymize_url(raw_url);\n>  \tfor (rm = ref_map; rm; rm = rm->next) {\n>  \t\tstruct ref *ref = NULL;\n>  \n> @@ -376,6 +419,7 @@ static int store_updated_refs(const char *url, const char *remote_name,\n>  \t\t\t\tfprintf(stderr, \" %s\\n\", note);\n>  \t\t}\n>  \t}\n> +\tfree(url);\n>  \tfclose(fp);\n>  \tif (rc & 2)\n>  \t\terror(\"some local refs could not be updated; try running\\n\"\n"},{"id":"111358","messageId":"49E5DE98.1080600@viscovery.net","threadId":"18880","inReplyTo":"1239797816-24582-1-git-send-email-ae@op5.se","subject":"Re: [PATCH] fetch: Strip usernames from url's before storing them","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2009-04-15T13:18:16Z","receivedAt":"2009-04-15T13:18:16Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Andreas Ericsson schrieb:\n> +/*\n> + * strip username information from the url\n> + * This will allocate a new string, or return its argument\n> + * if no stripping is necessary.\n> + *\n> + * The url's we want to catch are the following:\n> + *   ssh://[user@]host.xz[:port]/path/to/repo.git/\n> + *   [user@]host.xz:/path/to/repo.git/\n> + *   http[s]://[user[:password]@]host.xz/path/to/repo.git\n> + *\n> + * Although git doesn't currently support giving the password\n> + * to http url's on the command-line, it's easier to catch\n> + * that case too than it is to cater for it specially.\n> + */\n> +static char *anonymize_url(const char *url)\n> +{\n> +\tchar *anon_url;\n> +\tconst char *at_sign = strchr(url, '@');\n> +\tsize_t prefix_len = 0;\n> +\n> +\tif (!at_sign)\n\n\tif (!at_sign || has_dos_drive_prefix(url))\n\nor even better move this function to transport.c and use is_local().\n\n> +\t\treturn strdup(url);\n> +\n> +\tif (!prefixcmp(url, \"ssh://\"))\n> +\t\tprefix_len = strlen(\"ssh://\");\n> +\telse if (!prefixcmp(url, \"http://\"))\n> +\t\tprefix_len = strlen(\"http://\");\n> +\telse if (!prefixcmp(url, \"https://\"))\n> +\t\tprefix_len = strlen(\"https://\");\n> +\telse if (!strchr(at_sign + 1, ':'))\n> +\t\treturn strdup(url);\n> +\n> +\tanon_url = xcalloc(1, 1 + prefix_len +\n> +\t\t\t   ((unsigned long)at_sign - (unsigned long)url));\n> +\tif (prefix_len)\n> +\t\tmemcpy(anon_url, url, prefix_len);\n> +\tmemcpy(anon_url + prefix_len, at_sign + 1, strlen(at_sign + 1));\n> +\n> +\treturn anon_url;\n> +}\n\n-- Hannes\n"},{"id":"111359","messageId":"49E5E8C5.4050501@op5.se","threadId":"18880","inReplyTo":"49E5D372.1090504@drmicha.warpmail.net","subject":"Re: [PATCH] fetch: Strip usernames from url's before storing them","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2009-04-15T14:01:41Z","receivedAt":"2009-04-15T14:01:41Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"Michael J Gruber wrote:\n> Andreas Ericsson venit, vidit, dixit 15.04.2009 14:16:\n>> When pulling from a remote, the full URL including username\n>> is by default added to the commit message. Since it adds\n>> very little value but could be used by malicious people to\n>> glean valid usernames (with matching hostnames), we're far\n>> better off just stripping the username before storing the\n>> remote URL locally.\n> \n> Uhm, this is for non-fast-forwards when pull uses \"merge\" and creates a\n> merge commit, right?\n> Fetch does not create commit messages, and pull does not either if it\n> rebases. So maybe the commit message could make it clearer for lesser\n> git-educated people such as myself ;)\n> \n\nYes and no. This alters what gets written to .git/FETCH_HEAD, but since\nwhat's written there only ever turns up in the history in the form of a\ncommit-message, you're essentially right.\n\nThe reason for this patch is that we published some repositories publicly\na week or two ago and one such malicious person started attacking all our\npublic servers with the usernames found in the commit messages. In our\ncase, this isn't such a big issue since all of the servers just fake an\nSSH daemon when connected to from outside our internal network, but we\nshouldn't take too lightly on casual information disclosure. It *could*\nhave been a problem and, if nothing else, this patch will probably save\nsome diskspace if it can prevent others being targeted by the same kind\nof brute-force attack as we were.\n\nJunio, this is based off of master, but applies cleanly to maint as well.\nI'd actually prefer it to go on maint than master. The usernames in the\nurl's provide no real value but are potentially dangerous to disclose.\n\nAttached is the micro-program I wrote to test the function itself.\nSince I couldn't quite figure out how to set up a remote repository with\npassword protection and then fetch from it in a way that was generic\nenough to go into the test-suite I didn't bother with that, but issuing\na git-pull shows that FETCH_HEAD and the commit message gets the correct\ntext.\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n\nConsidering the successes of the wars on alcohol, poverty, drugs and\nterror, I think we should give some serious thought to declaring war\non peace.\n\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define prefixcmp(haystack, needle) strncmp(haystack, needle, strlen(needle))\n#define xcalloc(n, size) calloc(n, size)\n#define xstrdup(str) strdup(str)\n\nstatic char *anonymize_url(const char *url)\n{\n\tchar *anon_url;\n\tconst char *at_sign = strchr(url, '@');\n\tsize_t len, prefix_len = 0;\n\n\tif (!at_sign)\n\t\treturn xstrdup(url);\n\n\tif (!prefixcmp(url, \"ssh://\"))\n\t\tprefix_len = strlen(\"ssh://\");\n\telse if (!prefixcmp(url, \"http://\"))\n\t\tprefix_len = strlen(\"http://\");\n\telse if (!prefixcmp(url, \"https://\"))\n\t\tprefix_len = strlen(\"https://\");\n\telse if (!strchr(at_sign + 1, ':'))\n\t\treturn xstrdup(url);\n\n\tlen = prefix_len + strlen(at_sign + 1);\n\tanon_url = xcalloc(1, 1 + prefix_len + strlen(at_sign + 1));\n\tif (prefix_len)\n\t\tmemcpy(anon_url, url, prefix_len);\n\tmemcpy(anon_url + prefix_len, at_sign + 1, strlen(at_sign + 1));\n\n\treturn anon_url;\n}\n\nint main(int argc, char **argv)\n{\n\tint errors = 0;\n\tstruct {\n\t\tchar *raw;\n\t\tchar *correct;\n\t}\n\turls[] = {\n\t\t{ \"rsync://host.xz/path/to/repo.git/\", NULL, },\n\t\t{ \"http://host.xz:port/path/to/repo.git/\", NULL, },\n\t\t{ \"https://host.xz:port/path/to/repo.git/\", NULL,},\n\t\t{ \"git://host.xz:port/path/to/repo.git/\", NULL, },\n\t\t{ \"git://host.xz:port/~user/path/to/repo.git/\", NULL, },\n\t\t{\n\t\t\t\"http://user@host.xz:port/path/to/repo.git/\",\n\t\t\t\t\"http://host.xz:port/path/to/repo.git/\",\n\t\t},\n\t\t{\n\t\t\t\"https://user@host.xz:port/path/to/repo.git/\",\n\t\t\t\t\"https://host.xz:port/path/to/repo.git/\",\n\t\t},\n\t\t{\n\t\t\t\"ssh://user@host.xz:port/path/to/repo.git/\",\n\t\t\t\t\"ssh://host.xz:port/path/to/repo.git/\",\n\t\t},\n\t\t{\n\t\t\t\"ssh://user@host.xz/path/to/repo.git/\",\n\t\t\t\t\"ssh://host.xz/path/to/repo.git/\",\n\t\t},\n\t\t{\n\t\t\t\"ssh://user@host.xz/~user/path/to/repo.git/\",\n\t\t\t\t\"ssh://host.xz/~user/path/to/repo.git/\",\n\t\t},\n\t\t{\n\t\t\t\"user@host.xz:/path/to/repo.git/\",\n\t\t\t\t\"host.xz:/path/to/repo.git/\",\n\t\t},\n\t\t{\n\t\t\t\"user@host.xz:~user/path/to/repo.git/\",\n\t\t\t\t\"host.xz:~user/path/to/repo.git/\",\n\t\t},\n\t\t{ NULL, NULL },\n\t};\n\tint i;\n\n\tfor (i = 0; urls[i].raw; i++) {\n\t\tchar *anon_url = anonymize_url(urls[i].raw);\n\t\tif (!strcmp(anon_url, urls[i].correct ? urls[i].correct : urls[i].raw))\n\t\t\tcontinue;\n\n\t\terrors++;\n\t\tprintf(\"raw    : %s\\nanon   : %s\\n\", urls[i].raw, anon_url);\n\t\tprintf(\"correct: %s\\n\", urls[i].correct);\n\t}\n\n\tprintf(\"There were %d errors\\n\", errors);\n\treturn 0;\n}\n"},{"id":"111360","messageId":"49E5EBD2.1070704@op5.se","threadId":"18880","inReplyTo":"49E5DE98.1080600@viscovery.net","subject":"Re: [PATCH] fetch: Strip usernames from url's before storing them","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2009-04-15T14:14:42Z","receivedAt":"2009-04-15T14:14:42Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"Johannes Sixt wrote:\n> Andreas Ericsson schrieb:\n>> +/*\n>> + * strip username information from the url\n>> + * This will allocate a new string, or return its argument\n>> + * if no stripping is necessary.\n>> + *\n>> + * The url's we want to catch are the following:\n>> + *   ssh://[user@]host.xz[:port]/path/to/repo.git/\n>> + *   [user@]host.xz:/path/to/repo.git/\n>> + *   http[s]://[user[:password]@]host.xz/path/to/repo.git\n>> + *\n>> + * Although git doesn't currently support giving the password\n>> + * to http url's on the command-line, it's easier to catch\n>> + * that case too than it is to cater for it specially.\n>> + */\n>> +static char *anonymize_url(const char *url)\n>> +{\n>> +\tchar *anon_url;\n>> +\tconst char *at_sign = strchr(url, '@');\n>> +\tsize_t prefix_len = 0;\n>> +\n>> +\tif (!at_sign)\n> \n> \tif (!at_sign || has_dos_drive_prefix(url))\n> \n> or even better move this function to transport.c and use is_local().\n\nGood idea, even though it really shouldn't matter in practice due\nto the last if() below. Then again, there's no telling what some\nsilly IDE might take into its head to name paths ;-)\n\nI also see now I botched the job and sent the un-amended patch.\nv2 incoming.\n\n> \n>> +\t\treturn strdup(url);\n>> +\n>> +\tif (!prefixcmp(url, \"ssh://\"))\n>> +\t\tprefix_len = strlen(\"ssh://\");\n>> +\telse if (!prefixcmp(url, \"http://\"))\n>> +\t\tprefix_len = strlen(\"http://\");\n>> +\telse if (!prefixcmp(url, \"https://\"))\n>> +\t\tprefix_len = strlen(\"https://\");\n>> +\telse if (!strchr(at_sign + 1, ':'))\n>> +\t\treturn strdup(url);\n>> +\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n\nConsidering the successes of the wars on alcohol, poverty, drugs and\nterror, I think we should give some serious thought to declaring war\non peace.\n"},{"id":"111361","messageId":"1239805814-21340-1-git-send-email-ae@op5.se","threadId":"18880","inReplyTo":"49E5EBD2.1070704@op5.se","subject":"[PATCH v2] fetch: Strip usernames from url's before storing them","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2009-04-15T14:30:14Z","receivedAt":"2009-04-15T14:30:14Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"When pulling from a remote, the full URL including username\nis by default added to the commit message. Since it adds\nvery little value but could be used by malicious people to\nglean valid usernames (with matching hostnames), we're far\nbetter off just stripping the username before storing the\nremote URL locally.\n\nNote that this patch has no lasting visible effect when\n\"git pull\" does not create a merge commit. It simply\nalters what gets written to .git/FETCH_HEAD, which is used\nby \"git merge\" to automagically create its' messages.\n\nSigned-off-by: Andreas Ericsson <ae@op5.se>\n---\n\nThis incorporates the changes suggested by both J6t and\nMichael Gruber, as well as the properly functioning\nversion of the patch (the last one had an off-by-lots\nfor some url's, as I failed at --amend'ing it).\n\n builtin-fetch.c |    7 +++++--\n transport.c     |   40 ++++++++++++++++++++++++++++++++++++++++\n transport.h     |    1 +\n 3 files changed, 46 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin-fetch.c b/builtin-fetch.c\nindex 3c998ea..0bb290b 100644\n--- a/builtin-fetch.c\n+++ b/builtin-fetch.c\n@@ -289,7 +289,7 @@ static int update_local_ref(struct ref *ref,\n \t}\n }\n \n-static int store_updated_refs(const char *url, const char *remote_name,\n+static int store_updated_refs(const char *raw_url, const char *remote_name,\n \t\tstruct ref *ref_map)\n {\n \tFILE *fp;\n@@ -298,11 +298,13 @@ static int store_updated_refs(const char *url, const char *remote_name,\n \tchar note[1024];\n \tconst char *what, *kind;\n \tstruct ref *rm;\n-\tchar *filename = git_path(\"FETCH_HEAD\");\n+\tchar *url, *filename = git_path(\"FETCH_HEAD\");\n \n \tfp = fopen(filename, \"a\");\n \tif (!fp)\n \t\treturn error(\"cannot open %s: %s\\n\", filename, strerror(errno));\n+\n+\turl = transport_anonymize_url(raw_url);\n \tfor (rm = ref_map; rm; rm = rm->next) {\n \t\tstruct ref *ref = NULL;\n \n@@ -376,6 +378,7 @@ static int store_updated_refs(const char *url, const char *remote_name,\n \t\t\t\tfprintf(stderr, \" %s\\n\", note);\n \t\t}\n \t}\n+\tfree(url);\n \tfclose(fp);\n \tif (rc & 2)\n \t\terror(\"some local refs could not be updated; try running\\n\"\ndiff --git a/transport.c b/transport.c\nindex 3dfb03c..9e6dc5e 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1083,3 +1083,43 @@ int transport_disconnect(struct transport *transport)\n \tfree(transport);\n \treturn ret;\n }\n+\n+/*\n+ * Strip username information from the url and return it in a\n+ * newly allocated string which the caller has to free.\n+ *\n+ * The url's we want to catch are the following:\n+ *   ssh://[user@]host.xz[:port]/path/to/repo.git/\n+ *   [user@]host.xz:/path/to/repo.git/\n+ *   http[s]://[user[:password]@]host.xz/path/to/repo.git\n+ *\n+ * Although git doesn't currently support giving the password\n+ * to http url's on the command-line, it's easier to catch\n+ * that case too than it is to cater for it specially.\n+ */\n+char *transport_anonymize_url(const char *url)\n+{\n+\tchar *anon_url;\n+\tconst char *at_sign = strchr(url, '@');\n+\tsize_t len, prefix_len = 0;\n+\n+\tif (is_local(url) || !at_sign)\n+\t\treturn xstrdup(url);\n+\n+\tif (!prefixcmp(url, \"ssh://\"))\n+\t\tprefix_len = strlen(\"ssh://\");\n+\telse if (!prefixcmp(url, \"http://\"))\n+\t\tprefix_len = strlen(\"http://\");\n+\telse if (!prefixcmp(url, \"https://\"))\n+\t\tprefix_len = strlen(\"https://\");\n+\telse if (!strchr(at_sign + 1, ':'))\n+\t\treturn xstrdup(url);\n+\n+\tlen = prefix_len + strlen(at_sign + 1);\n+\tanon_url = xcalloc(1, 1 + prefix_len + strlen(at_sign + 1));\n+\tif (prefix_len)\n+\t\tmemcpy(anon_url, url, prefix_len);\n+\tmemcpy(anon_url + prefix_len, at_sign + 1, strlen(at_sign + 1));\n+\n+\treturn anon_url;\n+}\ndiff --git a/transport.h b/transport.h\nindex b1c2252..27bfc52 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -74,5 +74,6 @@ const struct ref *transport_get_remote_refs(struct transport *transport);\n int transport_fetch_refs(struct transport *transport, const struct ref *refs);\n void transport_unlock_pack(struct transport *transport);\n int transport_disconnect(struct transport *transport);\n+char *transport_anonymize_url(const char *url);\n \n #endif\n-- \n1.6.3.rc0.2.g743353.dirty\n"},{"id":"111380","messageId":"7viql5vnqd.fsf@gitster.siamese.dyndns.org","threadId":"18880","inReplyTo":"49E5E8C5.4050501@op5.se","subject":"Re: [PATCH] fetch: Strip usernames from url's before storing them","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-04-15T17:19:22Z","receivedAt":"2009-04-15T17:19:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andreas Ericsson <ae@op5.se> writes:\n\n> The reason for this patch is that we published some repositories publicly\n> a week or two ago and one such malicious person started attacking all our\n> public servers with the usernames found in the commit messages.\n\nInteresting.  Do you also worry about the names on committer and author\nlines?\n"},{"id":"111381","messageId":"7vbpqxvnpl.fsf@gitster.siamese.dyndns.org","threadId":"18880","inReplyTo":"1239805814-21340-1-git-send-email-ae@op5.se","subject":"Re: [PATCH v2] fetch: Strip usernames from url's before storing them","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-04-15T17:19:50Z","receivedAt":"2009-04-15T17:19:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andreas Ericsson <ae@op5.se> writes:\n\n> When pulling from a remote, the full URL including username\n> is by default added to the commit message. Since it adds\n> very little value but could be used by malicious people to\n> glean valid usernames (with matching hostnames), we're far\n> better off just stripping the username before storing the\n> remote URL locally.\n\nSounds like a sensible thing to do.\n\n> +/*\n> + * Strip username information from the url and return it in a\n> + * newly allocated string which the caller has to free.\n> + *\n> + * The url's we want to catch are the following:\n> + *   ssh://[user@]host.xz[:port]/path/to/repo.git/\n> + *   [user@]host.xz:/path/to/repo.git/\n> + *   http[s]://[user[:password]@]host.xz/path/to/repo.git\n\nIf this is a valid URL:\n\n\tscheme://host.xz/path@with@at@sign.git/\n\nwe do not want to mistakenly trigger this logic.\n\nI do not know if rsync://me@there/path is supported, but we should\ngeneralize to support any scheme://me@there/path to keep the code simpler.\nYou do not do anything special based on the URL scheme other than learning\nhow long the scheme:// part is to copy it anyway.  Perhaps like...\n\nchar *transport_anonymize_url(const char *url)\n{\n\tchar *anon_url, *scheme_prefix, *anon_part;\n\tsize_t len, prefix_len = 0;\n\n\tanon_part = strchr(url, '@');\n\tif (is_local(url) || !anon_part)\n\t\tgoto literal_copy;\n\n\tanon_part++;\n\tscheme_prefix = strstr(url, \"://\");\n\tif (scheme_prefix) {\n\t\tconst char *cp;\n\t\t/* make sure scheme is reasonable */\n\t\tfor (cp = url; cp < scheme_prefix; cp++) {\n\t\t\tswitch (*cp) { /* RFC 1738 2.1 */\n\t\t\tcase '+':\n\t\t\tcase '.':\n\t\t\tcase '-':\n\t\t\t\tbreak; /* ok */\n\t\t\tdefault:\n\t\t\t\tif (isalnum(*cp))\n\t\t\t\t\tbreak;\n\t\t\t\t/* it isn't */\n\t\t\t\tgoto literal_copy;\n\t\t\t}\n\t\t}\n\t\t/* @ past the first slash does not count */\n\t\tcp = strchr(scheme_prefix + 3, '/');\n\t\tif (cp < anon_part)\n\t\t\tgoto literal_copy;\n\t\tprefix_len = scheme_prefix - url + 3;\n\t}\n\telse if (!strchr(anon_part, ':'))\n\t\t/* cannot be \"me@there:/path/name\" */\n\t\tgoto literal_copy;\n\tlen = prefix_len + strlen(anon_part);\n\tanon_url = xmalloc(len + 1);\n\tmemcpy(anon_url, url, prefix_len);\n\tmemcpy(anon_url + prefix_len, anon_part, strlen(anon_part));\n\treturn anon_url;\n literal_copy:\n\treturn xstrdup(url);\n}\n"},{"id":"111390","messageId":"49E62295.3070100@op5.se","threadId":"18880","inReplyTo":"7viql5vnqd.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] fetch: Strip usernames from url's before storing them","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2009-04-15T18:08:21Z","receivedAt":"2009-04-15T18:08:21Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"Junio C Hamano wrote:\n> Andreas Ericsson <ae@op5.se> writes:\n> \n>> The reason for this patch is that we published some repositories publicly\n>> a week or two ago and one such malicious person started attacking all our\n>> public servers with the usernames found in the commit messages.\n> \n> Interesting.  Do you also worry about the names on committer and author\n> lines?\n\nWe don't refuse anyone who's allowed to push by file-permissions. Perhaps\nwe should, but we don't. This was discovered as a nasty after-shock, and\n\"unfortunately\" a bunch of people are already working with the commits\nexposed by the code. Since we're not really affected at all by the bad\nparts of the code, we've decided not to bother rewriting history. We'd\nrather keep life simple for our contributors (we're not as lively a\ncommunity as git, so we can't afford to lose half a dozen just to protect\nourselves; It's better to just alter those usernames and keep going with\nthe history we've got).\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n\nConsidering the successes of the wars on alcohol, poverty, drugs and\nterror, I think we should give some serious thought to declaring war\non peace.\n"},{"id":"111395","messageId":"49E6475A.3090801@op5.se","threadId":"18880","inReplyTo":"7vbpqxvnpl.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH v2] fetch: Strip usernames from url's before storing them","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2009-04-15T20:45:14Z","receivedAt":"2009-04-15T20:45:14Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"Thanks for the feedback. Many appreciated.\n\nJunio C Hamano wrote:\n> Andreas Ericsson <ae@op5.se> writes:\n> \n>> +/*\n>> + * Strip username information from the url and return it in a\n>> + * newly allocated string which the caller has to free.\n>> + *\n>> + * The url's we want to catch are the following:\n>> + *   ssh://[user@]host.xz[:port]/path/to/repo.git/\n>> + *   [user@]host.xz:/path/to/repo.git/\n>> + *   http[s]://[user[:password]@]host.xz/path/to/repo.git\n> \n> If this is a valid URL:\n> \n> \tscheme://host.xz/path@with@at@sign.git/\n> \n> we do not want to mistakenly trigger this logic.\n> \n\nI'm guessing, and I'm slightly inebriated so don't yell too hard at me if\nyou think your mail will hit me in the morning ;-)\n\nIt *is* valid for \"bare ssh\" url's, but that would only trigger the fourth\n(and last) case of the if() else if() thing which would return a strdup().\nIt won't have a scheme, and it won't have a colon after the @-sign.\n\nAn url with a scheme can't contain a colon *before* the at-sign if it does\ncontain a username, and it won't contain a colon *after* the @-sign if it\n*does* contain a username. This is a guess, but I wrote that part of the\ntransport code initially, so I've got a decent inkling of what git accepts\nin the first place (although it might not be strong enough in the face of\nthe various RFC's, I know).\n\n> I do not know if rsync://me@there/path is supported, but we should\n> generalize to support any scheme://me@there/path to keep the code simpler.\n> You do not do anything special based on the URL scheme other than learning\n> how long the scheme:// part is to copy it anyway.\n\nI've never seen rsync://user@ style url's before. It's trivial to add support\nfor it if it's valid though, but for the reasons above, I'm not too fussed\nabout trying to support URL's we're extremely unlikely to see in real life.\nThe last else if() really does catch a lot.\n\n>  Perhaps like...\n> \n> char *transport_anonymize_url(const char *url)\n> {\n> \tchar *anon_url, *scheme_prefix, *anon_part;\n> \tsize_t len, prefix_len = 0;\n> \n> \tanon_part = strchr(url, '@');\n> \tif (is_local(url) || !anon_part)\n> \t\tgoto literal_copy;\n> \n> \tanon_part++;\n> \tscheme_prefix = strstr(url, \"://\");\n> \tif (scheme_prefix) {\n> \t\tconst char *cp;\n> \t\t/* make sure scheme is reasonable */\n> \t\tfor (cp = url; cp < scheme_prefix; cp++) {\n> \t\t\tswitch (*cp) { /* RFC 1738 2.1 */\n> \t\t\tcase '+':\n> \t\t\tcase '.':\n> \t\t\tcase '-':\n> \t\t\t\tbreak; /* ok */\n\nIsn't %xx also a valid escape sequence for unknown chars in url's, according\nto the aforementioned RFC?\n\n\n> \t\t\tdefault:\n> \t\t\t\tif (isalnum(*cp))\n> \t\t\t\t\tbreak;\n> \t\t\t\t/* it isn't */\n> \t\t\t\tgoto literal_copy;\n> \t\t\t}\n> \t\t}\n> \t\t/* @ past the first slash does not count */\n> \t\tcp = strchr(scheme_prefix + 3, '/');\n> \t\tif (cp < anon_part)\n> \t\t\tgoto literal_copy;\n> \t\tprefix_len = scheme_prefix - url + 3;\n> \t}\n> \telse if (!strchr(anon_part, ':'))\n> \t\t/* cannot be \"me@there:/path/name\" */\n\nNice, but the comment is misleading to the simple (or drunk) mind.\nIt can't contain an @-sign at all due to the check above, which I'd\nbe happier if it mentions (me being both atm, I'm inclined to think\nin rather straight lines).\n\nOtherwise I really like it.\n\n> \t\tgoto literal_copy;\n> \tlen = prefix_len + strlen(anon_part);\n> \tanon_url = xmalloc(len + 1);\n> \tmemcpy(anon_url, url, prefix_len);\n> \tmemcpy(anon_url + prefix_len, anon_part, strlen(anon_part));\n> \treturn anon_url;\n>  literal_copy:\n> \treturn xstrdup(url);\n> }\n\n\nThis looks sensible and fairly generic, and I'll happily defer to a\nmore capable and less drunk programmer any time of the day. I'll copy\nit into anon-url.c tomorrow and see how it pans out with some of the\nweirder URL's you gave today.\n\nIf anyone's got some tricky url's they want me to try, email me\nbefore 08:00 GMT tomorrow and I'll give 'em a whirl with multiple\nalgo's.\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n\nConsidering the successes of the wars on alcohol, poverty, drugs and\nterror, I think we should give some serious thought to declaring war\non peace.\n"},{"id":"111476","messageId":"1239956411-11195-1-git-send-email-ae@op5.se","threadId":"18880","inReplyTo":"7vbpqxvnpl.fsf@gitster.siamese.dyndns.org","subject":"[PATCH v3] fetch: Strip usernames from url's before storing them","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2009-04-17T08:20:11Z","receivedAt":"2009-04-17T08:20:11Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"When pulling from a remote, the full URL including username\nis by default added to the commit message. Since it adds\nvery little value but could be used by malicious people to\nglean valid usernames (with matching hostnames), we're far\nbetter off just stripping the username before storing the\nremote URL locally.\n\nNote that this patch has no lasting visible effect when\n\"git pull\" does not create a merge commit. It simply\nalters what gets written to .git/FETCH_HEAD, which is used\nby \"git merge\" to automagically create its messages.\n\nSigned-off-by: Andreas Ericsson <ae@op5.se>\n---\n\nI made some minor modifications to your function, Junio.\n* use xcalloc() instead of malloc() to make sure the string\n  is nul-terminated.\n* take strlen() of anon_part instead of calculating the whole\n  thing once, as we use that measurement twice.\n* moved handling of !scheme_prefix && !is_bare_ssh_url(url)\n  up top, so both conditions can be seen at once on my fairly\n  cramped editor.\n\n builtin-fetch.c |    7 +++++--\n transport.c     |   48 ++++++++++++++++++++++++++++++++++++++++++++++++\n transport.h     |    1 +\n 3 files changed, 54 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin-fetch.c b/builtin-fetch.c\nindex 3c998ea..0bb290b 100644\n--- a/builtin-fetch.c\n+++ b/builtin-fetch.c\n@@ -289,7 +289,7 @@ static int update_local_ref(struct ref *ref,\n \t}\n }\n \n-static int store_updated_refs(const char *url, const char *remote_name,\n+static int store_updated_refs(const char *raw_url, const char *remote_name,\n \t\tstruct ref *ref_map)\n {\n \tFILE *fp;\n@@ -298,11 +298,13 @@ static int store_updated_refs(const char *url, const char *remote_name,\n \tchar note[1024];\n \tconst char *what, *kind;\n \tstruct ref *rm;\n-\tchar *filename = git_path(\"FETCH_HEAD\");\n+\tchar *url, *filename = git_path(\"FETCH_HEAD\");\n \n \tfp = fopen(filename, \"a\");\n \tif (!fp)\n \t\treturn error(\"cannot open %s: %s\\n\", filename, strerror(errno));\n+\n+\turl = transport_anonymize_url(raw_url);\n \tfor (rm = ref_map; rm; rm = rm->next) {\n \t\tstruct ref *ref = NULL;\n \n@@ -376,6 +378,7 @@ static int store_updated_refs(const char *url, const char *remote_name,\n \t\t\t\tfprintf(stderr, \" %s\\n\", note);\n \t\t}\n \t}\n+\tfree(url);\n \tfclose(fp);\n \tif (rc & 2)\n \t\terror(\"some local refs could not be updated; try running\\n\"\ndiff --git a/transport.c b/transport.c\nindex 3dfb03c..38c12e7 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1083,3 +1083,51 @@ int transport_disconnect(struct transport *transport)\n \tfree(transport);\n \treturn ret;\n }\n+\n+/*\n+ * Strip username (and password) from an url and return\n+ * it in a newly allocated string.\n+ */\n+static char *transport_anonymize_url(const char *url)\n+{\n+\tchar *anon_url, *scheme_prefix, *anon_part;\n+\tsize_t anon_len, prefix_len = 0;\n+\n+\tanon_part = strchr(url, '@');\n+\tif (is_local(url) || !anon_part)\n+\t\tgoto literal_copy;\n+\n+\tanon_len = strlen(++anon_part);\n+\tscheme_prefix = strstr(url, \"://\");\n+\tif (!scheme_prefix) {\n+\t\tif (!strchr(anon_part, ':'))\n+\t\t\t/* cannot be \"me@there:/path/name\" */\n+\t\t\tgoto literal_copy;\n+\t} else {\n+\t\tconst char *cp;\n+\t\t/* make sure scheme is reasonable */\n+\t\tfor (cp = url; cp < scheme_prefix; cp++) {\n+\t\t\tswitch (*cp) {\n+\t\t\t\t/* RFC 1738 2.1 */\n+\t\t\tcase '+': case '.': case '-':\n+\t\t\t\tbreak; /* ok */\n+\t\t\tdefault:\n+\t\t\t\tif (isalnum(*cp))\n+\t\t\t\t\tbreak;\n+\t\t\t\t/* it isn't */\n+\t\t\t\tgoto literal_copy;\n+\t\t\t}\n+\t\t}\n+\t\t/* @ past the first slash does not count */\n+\t\tcp = strchr(scheme_prefix + 3, '/');\n+\t\tif (cp && cp < anon_part)\n+\t\t\tgoto literal_copy;\n+\t\tprefix_len = scheme_prefix - url + 3;\n+\t}\n+\tanon_url = xcalloc(1, 1 + prefix_len + anon_len);\n+\tmemcpy(anon_url, url, prefix_len);\n+\tmemcpy(anon_url + prefix_len, anon_part, anon_len);\n+\treturn anon_url;\n+\tliteral_copy:\n+\treturn xstrdup(url);\n+}\ndiff --git a/transport.h b/transport.h\nindex b1c2252..27bfc52 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -74,5 +74,6 @@ const struct ref *transport_get_remote_refs(struct transport *transport);\n int transport_fetch_refs(struct transport *transport, const struct ref *refs);\n void transport_unlock_pack(struct transport *transport);\n int transport_disconnect(struct transport *transport);\n+char *transport_anonymize_url(const char *url);\n \n #endif\n-- \n1.6.3.rc0.2.g743353.dirty\n"},{"id":"111688","messageId":"49EC26BC.5070505@op5.se","threadId":"18880","inReplyTo":"1239956411-11195-1-git-send-email-ae@op5.se","subject":"Re: [PATCH v3] fetch: Strip usernames from url's before storing them","fromName":"Andreas Ericsson","fromEmail":"ae@op5.se","sentAt":"2009-04-20T07:39:40Z","receivedAt":"2009-04-20T07:39:40Z","isPatch":true,"sender":{"key":"ae@op5.se","avatar":"https://gravatar.com/avatar/426e89595c75a8f5252dd0c989e5fabe5bcac616e68557427ad9aef6b0ca342a?d=mp&s=160"},"body":"We've used this patch in production the past couple of days.\nAll tests pass and it works just fine. Any issues with it I\nshould fix, or did it just slip through?\n\nAndreas Ericsson wrote:\n> When pulling from a remote, the full URL including username\n> is by default added to the commit message. Since it adds\n> very little value but could be used by malicious people to\n> glean valid usernames (with matching hostnames), we're far\n> better off just stripping the username before storing the\n> remote URL locally.\n> \n> Note that this patch has no lasting visible effect when\n> \"git pull\" does not create a merge commit. It simply\n> alters what gets written to .git/FETCH_HEAD, which is used\n> by \"git merge\" to automagically create its messages.\n> \n> Signed-off-by: Andreas Ericsson <ae@op5.se>\n> ---\n> \n> I made some minor modifications to your function, Junio.\n> * use xcalloc() instead of malloc() to make sure the string\n>   is nul-terminated.\n> * take strlen() of anon_part instead of calculating the whole\n>   thing once, as we use that measurement twice.\n> * moved handling of !scheme_prefix && !is_bare_ssh_url(url)\n>   up top, so both conditions can be seen at once on my fairly\n>   cramped editor.\n> \n>  builtin-fetch.c |    7 +++++--\n>  transport.c     |   48 ++++++++++++++++++++++++++++++++++++++++++++++++\n>  transport.h     |    1 +\n>  3 files changed, 54 insertions(+), 2 deletions(-)\n> \n> diff --git a/builtin-fetch.c b/builtin-fetch.c\n> index 3c998ea..0bb290b 100644\n> --- a/builtin-fetch.c\n> +++ b/builtin-fetch.c\n> @@ -289,7 +289,7 @@ static int update_local_ref(struct ref *ref,\n>  \t}\n>  }\n>  \n> -static int store_updated_refs(const char *url, const char *remote_name,\n> +static int store_updated_refs(const char *raw_url, const char *remote_name,\n>  \t\tstruct ref *ref_map)\n>  {\n>  \tFILE *fp;\n> @@ -298,11 +298,13 @@ static int store_updated_refs(const char *url, const char *remote_name,\n>  \tchar note[1024];\n>  \tconst char *what, *kind;\n>  \tstruct ref *rm;\n> -\tchar *filename = git_path(\"FETCH_HEAD\");\n> +\tchar *url, *filename = git_path(\"FETCH_HEAD\");\n>  \n>  \tfp = fopen(filename, \"a\");\n>  \tif (!fp)\n>  \t\treturn error(\"cannot open %s: %s\\n\", filename, strerror(errno));\n> +\n> +\turl = transport_anonymize_url(raw_url);\n>  \tfor (rm = ref_map; rm; rm = rm->next) {\n>  \t\tstruct ref *ref = NULL;\n>  \n> @@ -376,6 +378,7 @@ static int store_updated_refs(const char *url, const char *remote_name,\n>  \t\t\t\tfprintf(stderr, \" %s\\n\", note);\n>  \t\t}\n>  \t}\n> +\tfree(url);\n>  \tfclose(fp);\n>  \tif (rc & 2)\n>  \t\terror(\"some local refs could not be updated; try running\\n\"\n> diff --git a/transport.c b/transport.c\n> index 3dfb03c..38c12e7 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -1083,3 +1083,51 @@ int transport_disconnect(struct transport *transport)\n>  \tfree(transport);\n>  \treturn ret;\n>  }\n> +\n> +/*\n> + * Strip username (and password) from an url and return\n> + * it in a newly allocated string.\n> + */\n> +static char *transport_anonymize_url(const char *url)\n> +{\n> +\tchar *anon_url, *scheme_prefix, *anon_part;\n> +\tsize_t anon_len, prefix_len = 0;\n> +\n> +\tanon_part = strchr(url, '@');\n> +\tif (is_local(url) || !anon_part)\n> +\t\tgoto literal_copy;\n> +\n> +\tanon_len = strlen(++anon_part);\n> +\tscheme_prefix = strstr(url, \"://\");\n> +\tif (!scheme_prefix) {\n> +\t\tif (!strchr(anon_part, ':'))\n> +\t\t\t/* cannot be \"me@there:/path/name\" */\n> +\t\t\tgoto literal_copy;\n> +\t} else {\n> +\t\tconst char *cp;\n> +\t\t/* make sure scheme is reasonable */\n> +\t\tfor (cp = url; cp < scheme_prefix; cp++) {\n> +\t\t\tswitch (*cp) {\n> +\t\t\t\t/* RFC 1738 2.1 */\n> +\t\t\tcase '+': case '.': case '-':\n> +\t\t\t\tbreak; /* ok */\n> +\t\t\tdefault:\n> +\t\t\t\tif (isalnum(*cp))\n> +\t\t\t\t\tbreak;\n> +\t\t\t\t/* it isn't */\n> +\t\t\t\tgoto literal_copy;\n> +\t\t\t}\n> +\t\t}\n> +\t\t/* @ past the first slash does not count */\n> +\t\tcp = strchr(scheme_prefix + 3, '/');\n> +\t\tif (cp && cp < anon_part)\n> +\t\t\tgoto literal_copy;\n> +\t\tprefix_len = scheme_prefix - url + 3;\n> +\t}\n> +\tanon_url = xcalloc(1, 1 + prefix_len + anon_len);\n> +\tmemcpy(anon_url, url, prefix_len);\n> +\tmemcpy(anon_url + prefix_len, anon_part, anon_len);\n> +\treturn anon_url;\n> +\tliteral_copy:\n> +\treturn xstrdup(url);\n> +}\n> diff --git a/transport.h b/transport.h\n> index b1c2252..27bfc52 100644\n> --- a/transport.h\n> +++ b/transport.h\n> @@ -74,5 +74,6 @@ const struct ref *transport_get_remote_refs(struct transport *transport);\n>  int transport_fetch_refs(struct transport *transport, const struct ref *refs);\n>  void transport_unlock_pack(struct transport *transport);\n>  int transport_disconnect(struct transport *transport);\n> +char *transport_anonymize_url(const char *url);\n>  \n>  #endif\n\n\n-- \nAndreas Ericsson                   andreas.ericsson@op5.se\nOP5 AB                             www.op5.se\nTel: +46 8-230225                  Fax: +46 8-230231\n\nConsidering the successes of the wars on alcohol, poverty, drugs and\nterror, I think we should give some serious thought to declaring war\non peace.\n"},{"id":"111697","messageId":"7vvdozk9gx.fsf@gitster.siamese.dyndns.org","threadId":"18880","inReplyTo":"49EC26BC.5070505@op5.se","subject":"Re: [PATCH v3] fetch: Strip usernames from url's before storing them","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-04-20T08:36:30Z","receivedAt":"2009-04-20T08:36:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andreas Ericsson <ae@op5.se> writes:\n\n> We've used this patch in production the past couple of days.\n> All tests pass and it works just fine. Any issues with it I\n> should fix, or did it just slip through?\n\nThe latter, and a bit of my slowing down on handling new feature patches\nduring the pre-release freeze.\n\nWill queue.\n"}]}