{"thread":{"id":"34508","subject":"[PATCH v7 0/4] config: add support for http.<url>.* settings","startedAt":"2013-07-22T03:18:34Z","lastAt":"2013-07-22T12:52:49Z","messageCount":11,"participants":["Kyle J. McKay","Junio C Hamano"],"isPatch":true,"patchVersion":7,"patchTotal":4},"messages":[{"id":"223898","messageId":"f1109cf05979890ea9441fae7881586@f74d39fa044aa309eaea14b9f57fe79","threadId":"34508","inReplyTo":null,"subject":"[PATCH v7 0/4] config: add support for http.<url>.* settings","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T03:18:34Z","receivedAt":"2013-07-22T03:18:34Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"NOTE: This patch requires the following preparatory change:\n\n f1ff763 http.c: fix parsing of http.sslCertPasswordProtected variable\n\nwhich is currently in pu.\n\n\nThis patch series adds support for http.<url>.* settings.  The patch is\norganized as a series of improvements on the functionality:\n\n1/4 - adds basic textual matching support\n2/4 - adds URL normalization before matching\n3/4 - adds a test for the URL normalization function\n4/4 - adds any user matching\n\n\nWith-Feedback-From-jh: Junio C Hamano <gitster@pobox.com>\n\nDifferences from v6:\n\n1/4 - Updated from v6's 1/4:\n\n* Added an http_init memset for http_option_max_matched_len (feedback-jh)\n\n2/4 - Updated from v6's 2/4:\n\n* Reduce use of strchr (feedback-jh)\n* Correctly handle characters with high bit set during normalization\n* Detect some additional invalid scheme names\n\n3/4 - Updated from v6's 3/4:\n\n* Add several additional tests for high bit set characters and invalid schemes\n\n4/4 - Updated from v6's 4/4:\n\n* Added an http_init memset for http_option_user_matched (feedback-jh)\n\n\nApplicable comments from earlier cover:\n\nTo better support matching URLs that are equivalent but spelled differently, a\nurl_normalize function has been added.  Currently this patch leaves it in\nhttp.c as http_options_url_normalize as I am unclear whether it should go into\nurl.{h,c} at this time since only http.c uses it.\n\nSince the url_normalize function's behavior is non-trivial, it is presented as\na separate patch on top of the basic http.<url>.* settings support.  A new test\nfor it has also been included as a separate patch.  I am unclear on the proper\nnumber for this test, but have gone ahead and put it with the other http tests\nsince this patch series places the url_normalize function into http.c.\n\n\nKyle J. McKay (4):\n  config: add support for http.<url>.* settings\n  config: improve support for http.<url>.* settings\n  tests: add new test for the url_normalize function\n  config: allow http.<url>.* any user matching\n\n .gitignore               |   1 +\n Documentation/config.txt |  19 ++\n Makefile                 |   5 +\n http.c                   | 666 +++++++++++++++++++++++++++++++++++++++++++++--\n t/t5200-url-normalize.sh | 182 +++++++++++++\n t/t5200/README           |   9 +\n t/t5200/url-1            |   1 +\n t/t5200/url-10           |   1 +\n t/t5200/url-11           |   1 +\n t/t5200/url-2            |   1 +\n t/t5200/url-3            |   1 +\n t/t5200/url-4            |   1 +\n t/t5200/url-5            |   1 +\n t/t5200/url-6            |   1 +\n t/t5200/url-7            |   1 +\n t/t5200/url-8            |   1 +\n t/t5200/url-9            |   1 +\n test-url-normalize.c     |  62 +++++\n 18 files changed, 938 insertions(+), 17 deletions(-)\n create mode 100755 t/t5200-url-normalize.sh\n create mode 100644 t/t5200/README\n create mode 100644 t/t5200/url-1\n create mode 100644 t/t5200/url-10\n create mode 100644 t/t5200/url-11\n create mode 100644 t/t5200/url-2\n create mode 100644 t/t5200/url-3\n create mode 100644 t/t5200/url-4\n create mode 100644 t/t5200/url-5\n create mode 100644 t/t5200/url-6\n create mode 100644 t/t5200/url-7\n create mode 100644 t/t5200/url-8\n create mode 100644 t/t5200/url-9\n create mode 100644 test-url-normalize.c\n\n-- \n1.8.3\n"},{"id":"223899","messageId":"bf17b8070be3e49a0fa8e5f2c22da98@f74d39fa044aa309eaea14b9f57fe79","threadId":"34508","inReplyTo":"f1109cf05979890ea9441fae7881586@f74d39fa044aa309eaea14b9f57fe79","subject":"[PATCH v7 1/4] config: add support for http.<url>.* settings","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T03:18:35Z","receivedAt":"2013-07-22T03:18:35Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"The credentials configuration values already support url-specific\nconfiguration items in the form credential.<url>.*.  This patch\nadds similar support for http configuration values.\n\nThe <url> value is considered a match to a url if the <url> value\nis either an exact match or a prefix of the url which ends on a\npath component boundary ('/').  So \"https://example.com/test\" will\nmatch \"https://example.com/test\" and \"https://example.com/test/too\"\nbut not \"https://example.com/testextra\".\n\nLonger matches take precedence over shorter matches with\nenvironment variable settings taking precedence over all.\n\nWith this configuration:\n\n[http]\n        useragent = other-agent\n        noEPSV = true\n[http \"https://example.com\"]\n        useragent = example-agent\n        sslVerify = false\n[http \"https://example.com/path\"]\n        useragent = path-agent\n\nThe \"https://other.example.com/\" url will have useragent\n\"other-agent\" and sslVerify will be on.\n\nThe \"https://example.com/\" url will have useragent\n\"example-agent\" and sslVerify will be off.\n\nThe \"https://example.com/path/sub\" url will have useragent\n\"path-agent\" and sslVerify will be off.\n\nAll three of the examples will have noEPSV enabled.\n\nSigned-off-by: Kyle J. McKay <mackyle@gmail.com>\n---\n Documentation/config.txt |  15 +++++\n http.c                   | 170 ++++++++++++++++++++++++++++++++++++++++++-----\n 2 files changed, 168 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 6e53fc5..41cab91 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1513,6 +1513,21 @@ http.useragent::\n \tof common USER_AGENT strings (but not including those like git/1.7.1).\n \tCan be overridden by the 'GIT_HTTP_USER_AGENT' environment variable.\n \n+http.<url>.*::\n+\tAny of the http.* options above can be applied selectively to some urls.\n+\tFor example \"http.https://example.com.useragent\" would set the user\n+\tagent only for https connections to example.com.  The <url> value\n+\tmatches a url if it is an exact match or a prefix of the url matching\n+\tat a \"/\" boundary.  Longer <url> matches take precedence over shorter\n+\tones with the environment variable settings taking precedence over all.\n+\tNote that <url> must match the url passed to git exactly (other than\n+\tpossibly being a prefix).  This means any user, password and/or port\n+\tsetting that appears in a url as well as any %XX escapes that are\n+\tpresent must also appear in <url> to have a successful match.  The urls\n+\tthat are matched against are those given directly to git commands.  In\n+\tother words, use exactly the same url that was passed to git (possibly\n+\tshortened) for the <url> value of the config setting.\n+\n i18n.commitEncoding::\n \tCharacter encoding the commit messages are stored in; Git itself\n \tdoes not care per se, but this information is necessary e.g. when\ndiff --git a/http.c b/http.c\nindex 37986f8..1531ffa 100644\n--- a/http.c\n+++ b/http.c\n@@ -30,6 +30,34 @@ static CURL *curl_default;\n \n char curl_errorstr[CURL_ERROR_SIZE];\n \n+enum http_option_type {\n+\tOPT_POST_BUFFER,\n+\tOPT_MIN_SESSIONS,\n+\tOPT_SSL_VERIFY,\n+\tOPT_SSL_TRY,\n+\tOPT_SSL_CERT,\n+\tOPT_SSL_CAINFO,\n+\tOPT_LOW_SPEED,\n+\tOPT_LOW_TIME,\n+\tOPT_NO_EPSV,\n+\tOPT_HTTP_PROXY,\n+\tOPT_COOKIE_FILE,\n+\tOPT_USER_AGENT,\n+\tOPT_PASSWD_REQ,\n+#ifdef USE_CURL_MULTI\n+\tOPT_MAX_REQUESTS,\n+#endif\n+#if LIBCURL_VERSION_NUM >= 0x070903\n+\tOPT_SSL_KEY,\n+#endif\n+#if LIBCURL_VERSION_NUM >= 0x070908\n+\tOPT_SSL_CAPATH,\n+#endif\n+\tOPT_MAX\n+};\n+\n+static size_t http_option_max_matched_len[OPT_MAX];\n+\n static int curl_ssl_verify = -1;\n static int curl_ssl_try;\n static const char *ssl_cert;\n@@ -141,33 +169,121 @@ static void process_curl_messages(void)\n }\n #endif\n \n+static size_t http_options_url_match_prefix(const char *url,\n+\t\t\t\t\t    const char *url_prefix,\n+\t\t\t\t\t    size_t url_prefix_len)\n+{\n+\t/*\n+\t * url_prefix matches url if url_prefix is an exact match for url or it\n+\t * is a prefix of url and the match ends on a path component boundary.\n+\t * Both url and url_prefix are considered to have an implicit '/' on the\n+\t * end for matching purposes if they do not already.\n+\t *\n+\t * url must be NUL terminated.  url_prefix_len is the length of\n+\t * url_prefix which need not be NUL terminated.\n+\t *\n+\t * The return value is the length of the match in characters (excluding\n+\t * any final '/') or 0 for no match.  Passing \"/\" as url_prefix will\n+\t * always cause 0 to be returned.\n+\t */\n+\tsize_t url_len;\n+\tif (url_prefix_len && url_prefix[url_prefix_len - 1] == '/')\n+\t\turl_prefix_len--;\n+\tif (!url_prefix_len || strncmp(url, url_prefix, url_prefix_len))\n+\t\treturn 0;\n+\turl_len = strlen(url);\n+\tif ((url_len == url_prefix_len) || (url[url_prefix_len] == '/'))\n+\t\treturn url_prefix_len;\n+\treturn 0;\n+}\n+\n+static int new_match_is_shorter(size_t matchlen, enum http_option_type opt)\n+{\n+\t/*\n+\t * Compare matchlen to the last matched length of option opt and\n+\t * return true if matchlen is shorter than the last matched length\n+\t * (meaning the config setting should be ignored).  Upon seeing the\n+\t * _same_ key (i.e. new key has the same match length which is therefore\n+\t * not shorter) the new setting will override the previous setting.\n+\t * Otherwise return false and record matchlen as the current last\n+\t * matched length of option opt.\n+\t */\n+\tif (matchlen < http_option_max_matched_len[opt])\n+\t\treturn 1;\n+\thttp_option_max_matched_len[opt] = matchlen;\n+\treturn 0;\n+}\n+\n static int http_options(const char *var, const char *value, void *cb)\n {\n-\tif (!strcmp(\"http.sslverify\", var)) {\n+\tconst char *url = cb;\n+\tconst char *key, *dot;\n+\tsize_t matchlen = 0;\n+\n+\tkey = skip_prefix(var, \"http.\");\n+\tif (!key)\n+\t\treturn git_default_config(var, value, cb);\n+\n+\t/*\n+\t * If the part following the leading \"http.\" contains a '.' then check\n+\t * to see if the part between \"http.\" and the last '.' is a match to\n+\t * url.  If it's not then ignore the setting.  Otherwise set key to\n+\t * point to the option which is the part after the final '.' and\n+\t * use key in subsequent comparisons to determine the option type.\n+\t */\n+\tdot = strrchr(key, '.');\n+\tif (dot) {\n+\t\tmatchlen = http_options_url_match_prefix(url, key, dot - key);\n+\t\tif (!matchlen)\n+\t\t\treturn 0;\n+\t\tkey = dot + 1;\n+\t}\n+\n+\tif (!strcmp(\"sslverify\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_SSL_VERIFY))\n+\t\t\treturn 0;\n \t\tcurl_ssl_verify = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(\"http.sslcert\", var))\n+\tif (!strcmp(\"sslcert\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_SSL_CERT))\n+\t\t\treturn 0;\n \t\treturn git_config_string(&ssl_cert, var, value);\n+\t}\n #if LIBCURL_VERSION_NUM >= 0x070903\n-\tif (!strcmp(\"http.sslkey\", var))\n+\tif (!strcmp(\"sslkey\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_SSL_KEY))\n+\t\t\treturn 0;\n \t\treturn git_config_string(&ssl_key, var, value);\n+\t}\n #endif\n #if LIBCURL_VERSION_NUM >= 0x070908\n-\tif (!strcmp(\"http.sslcapath\", var))\n+\tif (!strcmp(\"sslcapath\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_SSL_CAPATH))\n+\t\t\treturn 0;\n \t\treturn git_config_string(&ssl_capath, var, value);\n+\t}\n #endif\n-\tif (!strcmp(\"http.sslcainfo\", var))\n+\tif (!strcmp(\"sslcainfo\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_SSL_CAINFO))\n+\t\t\treturn 0;\n \t\treturn git_config_string(&ssl_cainfo, var, value);\n-\tif (!strcmp(\"http.sslcertpasswordprotected\", var)) {\n+\t}\n+\tif (!strcmp(\"sslcertpasswordprotected\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_PASSWD_REQ))\n+\t\t\treturn 0;\n \t\tssl_cert_password_required = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(\"http.ssltry\", var)) {\n+\tif (!strcmp(\"ssltry\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_SSL_TRY))\n+\t\t\treturn 0;\n \t\tcurl_ssl_try = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(\"http.minsessions\", var)) {\n+\tif (!strcmp(\"minsessions\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_MIN_SESSIONS))\n+\t\t\treturn 0;\n \t\tmin_curl_sessions = git_config_int(var, value);\n #ifndef USE_CURL_MULTI\n \t\tif (min_curl_sessions > 1)\n@@ -176,39 +292,58 @@ static int http_options(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n #ifdef USE_CURL_MULTI\n-\tif (!strcmp(\"http.maxrequests\", var)) {\n+\tif (!strcmp(\"maxrequests\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_MAX_REQUESTS))\n+\t\t\treturn 0;\n \t\tmax_requests = git_config_int(var, value);\n \t\treturn 0;\n \t}\n #endif\n-\tif (!strcmp(\"http.lowspeedlimit\", var)) {\n+\tif (!strcmp(\"lowspeedlimit\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_LOW_SPEED))\n+\t\t\treturn 0;\n \t\tcurl_low_speed_limit = (long)git_config_int(var, value);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(\"http.lowspeedtime\", var)) {\n+\tif (!strcmp(\"lowspeedtime\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_LOW_TIME))\n+\t\t\treturn 0;\n \t\tcurl_low_speed_time = (long)git_config_int(var, value);\n \t\treturn 0;\n \t}\n \n-\tif (!strcmp(\"http.noepsv\", var)) {\n+\tif (!strcmp(\"noepsv\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_NO_EPSV))\n+\t\t\treturn 0;\n \t\tcurl_ftp_no_epsv = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\tif (!strcmp(\"http.proxy\", var))\n+\tif (!strcmp(\"proxy\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_HTTP_PROXY))\n+\t\t\treturn 0;\n \t\treturn git_config_string(&curl_http_proxy, var, value);\n+\t}\n \n-\tif (!strcmp(\"http.cookiefile\", var))\n+\tif (!strcmp(\"cookiefile\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_COOKIE_FILE))\n+\t\t\treturn 0;\n \t\treturn git_config_string(&curl_cookie_file, var, value);\n+\t}\n \n-\tif (!strcmp(\"http.postbuffer\", var)) {\n+\tif (!strcmp(\"postbuffer\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_POST_BUFFER))\n+\t\t\treturn 0;\n \t\thttp_post_buffer = git_config_int(var, value);\n \t\tif (http_post_buffer < LARGE_PACKET_MAX)\n \t\t\thttp_post_buffer = LARGE_PACKET_MAX;\n \t\treturn 0;\n \t}\n \n-\tif (!strcmp(\"http.useragent\", var))\n+\tif (!strcmp(\"useragent\", key)) {\n+\t\tif (new_match_is_shorter(matchlen, OPT_USER_AGENT))\n+\t\t\treturn 0;\n \t\treturn git_config_string(&user_agent, var, value);\n+\t}\n \n \t/* Fall back on the default ones */\n \treturn git_default_config(var, value, cb);\n@@ -337,7 +472,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)\n \n \thttp_is_verbose = 0;\n \n-\tgit_config(http_options, NULL);\n+\tmemset(http_option_max_matched_len, 0, sizeof(http_option_max_matched_len));\n+\tgit_config(http_options, (void *)url);\n \n \tcurl_global_init(CURL_GLOBAL_ALL);\n \n-- \n1.8.3\n"},{"id":"223902","messageId":"242e892174353ddcea572934ce891e1@f74d39fa044aa309eaea14b9f57fe79","threadId":"34508","inReplyTo":"f1109cf05979890ea9441fae7881586@f74d39fa044aa309eaea14b9f57fe79","subject":"[PATCH v7 2/4] config: improve support for http.<url>.* settings","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T03:18:36Z","receivedAt":"2013-07-22T03:18:36Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"Improve on the http.<url>.* url matching behavior by first\nnormalizing the urls before they are compared.\n\nWith this change, for example, the following configuration\nsection:\n\n[http \"https://example.com/path\"]\n        useragent = example-agent\n        sslVerify = false\n\nwill properly match a \"HTTPS://example.COM/p%61th\" url which\nis equivalent.\n\nThe normalization rules are based on RFC 3986 and should\nresult in any two equivalent urls being a match.\n\nSigned-off-by: Kyle J. McKay <mackyle@gmail.com>\n---\n Documentation/config.txt |  19 ++-\n http.c                   | 311 ++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 318 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 41cab91..e461f32 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1517,16 +1517,15 @@ http.<url>.*::\n \tAny of the http.* options above can be applied selectively to some urls.\n \tFor example \"http.https://example.com.useragent\" would set the user\n \tagent only for https connections to example.com.  The <url> value\n-\tmatches a url if it is an exact match or a prefix of the url matching\n-\tat a \"/\" boundary.  Longer <url> matches take precedence over shorter\n-\tones with the environment variable settings taking precedence over all.\n-\tNote that <url> must match the url passed to git exactly (other than\n-\tpossibly being a prefix).  This means any user, password and/or port\n-\tsetting that appears in a url as well as any %XX escapes that are\n-\tpresent must also appear in <url> to have a successful match.  The urls\n-\tthat are matched against are those given directly to git commands.  In\n-\tother words, use exactly the same url that was passed to git (possibly\n-\tshortened) for the <url> value of the config setting.\n+\tmatches a url if it is an exact match or if it is a prefix of the url\n+\tmatching at a \"/\" boundary.  Longer <url> matches take precedence over\n+\tshorter ones with the environment variable settings taking precedence\n+\tover all.  The urls are normalized before testing for a match.  Note,\n+\thowever, that any user, password and/or port setting that appears in a\n+\turl must also match that part of <url> to have a successful match.  The\n+\turls that are matched against are those given directly to git commands.\n+\tThis means any urls visited as a result of a redirection do not\n+\tparticipate in matching.\n \n i18n.commitEncoding::\n \tCharacter encoding the commit messages are stored in; Git itself\ndiff --git a/http.c b/http.c\nindex 1531ffa..29e119c 100644\n--- a/http.c\n+++ b/http.c\n@@ -169,6 +169,300 @@ static void process_curl_messages(void)\n }\n #endif\n \n+#define URL_ALPHA \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"\n+#define URL_DIGIT \"0123456789\"\n+#define URL_ALPHADIGIT URL_ALPHA URL_DIGIT\n+#define URL_SCHEME_CHARS URL_ALPHADIGIT \"+.-\"\n+#define URL_HOST_CHARS URL_ALPHADIGIT \".-[:]\" /* IPv6 literals need [:] */\n+#define URL_UNSAFE_CHARS \" <>\\\"%{}|\\\\^`\" /* plus 0x00-0x1F,0x7F-0xFF */\n+#define URL_GEN_RESERVED \":/?#[]@\"\n+#define URL_SUB_RESERVED \"!$&'()*+,;=\"\n+#define URL_RESERVED URL_GEN_RESERVED URL_SUB_RESERVED /* only allowed delims */\n+\n+static int append_normalized_escapes(struct strbuf *buf,\n+\t\t\t\t     const char *from,\n+\t\t\t\t     size_t from_len,\n+\t\t\t\t     const char *esc_extra,\n+\t\t\t\t     const char *esc_ok)\n+{\n+\t/*\n+\t * Append to strbuf 'buf' characters from string 'from' with length\n+\t * 'from_len' while unescaping characters that do not need to be escaped\n+\t * and escaping characters that do.  The set of characters to escape\n+\t * (the complement of which is unescaped) starts out as the RFC 3986\n+\t * unsafe characters (0x00-0x1F,0x7F-0xFF,\" <>\\\"#%{}|\\\\^`\").  If\n+\t * 'esc_extra' is not NULL, those additional characters will also always\n+\t * be escaped.  If 'esc_ok' is not NULL, those characters will be left\n+\t * escaped if found that way, but will not be unescaped otherwise (used\n+\t * for delimiters).  If a %-escape sequence is encountered that is not\n+\t * followed by 2 hexadecimal digits, the sequence is invalid and\n+\t * false (0) will be returned.  Otherwise true (1) will be returned for\n+\t * success.\n+\t *\n+\t * Note that all %-escape sequences will be normalized to UPPERCASE\n+\t * as indicated in RFC 3986.  Unless included in esc_extra or esc_ok\n+\t * alphanumerics and \"-._~\" will always be unescaped as per RFC 3986.\n+\t */\n+\n+\twhile (from_len) {\n+\t\tint ch = *from++;\n+\t\tint was_esc = 0;\n+\n+\t\tfrom_len--;\n+\t\tif (ch == '%') {\n+\t\t\tif (from_len < 2 ||\n+\t\t\t    !isxdigit((unsigned char)from[0]) ||\n+\t\t\t    !isxdigit((unsigned char)from[1]))\n+\t\t\t\treturn 0;\n+\t\t\tch = hexval_table[(unsigned char)*from++] << 4;\n+\t\t\tch |= hexval_table[(unsigned char)*from++];\n+\t\t\tfrom_len -= 2;\n+\t\t\twas_esc = 1;\n+\t\t}\n+\t\tif ((unsigned char)ch <= 0x1F || (unsigned char)ch >= 0x7F ||\n+\t\t    strchr(URL_UNSAFE_CHARS, ch) ||\n+\t\t    (esc_extra && strchr(esc_extra, ch)) ||\n+\t\t    (was_esc && strchr(esc_ok, ch)))\n+\t\t\tstrbuf_addf(buf, \"%%%02X\", (unsigned char)ch);\n+\t\telse\n+\t\t\tstrbuf_addch(buf, ch);\n+\t}\n+\n+\treturn 1;\n+}\n+\n+static char *http_options_url_normalize(const char *url)\n+{\n+\t/*\n+\t * Normalize NUL-terminated url using the following rules:\n+\t *\n+\t * 1. Case-insensitive parts of url will be converted to lower case\n+\t * 2. %-encoded characters that do not need to be will be unencoded\n+\t * 3. Characters that are not %-encoded and must be will be encoded\n+\t * 4. All %-encodings will be converted to upper case hexadecimal\n+\t * 5. Leading 0s are removed from port numbers\n+\t * 6. If the default port for the scheme is given it will be removed\n+\t * 7. A path part (including empty) not starting with '/' has one added\n+\t * 8. Any dot segments (. or ..) in the path are resolved and removed\n+\t * 9. IPv6 host literals are allowed (but not normalized or validated)\n+\t *\n+\t * The rules are based on information in RFC 3986.\n+\t *\n+\t * Please note this function requires a full URL including a scheme\n+\t * and host part (except for file: URLs which may have an empty host).\n+\t *\n+\t * The return value is a newly allocated string that must be freed\n+\t * or NULL if the url is not valid.\n+\t *\n+\t * This is NOT a URL validation function.  Full URL validation is NOT\n+\t * performed.  Some invalid host names are passed through this function\n+\t * undetected.  However, most all other problems that make a URL invalid\n+\t * will be detected (including a missing host for non file: URLs).\n+\t */\n+\n+\tsize_t url_len = strlen(url);\n+\tstruct strbuf norm;\n+\tsize_t spanned;\n+\tconst char *slash_ptr, *at_ptr, *colon_ptr, *path_start;\n+\tint found_host = 0;\n+\n+\n+\t/*\n+\t * Copy lowercased scheme and :// suffix, %-escapes are not allowed\n+\t * First character of scheme must be URL_ALPHA\n+\t */\n+\tspanned = strspn(url, URL_SCHEME_CHARS);\n+\tif (!spanned || !isalpha(url[0]) || spanned + 3 > url_len ||\n+\t    url[spanned] != ':' || url[spanned+1] != '/' || url[spanned+2] != '/')\n+\t\treturn NULL; /* Bad scheme and/or missing \"://\" part */\n+\tstrbuf_init(&norm, url_len);\n+\tspanned += 3;\n+\turl_len -= spanned;\n+\twhile (spanned--)\n+\t\tstrbuf_addch(&norm, tolower(*url++));\n+\n+\n+\t/*\n+\t * Copy any username:password if present normalizing %-escapes\n+\t */\n+\tat_ptr = strchr(url, '@');\n+\tslash_ptr = url + strcspn(url, \"/?#\");\n+\tif (at_ptr && at_ptr < slash_ptr) {\n+\t\tif (at_ptr > url) {\n+\t\t\tif (!append_normalized_escapes(&norm, url, at_ptr - url,\n+\t\t\t\t\t\t       \"\", URL_RESERVED)) {\n+\t\t\t\tstrbuf_release(&norm);\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t}\n+\t\tstrbuf_addch(&norm, '@');\n+\t\turl_len -= (++at_ptr - url);\n+\t\turl = at_ptr;\n+\t}\n+\n+\n+\t/*\n+\t * Copy the host part excluding any port part, no %-escapes allowed\n+\t */\n+\tif (!url_len || strchr(\":/?#\", *url)) {\n+\t\t/* Missing host invalid for all URL schemes except file */\n+\t\tif (strncmp(norm.buf, \"file:\", 5)) {\n+\t\t\tstrbuf_release(&norm);\n+\t\t\treturn NULL;\n+\t\t}\n+\t} else {\n+\t\tfound_host = 1;\n+\t}\n+\tcolon_ptr = slash_ptr - 1;\n+\twhile (colon_ptr > url && *colon_ptr != ':' && *colon_ptr != ']')\n+\t\tcolon_ptr--;\n+\tif (*colon_ptr != ':') {\n+\t\tcolon_ptr = slash_ptr;\n+\t} else if (!found_host && colon_ptr < slash_ptr && colon_ptr + 1 != slash_ptr) {\n+\t\t/* file: URLs may not have a port number */\n+\t\tstrbuf_release(&norm);\n+\t\treturn NULL;\n+\t}\n+\tspanned = strspn(url, URL_HOST_CHARS);\n+\tif (spanned < colon_ptr - url) {\n+\t\t/* Host name has invalid characters */\n+\t\tstrbuf_release(&norm);\n+\t\treturn NULL;\n+\t}\n+\twhile (url < colon_ptr) {\n+\t\tstrbuf_addch(&norm, tolower(*url++));\n+\t\turl_len--;\n+\t}\n+\n+\n+\t/*\n+\t * Check the port part and copy if not the default (after removing any\n+\t * leading 0s); no %-escapes allowed\n+\t */\n+\tif (colon_ptr < slash_ptr) {\n+\t\t/* skip the ':' and leading 0s but not the last one if all 0s */\n+\t\turl++;\n+\t\turl += strspn(url, \"0\");\n+\t\tif (url == slash_ptr && url[-1] == '0')\n+\t\t\turl--;\n+\t\tif (url == slash_ptr) {\n+\t\t\t/* Skip \":\" port with no number, it's same as default */\n+\t\t} else if (slash_ptr - url == 2 &&\n+\t\t\t   !strncmp(norm.buf, \"http:\", 5) &&\n+\t\t\t   !strncmp(url, \"80\", 2)) {\n+\t\t\t/* Skip http :80 as it's the default */\n+\t\t} else if (slash_ptr - url == 3 &&\n+\t\t\t   !strncmp(norm.buf, \"https:\", 6) &&\n+\t\t\t   !strncmp(url, \"443\", 3)) {\n+\t\t\t/* Skip https :443 as it's the default */\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * Port number must be all digits with leading 0s removed\n+\t\t\t * and since all the protocols we deal with have a 16-bit\n+\t\t\t * port number it must also be in the range 1..65535\n+\t\t\t * 0 is not allowed because that means \"next available\"\n+\t\t\t * on just about every system and therefore cannot be used\n+\t\t\t */\n+\t\t\tunsigned long pnum = 0;\n+\t\t\tspanned = strspn(url, URL_DIGIT);\n+\t\t\tif (spanned < slash_ptr - url) {\n+\t\t\t\t/* port number has invalid characters */\n+\t\t\t\tstrbuf_release(&norm);\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\tif (slash_ptr - url <= 5)\n+\t\t\t\tpnum = strtoul(url, NULL, 10);\n+\t\t\tif (pnum == 0 || pnum > 65535) {\n+\t\t\t\t/* port number not in range 1..65535 */\n+\t\t\t\tstrbuf_release(&norm);\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\tstrbuf_addch(&norm, ':');\n+\t\t\tstrbuf_add(&norm, url, slash_ptr - url);\n+\t\t}\n+\t\turl_len -= slash_ptr - colon_ptr;\n+\t\turl = slash_ptr;\n+\t}\n+\n+\n+\t/*\n+\t * Now copy the path resolving any . and .. segments being careful not\n+\t * to corrupt the URL by unescaping any delimiters, but do add an\n+\t * initial '/' if it's missing and do normalize any %-escape sequences.\n+\t */\n+\tpath_start = norm.buf + norm.len;\n+\tstrbuf_addch(&norm, '/');\n+\tif (*url == '/') {\n+\t\turl++;\n+\t\turl_len--;\n+\t}\n+\tfor (;;) {\n+\t\tconst char *seg_start = norm.buf + norm.len;\n+\t\tconst char *next_slash = url + strcspn(url, \"/?#\");\n+\t\tint skip_add_slash = 0;\n+\t\t/*\n+\t\t * RFC 3689 indicates that any . or .. segments should be\n+\t\t * unescaped before being checked for.\n+\t\t */\n+\t\tif (!append_normalized_escapes(&norm, url, next_slash - url, \"\",\n+\t\t\t\t\t       URL_RESERVED)) {\n+\t\t\tstrbuf_release(&norm);\n+\t\t\treturn NULL;\n+\t\t}\n+\t\tif (!strcmp(seg_start, \".\")) {\n+\t\t\t/* ignore a . segment; be careful not to remove initial '/' */\n+\t\t\tif (seg_start == path_start + 1) {\n+\t\t\t\tstrbuf_setlen(&norm, norm.len - 1);\n+\t\t\t\tskip_add_slash = 1;\n+\t\t\t} else {\n+\t\t\t\tstrbuf_setlen(&norm, norm.len - 2);\n+\t\t\t}\n+\t\t} else if (!strcmp(seg_start, \"..\")) {\n+\t\t\t/*\n+\t\t\t * ignore a .. segment and remove the previous segment;\n+\t\t\t * be careful not to remove initial '/' from path\n+\t\t\t */\n+\t\t\tconst char *prev_slash = norm.buf + norm.len - 3;\n+\t\t\tif (prev_slash == path_start) {\n+\t\t\t\t/* invalid .. because no previous segment to remove */\n+\t\t\t\tstrbuf_release(&norm);\n+\t\t\t\treturn NULL;\n+\t\t\t}\n+\t\t\twhile (*--prev_slash != '/') {}\n+\t\t\tif (prev_slash == path_start) {\n+\t\t\t\tstrbuf_setlen(&norm, prev_slash - norm.buf + 1);\n+\t\t\t\tskip_add_slash = 1;\n+\t\t\t} else {\n+\t\t\t\tstrbuf_setlen(&norm, prev_slash - norm.buf);\n+\t\t\t}\n+\t\t}\n+\t\turl_len -= next_slash - url;\n+\t\turl = next_slash;\n+\t\t/* if the next char is not '/' done with the path */\n+\t\tif (*url != '/')\n+\t\t\tbreak;\n+\t\turl++;\n+\t\turl_len--;\n+\t\tif (!skip_add_slash)\n+\t\t\tstrbuf_addch(&norm, '/');\n+\t}\n+\n+\n+\t/*\n+\t * Now simply copy the rest, if any, only normalizing %-escapes and\n+\t * being careful not to corrupt the URL by unescaping any delimiters.\n+\t */\n+\tif (*url) {\n+\t\tif (!append_normalized_escapes(&norm, url, url_len, \"\", URL_RESERVED)) {\n+\t\t\tstrbuf_release(&norm);\n+\t\t\treturn NULL;\n+\t\t}\n+\t}\n+\n+\n+\treturn strbuf_detach(&norm, NULL);\n+}\n+\n static size_t http_options_url_match_prefix(const char *url,\n \t\t\t\t\t    const char *url_prefix,\n \t\t\t\t\t    size_t url_prefix_len)\n@@ -185,8 +479,13 @@ static size_t http_options_url_match_prefix(const char *url,\n \t * The return value is the length of the match in characters (excluding\n \t * any final '/') or 0 for no match.  Passing \"/\" as url_prefix will\n \t * always cause 0 to be returned.\n+\t *\n+\t * Passing NULL as url and/or url_prefix will always cause 0 to be\n+\t * returned without causing any faults.\n \t */\n \tsize_t url_len;\n+\tif (!url || !url_prefix)\n+\t\treturn 0;\n \tif (url_prefix_len && url_prefix[url_prefix_len - 1] == '/')\n \t\turl_prefix_len--;\n \tif (!url_prefix_len || strncmp(url, url_prefix, url_prefix_len))\n@@ -233,7 +532,13 @@ static int http_options(const char *var, const char *value, void *cb)\n \t */\n \tdot = strrchr(key, '.');\n \tif (dot) {\n-\t\tmatchlen = http_options_url_match_prefix(url, key, dot - key);\n+\t\tchar *config_url = xmemdupz(key, dot - key);\n+\t\tchar *norm_url = http_options_url_normalize(config_url);\n+\t\tfree(config_url);\n+\t\tif (!norm_url)\n+\t\t\treturn 0;\n+\t\tmatchlen = http_options_url_match_prefix(url, norm_url, strlen(norm_url));\n+\t\tfree(norm_url);\n \t\tif (!matchlen)\n \t\t\treturn 0;\n \t\tkey = dot + 1;\n@@ -469,11 +774,13 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)\n {\n \tchar *low_speed_limit;\n \tchar *low_speed_time;\n+\tchar *norm_url = http_options_url_normalize(url);\n \n \thttp_is_verbose = 0;\n \n \tmemset(http_option_max_matched_len, 0, sizeof(http_option_max_matched_len));\n-\tgit_config(http_options, (void *)url);\n+\tgit_config(http_options, norm_url);\n+\tfree(norm_url);\n \n \tcurl_global_init(CURL_GLOBAL_ALL);\n \n-- \n1.8.3\n"},{"id":"223900","messageId":"185ed81d5965642db680fa361ee07ce@f74d39fa044aa309eaea14b9f57fe79","threadId":"34508","inReplyTo":"f1109cf05979890ea9441fae7881586@f74d39fa044aa309eaea14b9f57fe79","subject":"[PATCH v7 3/4] tests: add new test for the url_normalize function","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T03:18:37Z","receivedAt":"2013-07-22T03:18:37Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"In order to perform sane URL matching for http.<url>.* options,\nhttp.c normalizes URLs before performing matches.\n\nA new test-url-normalize test program is introduced along with\na new t5200-url-normalize.sh script to run the tests.\n\nSince the url_normalize function currently lives in http.c this\ntest will be skipped if NO_CURL is defined since http.c is skipped\nin that case.\n\nSigned-off-by: Kyle J. McKay <mackyle@gmail.com>\n---\n .gitignore               |   1 +\n Makefile                 |   5 ++\n t/t5200-url-normalize.sh | 182 +++++++++++++++++++++++++++++++++++++++++++++++\n t/t5200/README           |   9 +++\n t/t5200/url-1            |   1 +\n t/t5200/url-10           |   1 +\n t/t5200/url-11           |   1 +\n t/t5200/url-2            |   1 +\n t/t5200/url-3            |   1 +\n t/t5200/url-4            |   1 +\n t/t5200/url-5            |   1 +\n t/t5200/url-6            |   1 +\n t/t5200/url-7            |   1 +\n t/t5200/url-8            |   1 +\n t/t5200/url-9            |   1 +\n test-url-normalize.c     |  61 ++++++++++++++++\n 16 files changed, 269 insertions(+)\n create mode 100755 t/t5200-url-normalize.sh\n create mode 100644 t/t5200/README\n create mode 100644 t/t5200/url-1\n create mode 100644 t/t5200/url-10\n create mode 100644 t/t5200/url-11\n create mode 100644 t/t5200/url-2\n create mode 100644 t/t5200/url-3\n create mode 100644 t/t5200/url-4\n create mode 100644 t/t5200/url-5\n create mode 100644 t/t5200/url-6\n create mode 100644 t/t5200/url-7\n create mode 100644 t/t5200/url-8\n create mode 100644 t/t5200/url-9\n create mode 100644 test-url-normalize.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 6669bf0..cd97e16 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -198,6 +198,7 @@\n /test-string-list\n /test-subprocess\n /test-svn-fe\n+/test-url-normalize\n /test-wildmatch\n /common-cmds.h\n *.tar.gz\ndiff --git a/Makefile b/Makefile\nindex 0f931a2..f83879c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -567,6 +567,7 @@ TEST_PROGRAMS_NEED_X += test-sigchain\n TEST_PROGRAMS_NEED_X += test-string-list\n TEST_PROGRAMS_NEED_X += test-subprocess\n TEST_PROGRAMS_NEED_X += test-svn-fe\n+TEST_PROGRAMS_NEED_X += test-url-normalize\n TEST_PROGRAMS_NEED_X += test-wildmatch\n \n TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))\n@@ -2235,6 +2236,10 @@ test-parse-options$X: parse-options.o parse-options-cb.o\n \n test-svn-fe$X: vcs-svn/lib.a\n \n+test-url-normalize$X: test-url-normalize.o GIT-LDFLAGS $(GITLIBS)\n+\t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n+\t\t$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)\n+\n .PRECIOUS: $(TEST_OBJS)\n \n test-%$X: test-%.o GIT-LDFLAGS $(GITLIBS)\ndiff --git a/t/t5200-url-normalize.sh b/t/t5200-url-normalize.sh\nnew file mode 100755\nindex 0000000..12ac411\n--- /dev/null\n+++ b/t/t5200-url-normalize.sh\n@@ -0,0 +1,182 @@\n+#!/bin/sh\n+\n+test_description='url normalization'\n+. ./test-lib.sh\n+\n+if test -n \"$NO_CURL\"; then\n+\tskip_all='skipping test, git built without http support'\n+\ttest_done\n+fi\n+\n+# The base name of the test url files\n+tu=\"$TEST_DIRECTORY/t5200/url\"\n+\n+# Note that only file: URLs should be allowed without a host\n+\n+test_expect_success 'url scheme' '\n+\t! test-url-normalize \"\" &&\n+\t! test-url-normalize \"_\" &&\n+\t! test-url-normalize \"scheme\" &&\n+\t! test-url-normalize \"scheme:\" &&\n+\t! test-url-normalize \"scheme:/\" &&\n+\t! test-url-normalize \"scheme://\" &&\n+\t! test-url-normalize \"file\" &&\n+\t! test-url-normalize \"file:\" &&\n+\t! test-url-normalize \"file:/\" &&\n+\ttest-url-normalize \"file://\" &&\n+\t! test-url-normalize \"://acme.co\" &&\n+\t! test-url-normalize \"x_test://acme.co\" &&\n+\t! test-url-normalize \"-test://acme.co\" &&\n+\t! test-url-normalize \"0test://acme.co\" &&\n+\t! test-url-normalize \"+test://acme.co\" &&\n+\t! test-url-normalize \".test://acme.co\" &&\n+\t! test-url-normalize \"schem%6e://\" &&\n+\ttest-url-normalize \"x-Test+v1.0://acme.co\" &&\n+\ttest \"$(test-url-normalize -p \"AbCdeF://x.Y\")\" = \"abcdef://x.y/\"\n+'\n+\n+test_expect_success 'url authority' '\n+\t! test-url-normalize \"scheme://user:pass@\" &&\n+\t! test-url-normalize \"scheme://?\" &&\n+\t! test-url-normalize \"scheme://#\" &&\n+\t! test-url-normalize \"scheme:///\" &&\n+\t! test-url-normalize \"scheme://:\" &&\n+\t! test-url-normalize \"scheme://:555\" &&\n+\ttest-url-normalize \"file://user:pass@\" &&\n+\ttest-url-normalize \"file://?\" &&\n+\ttest-url-normalize \"file://#\" &&\n+\ttest-url-normalize \"file:///\" &&\n+\ttest-url-normalize \"file://:\" &&\n+\t! test-url-normalize \"file://:555\" &&\n+\ttest-url-normalize \"scheme://user:pass@host\" &&\n+\ttest-url-normalize \"scheme://@host\" &&\n+\ttest-url-normalize \"scheme://%00@host\" &&\n+\t! test-url-normalize \"scheme://%%@host\" &&\n+\t! test-url-normalize \"scheme://host_\" &&\n+\ttest-url-normalize \"scheme://user:pass@host/\" &&\n+\ttest-url-normalize \"scheme://@host/\" &&\n+\ttest-url-normalize \"scheme://host/\" &&\n+\ttest-url-normalize \"scheme://host?x\" &&\n+\ttest-url-normalize \"scheme://host#x\" &&\n+\ttest-url-normalize \"scheme://host/@\" &&\n+\ttest-url-normalize \"scheme://host?@x\" &&\n+\ttest-url-normalize \"scheme://host#@x\" &&\n+\ttest-url-normalize \"scheme://[::1]\" &&\n+\ttest-url-normalize \"scheme://[::1]/\" &&\n+\t! test-url-normalize \"scheme://hos%41/\" &&\n+\ttest-url-normalize \"scheme://[invalid....:/\" &&\n+\ttest-url-normalize \"scheme://invalid....:]/\" &&\n+\t! test-url-normalize \"scheme://invalid....:[/\" &&\n+\t! test-url-normalize \"scheme://invalid....:[\"\n+'\n+\n+test_expect_success 'url port checks' '\n+\ttest-url-normalize \"xyz://q@some.host:\" &&\n+\ttest-url-normalize \"xyz://q@some.host:456/\" &&\n+\t! test-url-normalize \"xyz://q@some.host:0\" &&\n+\t! test-url-normalize \"xyz://q@some.host:0000000\" &&\n+\ttest-url-normalize \"xyz://q@some.host:0000001?\" &&\n+\ttest-url-normalize \"xyz://q@some.host:065535#\" &&\n+\ttest-url-normalize \"xyz://q@some.host:65535\" &&\n+\t! test-url-normalize \"xyz://q@some.host:65536\" &&\n+\t! test-url-normalize \"xyz://q@some.host:99999\" &&\n+\t! test-url-normalize \"xyz://q@some.host:100000\" &&\n+\t! test-url-normalize \"xyz://q@some.host:100001\" &&\n+\ttest-url-normalize \"http://q@some.host:80\" &&\n+\ttest-url-normalize \"https://q@some.host:443\" &&\n+\ttest-url-normalize \"http://q@some.host:80/\" &&\n+\ttest-url-normalize \"https://q@some.host:443?\" &&\n+\t! test-url-normalize \"http://q@:8008\" &&\n+\t! test-url-normalize \"http://:8080\" &&\n+\t! test-url-normalize \"http://:\" &&\n+\ttest-url-normalize \"xyz://q@some.host:456/\" &&\n+\ttest-url-normalize \"xyz://[::1]:456/\" &&\n+\ttest-url-normalize \"xyz://[::1]:/\" &&\n+\t! test-url-normalize \"xyz://[::1]:000/\" &&\n+\t! test-url-normalize \"xyz://[::1]:0%300/\" &&\n+\t! test-url-normalize \"xyz://[::1]:0x80/\" &&\n+\t! test-url-normalize \"xyz://[::1]:4294967297/\" &&\n+\t! test-url-normalize \"xyz://[::1]:030f/\"\n+'\n+\n+test_expect_success 'url port normalization' '\n+\ttest \"$(test-url-normalize -p \"http://x:800\")\" = \"http://x:800/\" &&\n+\ttest \"$(test-url-normalize -p \"http://x:0800\")\" = \"http://x:800/\" &&\n+\ttest \"$(test-url-normalize -p \"http://x:00000800\")\" = \"http://x:800/\" &&\n+\ttest \"$(test-url-normalize -p \"http://x:065535\")\" = \"http://x:65535/\" &&\n+\ttest \"$(test-url-normalize -p \"http://x:1\")\" = \"http://x:1/\" &&\n+\ttest \"$(test-url-normalize -p \"http://x:80\")\" = \"http://x/\" &&\n+\ttest \"$(test-url-normalize -p \"http://x:080\")\" = \"http://x/\" &&\n+\ttest \"$(test-url-normalize -p \"http://x:000000080\")\" = \"http://x/\" &&\n+\ttest \"$(test-url-normalize -p \"https://x:443\")\" = \"https://x/\" &&\n+\ttest \"$(test-url-normalize -p \"https://x:0443\")\" = \"https://x/\" &&\n+\ttest \"$(test-url-normalize -p \"https://x:000000443\")\" = \"https://x/\"\n+'\n+\n+test_expect_success 'url general escapes' '\n+\t! test-url-normalize \"http://x.y?%fg\" &&\n+\ttest \"$(test-url-normalize -p \"X://W/%7e%41^%3a\")\" = \"x://w/~A%5E%3A\" &&\n+\ttest \"$(test-url-normalize -p \"X://W/:/?#[]@\")\" = \"x://w/:/?#[]@\" &&\n+\ttest \"$(test-url-normalize -p \"X://W/$&()*+,;=\")\" = \"x://w/$&()*+,;=\" &&\n+\ttest \"$(test-url-normalize -p \"X://W/'\\''\")\" = \"x://w/'\\''\" &&\n+\ttest \"$(test-url-normalize -p \"X://W?'\\!'\")\" = \"x://w/?'\\!'\"\n+';#'\n+\n+test_expect_success 'url high-bit escapes' '\n+\ttest \"$(test-url-normalize -p \"$(cat \"$tu-1\")\")\" = \"x://q/%01%02%03%04%05%06%07%08%0E%0F%10%11%12\" &&\n+\ttest \"$(test-url-normalize -p \"$(cat \"$tu-2\")\")\" = \"x://q/%13%14%15%16%17%18%19%1B%1C%1D%1E%1F%7F\" &&\n+\ttest \"$(test-url-normalize -p \"$(cat \"$tu-3\")\")\" = \"x://q/%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F\" &&\n+\ttest \"$(test-url-normalize -p \"$(cat \"$tu-4\")\")\" = \"x://q/%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F\" &&\n+\ttest \"$(test-url-normalize -p \"$(cat \"$tu-5\")\")\" = \"x://q/%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF\" &&\n+\ttest \"$(test-url-normalize -p \"$(cat \"$tu-6\")\")\" = \"x://q/%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF\" &&\n+\ttest \"$(test-url-normalize -p \"$(cat \"$tu-7\")\")\" = \"x://q/%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF\" &&\n+\ttest \"$(test-url-normalize -p \"$(cat \"$tu-8\")\")\" = \"x://q/%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF\" &&\n+\ttest \"$(test-url-normalize -p \"$(cat \"$tu-9\")\")\" = \"x://q/%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF\" &&\n+\ttest \"$(test-url-normalize -p \"$(cat \"$tu-10\")\")\" = \"x://q/%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF\" &&\n+\ttest \"$(test-url-normalize -p \"$(cat \"$tu-11\")\")\" = \"x://q/%C2%80%DF%BF%E0%A0%80%EF%BF%BD%F0%90%80%80%F0%AF%BF%BD\"\n+'\n+\n+test_expect_success 'url username/password escapes' '\n+\ttest \"$(test-url-normalize -p \"x://%41%62(^):%70+d@foo\")\" = \"x://Ab(%5E):p+d@foo/\"\n+'\n+\n+test_expect_success 'url normalized lengths' '\n+\ttest \"$(test-url-normalize -l \"Http://%4d%65:%4d^%70@The.Host\")\" = 25 &&\n+\ttest \"$(test-url-normalize -l \"http://%41:%42@x.y/%61/\")\" = 17 &&\n+\ttest \"$(test-url-normalize -l \"http://@x.y/^\")\" = 15\n+'\n+\n+test_expect_success 'url . and .. segments' '\n+\ttest \"$(test-url-normalize -p \"x://y/.\")\" = \"x://y/\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/./\")\" = \"x://y/\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/a/.\")\" = \"x://y/a\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/a/./\")\" = \"x://y/a/\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/.?\")\" = \"x://y/?\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/./?\")\" = \"x://y/?\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/a/.?\")\" = \"x://y/a?\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/a/./?\")\" = \"x://y/a/?\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/a/./b/.././../c\")\" = \"x://y/c\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/a/./b/../.././c/\")\" = \"x://y/c/\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/a/./b/.././../c/././.././.\")\" = \"x://y/\" &&\n+\t! test-url-normalize \"x://y/a/./b/.././../c/././.././..\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/a/./?/././..\")\" = \"x://y/a/?/././..\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/%2e/\")\" = \"x://y/\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/%2E/\")\" = \"x://y/\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/a/%2e./\")\" = \"x://y/\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/b/.%2E/\")\" = \"x://y/\" &&\n+\ttest \"$(test-url-normalize -p \"x://y/c/%2e%2E/\")\" = \"x://y/\"\n+'\n+\n+# http://@foo specifies an empty user name but does not specify a password\n+# http://foo  specifies neither a user name nor a password\n+# So they should not be equivalent\n+test_expect_success 'url equivalents' '\n+\ttest-url-normalize \"httP://x\" \"Http://X/\" &&\n+\ttest-url-normalize \"Http://%4d%65:%4d^%70@The.Host\" \"hTTP://Me:%4D^p@the.HOST:80/\" &&\n+\t! test-url-normalize \"https://@x.y/^\" \"httpS://x.y:443/^\" &&\n+\ttest-url-normalize \"https://@x.y/^\" \"httpS://@x.y:0443/^\" &&\n+\ttest-url-normalize \"https://@x.y/^/../abc\" \"httpS://@x.y:0443/abc\" &&\n+\ttest-url-normalize \"https://@x.y/^/..\" \"httpS://@x.y:0443/\"\n+'\n+\n+test_done\ndiff --git a/t/t5200/README b/t/t5200/README\nnew file mode 100644\nindex 0000000..b908fe9\n--- /dev/null\n+++ b/t/t5200/README\n@@ -0,0 +1,9 @@\n+The data files in this directory contain URLs with characters in\n+the range 0x01-0x1f and 0x7f-0xff to test the proper normalization\n+of unprintable characters.\n+\n+A select few characters in the 0x01-0x1f range are skipped to\n+help avoid problems running the test itself.\n+\n+They are in test files in this directory rather than being\n+embedded in the test script for portability.\ndiff --git a/t/t5200/url-1 b/t/t5200/url-1\nnew file mode 100644\nindex 0000000..519019c\n--- /dev/null\n+++ b/t/t5200/url-1\n@@ -0,0 +1 @@\n+x://q/\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\u000e\u000f\u0010\u0011\u0012\ndiff --git a/t/t5200/url-10 b/t/t5200/url-10\nnew file mode 100644\nindex 0000000..b9965de\n--- /dev/null\n+++ b/t/t5200/url-10\n@@ -0,0 +1 @@\n+x://q/ðñòóôõö÷øùúûüýþÿ\ndiff --git a/t/t5200/url-11 b/t/t5200/url-11\nnew file mode 100644\nindex 0000000..f0a50f1\n--- /dev/null\n+++ b/t/t5200/url-11\n@@ -0,0 +1 @@\n+x://q/Âß¿à ï¿½ðð¯¿½\ndiff --git a/t/t5200/url-2 b/t/t5200/url-2\nnew file mode 100644\nindex 0000000..43334b0\n--- /dev/null\n+++ b/t/t5200/url-2\n@@ -0,0 +1 @@\n+x://q/\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001b\u001c\u001d\u001e\u001f\ndiff --git a/t/t5200/url-3 b/t/t5200/url-3\nnew file mode 100644\nindex 0000000..7378c7b\n--- /dev/null\n+++ b/t/t5200/url-3\n@@ -0,0 +1 @@\n+x://q/\ndiff --git a/t/t5200/url-4 b/t/t5200/url-4\nnew file mode 100644\nindex 0000000..220b198\n--- /dev/null\n+++ b/t/t5200/url-4\n@@ -0,0 +1 @@\n+x://q/\ndiff --git a/t/t5200/url-5 b/t/t5200/url-5\nnew file mode 100644\nindex 0000000..1ccd927\n--- /dev/null\n+++ b/t/t5200/url-5\n@@ -0,0 +1 @@\n+x://q/ ¡¢£¤¥¦§¨©ª«¬­®¯\ndiff --git a/t/t5200/url-6 b/t/t5200/url-6\nnew file mode 100644\nindex 0000000..e8283aa\n--- /dev/null\n+++ b/t/t5200/url-6\n@@ -0,0 +1 @@\n+x://q/°±²³´µ¶·¸¹º»¼½¾¿\ndiff --git a/t/t5200/url-7 b/t/t5200/url-7\nnew file mode 100644\nindex 0000000..fa7c10b\n--- /dev/null\n+++ b/t/t5200/url-7\n@@ -0,0 +1 @@\n+x://q/ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ\ndiff --git a/t/t5200/url-8 b/t/t5200/url-8\nnew file mode 100644\nindex 0000000..79a0ba8\n--- /dev/null\n+++ b/t/t5200/url-8\n@@ -0,0 +1 @@\n+x://q/ÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß\ndiff --git a/t/t5200/url-9 b/t/t5200/url-9\nnew file mode 100644\nindex 0000000..8b44bec\n--- /dev/null\n+++ b/t/t5200/url-9\n@@ -0,0 +1 @@\n+x://q/àáâãäåæçèéêëìíîï\ndiff --git a/test-url-normalize.c b/test-url-normalize.c\nnew file mode 100644\nindex 0000000..d68312d\n--- /dev/null\n+++ b/test-url-normalize.c\n@@ -0,0 +1,61 @@\n+#ifdef NO_CURL\n+\n+int main()\n+{\n+\treturn 125;\n+}\n+\n+#else /* !NO_CURL */\n+\n+#include \"http.c\"\n+\n+#define url_normalize(u) http_options_url_normalize(u)\n+\n+int main(int argc, char **argv)\n+{\n+\tconst char *usage = \"test-url-normalize [-p | -l] <url1> | <url1> <url2>\";\n+\tchar *url1, *url2;\n+\tint opt_p = 0, opt_l = 0;\n+\n+\t/*\n+\t * For one url, succeed if url_normalize succeeds on it, fail otherwise.\n+\t * For two urls, succeed only if url_normalize succeeds on both and\n+\t * the results compare equal with strcmp.  If -p is given (one url only)\n+\t * and url_normalize succeeds, print the result followed by \"\\n\".  If\n+\t * -l is given (one url only) and url_normalize succeeds, print the\n+\t * returned length in decimal followed by \"\\n\".\n+\t */\n+\n+\tif (argc > 1 && !strcmp(argv[1], \"-p\")) {\n+\t\topt_p = 1;\n+\t\targc--;\n+\t\targv++;\n+\t} else if (argc > 1 && !strcmp(argv[1], \"-l\")) {\n+\t\topt_l = 1;\n+\t\targc--;\n+\t\targv++;\n+\t}\n+\n+\tif (argc < 2 || argc > 3)\n+\t\tdie(usage);\n+\n+\tif (argc == 2) {\n+\t\turl1 = url_normalize(argv[1]);\n+\t\tif (!url1)\n+\t\t\treturn 1;\n+\t\tif (opt_p)\n+\t\t\tprintf(\"%s\\n\", url1);\n+\t\tif (opt_l)\n+\t\t\tprintf(\"%u\\n\", (unsigned)strlen(url1));\n+\t\treturn 0;\n+\t}\n+\n+\tif (opt_p || opt_l)\n+\t\tdie(usage);\n+\n+\turl1 = url_normalize(argv[1]);\n+\turl2 = url_normalize(argv[2]);\n+\treturn (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1;\n+}\n+\n+#endif /* !NO_CURL */\n-- \n1.8.3\n"},{"id":"223901","messageId":"b8fedff6e14fded3929e2b835aac6f9@f74d39fa044aa309eaea14b9f57fe79","threadId":"34508","inReplyTo":"f1109cf05979890ea9441fae7881586@f74d39fa044aa309eaea14b9f57fe79","subject":"[PATCH v7 4/4] config: allow http.<url>.* any user matching","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T03:18:38Z","receivedAt":"2013-07-22T03:18:38Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"Previously the <url> had to specify an exactly matching user name\nand password if those were present in the url being matched against.\n\nNow the password portion is always ignored and omitting the user\nname from <url> allows it to match against any user name.\n\nSigned-off-by: Kyle J. McKay <mackyle@gmail.com>\n---\n Documentation/config.txt |  23 ++--\n http.c                   | 281 +++++++++++++++++++++++++++++++++++++++--------\n test-url-normalize.c     |  11 +-\n 3 files changed, 255 insertions(+), 60 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e461f32..8b32a15 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1517,15 +1517,20 @@ http.<url>.*::\n \tAny of the http.* options above can be applied selectively to some urls.\n \tFor example \"http.https://example.com.useragent\" would set the user\n \tagent only for https connections to example.com.  The <url> value\n-\tmatches a url if it is an exact match or if it is a prefix of the url\n-\tmatching at a \"/\" boundary.  Longer <url> matches take precedence over\n-\tshorter ones with the environment variable settings taking precedence\n-\tover all.  The urls are normalized before testing for a match.  Note,\n-\thowever, that any user, password and/or port setting that appears in a\n-\turl must also match that part of <url> to have a successful match.  The\n-\turls that are matched against are those given directly to git commands.\n-\tThis means any urls visited as a result of a redirection do not\n-\tparticipate in matching.\n+\tmatches a url if it refers to the same scheme, host and port and the\n+\tpath portion is an exact match or a prefix that matches at a \"/\"\n+\tboundary.  If <url> does not include a user name, it will match a url\n+\twith any username otherwise the user name must match as well (the\n+\tpassword part, if present in the url, is always ignored).  Longer <url>\n+\tpath matches take precedence over shorter matches no matter what order\n+\tthey occur in.  For same length matches, the last one wins except that a\n+\tsame-length <url> match that includes a user name will be preferred over\n+\ta same-length <url> match that does not.  The urls are normalized before\n+\tmatching so that equivalent urls that are simply spelled differently\n+\twill match properly.  Environment variable settings always override any\n+\tmatches.  The urls that are matched against are those given directly to\n+\tgit commands.  This means any urls visited as a result of a redirection\n+\tdo not participate in matching.\n \n i18n.commitEncoding::\n \tCharacter encoding the commit messages are stored in; Git itself\ndiff --git a/http.c b/http.c\nindex 29e119c..c636d3c 100644\n--- a/http.c\n+++ b/http.c\n@@ -56,7 +56,35 @@ enum http_option_type {\n \tOPT_MAX\n };\n \n+struct url_info {\n+\tchar *url;\t\t/* normalized url on success, must be freed, otherwise NULL */\n+\tconst char *err;\t/* if !url, a brief reason for the failure, otherwise NULL */\n+\n+\t/* the rest of the fields are only set if url != NULL */\n+\n+\tsize_t url_len;\t\t/* total length of url (which is now normalized) */\n+\tsize_t scheme_len;\t/* length of scheme name (excluding final :) */\n+\tsize_t user_off;\t/* offset into url to start of user name (0 => none) */\n+\tsize_t user_len;\t/* length of user name; if user_off != 0 but\n+\t\t\t\t   user_len == 0, an empty user name was given */\n+\tsize_t passwd_off;\t/* offset into url to start of passwd (0 => none) */\n+\tsize_t passwd_len;\t/* length of passwd; if passwd_off != 0 but\n+\t\t\t\t   passwd_len == 0, an empty passwd was given */\n+\tsize_t host_off;\t/* offset into url to start of host name (0 => none) */\n+\tsize_t host_len;\t/* length of host name; this INCLUDES any ':portnum';\n+\t\t\t\t * file urls may have host_len == 0 */\n+\tsize_t port_len;\t/* if a portnum is present (port_len != 0), it has\n+\t\t\t\t * this length (excluding the leading ':') at the\n+\t\t\t\t * end of the host name (always 0 for file urls) */\n+\tsize_t path_off;\t/* offset into url to the start of the url path;\n+\t\t\t\t * this will always point to a '/' character\n+\t\t\t\t * after the url has been normalized */\n+\tsize_t path_len;\t/* length of path portion excluding any trailing\n+\t\t\t\t * '?...' and '#...' portion; will always be >= 1 */\n+};\n+\n static size_t http_option_max_matched_len[OPT_MAX];\n+static int http_option_user_matched[OPT_MAX];\n \n static int curl_ssl_verify = -1;\n static int curl_ssl_try;\n@@ -231,7 +259,7 @@ static int append_normalized_escapes(struct strbuf *buf,\n \treturn 1;\n }\n \n-static char *http_options_url_normalize(const char *url)\n+static char *http_options_url_normalize(const char *url, struct url_info *out_info)\n {\n \t/*\n \t * Normalize NUL-terminated url using the following rules:\n@@ -254,6 +282,14 @@ static char *http_options_url_normalize(const char *url)\n \t * The return value is a newly allocated string that must be freed\n \t * or NULL if the url is not valid.\n \t *\n+\t * If out_info is non-NULL, the url and err fields therein will always\n+\t * be set.  If a non-NULL value is returned, it will be stored in\n+\t * out_info->url as well, out_info->err will be set to NULL and the\n+\t * other fields of *out_info will also be filled in.  If a NULL value\n+\t * is returned, NULL will be stored in out_info->url and out_info->err\n+\t * will be set to a brief, translated, error message, but no other\n+\t * fields will be filled in.\n+\t *\n \t * This is NOT a URL validation function.  Full URL validation is NOT\n \t * performed.  Some invalid host names are passed through this function\n \t * undetected.  However, most all other problems that make a URL invalid\n@@ -263,9 +299,10 @@ static char *http_options_url_normalize(const char *url)\n \tsize_t url_len = strlen(url);\n \tstruct strbuf norm;\n \tsize_t spanned;\n+\tsize_t scheme_len, user_off=0, user_len=0, passwd_off=0, passwd_len=0;\n+\tsize_t host_off=0, host_len=0, port_len=0, path_off, path_len, result_len;\n \tconst char *slash_ptr, *at_ptr, *colon_ptr, *path_start;\n-\tint found_host = 0;\n-\n+\tchar *result;\n \n \t/*\n \t * Copy lowercased scheme and :// suffix, %-escapes are not allowed\n@@ -273,9 +310,15 @@ static char *http_options_url_normalize(const char *url)\n \t */\n \tspanned = strspn(url, URL_SCHEME_CHARS);\n \tif (!spanned || !isalpha(url[0]) || spanned + 3 > url_len ||\n-\t    url[spanned] != ':' || url[spanned+1] != '/' || url[spanned+2] != '/')\n+\t    url[spanned] != ':' || url[spanned+1] != '/' || url[spanned+2] != '/') {\n+\t\tif (out_info) {\n+\t\t\tout_info->url = NULL;\n+\t\t\tout_info->err = _(\"invalid URL scheme name or missing '://' suffix\");\n+\t\t}\n \t\treturn NULL; /* Bad scheme and/or missing \"://\" part */\n+\t}\n \tstrbuf_init(&norm, url_len);\n+\tscheme_len = spanned;\n \tspanned += 3;\n \turl_len -= spanned;\n \twhile (spanned--)\n@@ -288,12 +331,25 @@ static char *http_options_url_normalize(const char *url)\n \tat_ptr = strchr(url, '@');\n \tslash_ptr = url + strcspn(url, \"/?#\");\n \tif (at_ptr && at_ptr < slash_ptr) {\n+\t\tuser_off = norm.len;\n \t\tif (at_ptr > url) {\n \t\t\tif (!append_normalized_escapes(&norm, url, at_ptr - url,\n \t\t\t\t\t\t       \"\", URL_RESERVED)) {\n+\t\t\t\tif (out_info) {\n+\t\t\t\t\tout_info->url = NULL;\n+\t\t\t\t\tout_info->err = _(\"invalid %XX escape sequence\");\n+\t\t\t\t}\n \t\t\t\tstrbuf_release(&norm);\n \t\t\t\treturn NULL;\n \t\t\t}\n+\t\t\tcolon_ptr = strchr(norm.buf + scheme_len + 3, ':');\n+\t\t\tif (colon_ptr) {\n+\t\t\t\tpasswd_off = (colon_ptr + 1) - norm.buf;\n+\t\t\t\tpasswd_len = norm.len - passwd_off;\n+\t\t\t\tuser_len = (passwd_off - 1) - (scheme_len + 3);\n+\t\t\t} else {\n+\t\t\t\tuser_len = norm.len - (scheme_len + 3);\n+\t\t\t}\n \t\t}\n \t\tstrbuf_addch(&norm, '@');\n \t\turl_len -= (++at_ptr - url);\n@@ -307,25 +363,37 @@ static char *http_options_url_normalize(const char *url)\n \tif (!url_len || strchr(\":/?#\", *url)) {\n \t\t/* Missing host invalid for all URL schemes except file */\n \t\tif (strncmp(norm.buf, \"file:\", 5)) {\n+\t\t\tif (out_info) {\n+\t\t\t\tout_info->url = NULL;\n+\t\t\t\tout_info->err = _(\"missing host and scheme is not 'file:'\");\n+\t\t\t}\n \t\t\tstrbuf_release(&norm);\n \t\t\treturn NULL;\n \t\t}\n \t} else {\n-\t\tfound_host = 1;\n+\t\thost_off = norm.len;\n \t}\n \tcolon_ptr = slash_ptr - 1;\n \twhile (colon_ptr > url && *colon_ptr != ':' && *colon_ptr != ']')\n \t\tcolon_ptr--;\n \tif (*colon_ptr != ':') {\n \t\tcolon_ptr = slash_ptr;\n-\t} else if (!found_host && colon_ptr < slash_ptr && colon_ptr + 1 != slash_ptr) {\n+\t} else if (!host_off && colon_ptr < slash_ptr && colon_ptr + 1 != slash_ptr) {\n \t\t/* file: URLs may not have a port number */\n+\t\tif (out_info) {\n+\t\t\tout_info->url = NULL;\n+\t\t\tout_info->err = _(\"a 'file:' URL may not have a port number\");\n+\t\t}\n \t\tstrbuf_release(&norm);\n \t\treturn NULL;\n \t}\n \tspanned = strspn(url, URL_HOST_CHARS);\n \tif (spanned < colon_ptr - url) {\n \t\t/* Host name has invalid characters */\n+\t\tif (out_info) {\n+\t\t\tout_info->url = NULL;\n+\t\t\tout_info->err = _(\"invalid characters in host name\");\n+\t\t}\n \t\tstrbuf_release(&norm);\n \t\treturn NULL;\n \t}\n@@ -367,6 +435,10 @@ static char *http_options_url_normalize(const char *url)\n \t\t\tspanned = strspn(url, URL_DIGIT);\n \t\t\tif (spanned < slash_ptr - url) {\n \t\t\t\t/* port number has invalid characters */\n+\t\t\t\tif (out_info) {\n+\t\t\t\t\tout_info->url = NULL;\n+\t\t\t\t\tout_info->err = _(\"invalid port number\");\n+\t\t\t\t}\n \t\t\t\tstrbuf_release(&norm);\n \t\t\t\treturn NULL;\n \t\t\t}\n@@ -374,15 +446,22 @@ static char *http_options_url_normalize(const char *url)\n \t\t\t\tpnum = strtoul(url, NULL, 10);\n \t\t\tif (pnum == 0 || pnum > 65535) {\n \t\t\t\t/* port number not in range 1..65535 */\n+\t\t\t\tif (out_info) {\n+\t\t\t\t\tout_info->url = NULL;\n+\t\t\t\t\tout_info->err = _(\"invalid port number\");\n+\t\t\t\t}\n \t\t\t\tstrbuf_release(&norm);\n \t\t\t\treturn NULL;\n \t\t\t}\n \t\t\tstrbuf_addch(&norm, ':');\n \t\t\tstrbuf_add(&norm, url, slash_ptr - url);\n+\t\t\tport_len = slash_ptr - url;\n \t\t}\n \t\turl_len -= slash_ptr - colon_ptr;\n \t\turl = slash_ptr;\n \t}\n+\tif (host_off)\n+\t\thost_len = norm.len - host_off;\n \n \n \t/*\n@@ -390,7 +469,8 @@ static char *http_options_url_normalize(const char *url)\n \t * to corrupt the URL by unescaping any delimiters, but do add an\n \t * initial '/' if it's missing and do normalize any %-escape sequences.\n \t */\n-\tpath_start = norm.buf + norm.len;\n+\tpath_off = norm.len;\n+\tpath_start = norm.buf + path_off;\n \tstrbuf_addch(&norm, '/');\n \tif (*url == '/') {\n \t\turl++;\n@@ -406,6 +486,10 @@ static char *http_options_url_normalize(const char *url)\n \t\t */\n \t\tif (!append_normalized_escapes(&norm, url, next_slash - url, \"\",\n \t\t\t\t\t       URL_RESERVED)) {\n+\t\t\tif (out_info) {\n+\t\t\t\tout_info->url = NULL;\n+\t\t\t\tout_info->err = _(\"invalid %XX escape sequence\");\n+\t\t\t}\n \t\t\tstrbuf_release(&norm);\n \t\t\treturn NULL;\n \t\t}\n@@ -425,6 +509,10 @@ static char *http_options_url_normalize(const char *url)\n \t\t\tconst char *prev_slash = norm.buf + norm.len - 3;\n \t\t\tif (prev_slash == path_start) {\n \t\t\t\t/* invalid .. because no previous segment to remove */\n+\t\t\t\tif (out_info) {\n+\t\t\t\t\tout_info->url = NULL;\n+\t\t\t\t\tout_info->err = _(\"invalid '..' path segment\");\n+\t\t\t\t}\n \t\t\t\tstrbuf_release(&norm);\n \t\t\t\treturn NULL;\n \t\t\t}\n@@ -446,6 +534,7 @@ static char *http_options_url_normalize(const char *url)\n \t\tif (!skip_add_slash)\n \t\t\tstrbuf_addch(&norm, '/');\n \t}\n+\tpath_len = norm.len - path_off;\n \n \n \t/*\n@@ -454,13 +543,33 @@ static char *http_options_url_normalize(const char *url)\n \t */\n \tif (*url) {\n \t\tif (!append_normalized_escapes(&norm, url, url_len, \"\", URL_RESERVED)) {\n+\t\t\tif (out_info) {\n+\t\t\t\tout_info->url = NULL;\n+\t\t\t\tout_info->err = _(\"invalid %XX escape sequence\");\n+\t\t\t}\n \t\t\tstrbuf_release(&norm);\n \t\t\treturn NULL;\n \t\t}\n \t}\n \n \n-\treturn strbuf_detach(&norm, NULL);\n+\tresult = strbuf_detach(&norm, &result_len);\n+\tif (out_info) {\n+\t\tout_info->url = result;\n+\t\tout_info->err = NULL;\n+\t\tout_info->url_len = result_len;\n+\t\tout_info->scheme_len = scheme_len;\n+\t\tout_info->user_off = user_off;\n+\t\tout_info->user_len = user_len;\n+\t\tout_info->passwd_off = passwd_off;\n+\t\tout_info->passwd_len = passwd_len;\n+\t\tout_info->host_off = host_off;\n+\t\tout_info->host_len = host_len;\n+\t\tout_info->port_len = port_len;\n+\t\tout_info->path_off = path_off;\n+\t\tout_info->path_len = path_len;\n+\t}\n+\treturn result;\n }\n \n static size_t http_options_url_match_prefix(const char *url,\n@@ -476,48 +585,120 @@ static size_t http_options_url_match_prefix(const char *url,\n \t * url must be NUL terminated.  url_prefix_len is the length of\n \t * url_prefix which need not be NUL terminated.\n \t *\n-\t * The return value is the length of the match in characters (excluding\n-\t * any final '/') or 0 for no match.  Passing \"/\" as url_prefix will\n-\t * always cause 0 to be returned.\n+\t * The return value is the length of the match in characters (including\n+\t * the final '/' even if it's implicit) or 0 for no match.\n \t *\n \t * Passing NULL as url and/or url_prefix will always cause 0 to be\n \t * returned without causing any faults.\n \t */\n-\tsize_t url_len;\n \tif (!url || !url_prefix)\n \t\treturn 0;\n-\tif (url_prefix_len && url_prefix[url_prefix_len - 1] == '/')\n+\tif (!url_prefix_len || (url_prefix_len == 1 && *url_prefix == '/'))\n+\t\treturn (!*url || *url == '/') ? 1 : 0;\n+\tif (url_prefix[url_prefix_len - 1] == '/')\n \t\turl_prefix_len--;\n-\tif (!url_prefix_len || strncmp(url, url_prefix, url_prefix_len))\n+\tif (strncmp(url, url_prefix, url_prefix_len))\n \t\treturn 0;\n-\turl_len = strlen(url);\n-\tif ((url_len == url_prefix_len) || (url[url_prefix_len] == '/'))\n-\t\treturn url_prefix_len;\n+\tif ((strlen(url) == url_prefix_len) || (url[url_prefix_len] == '/'))\n+\t\treturn url_prefix_len + 1;\n \treturn 0;\n }\n \n-static int new_match_is_shorter(size_t matchlen, enum http_option_type opt)\n+static int http_options_match_urls(const struct url_info *url,\n+\t\t\t\t   const struct url_info *url_prefix,\n+\t\t\t\t   int *exactusermatch)\n+{\n+\t/*\n+\t * url_prefix matches url if the scheme, host and port of url_prefix\n+\t * are the same as those of url and the path portion of url_prefix\n+\t * is the same as the path portion of url or it is a prefix that\n+\t * matches at a '/' boundary.  If url_prefix contains a user name,\n+\t * that must also exactly match the user name in url.\n+\t *\n+\t * If the user, host, port and path match in this fashion, the returned\n+\t * value is the length of the path match including any implicit\n+\t * final '/'.  For example, \"http://me@example.com/path\" is matched by\n+\t * \"http://example.com\" with a path length of 1.\n+\t *\n+\t * If there is a match and exactusermatch is not NULL, then\n+\t * *exactusermatch will be set to true if both url and url_prefix\n+\t * contained a user name or false if url_prefix did not have a\n+\t * user name.  If there is no match *exactusermatch is left untouched.\n+\t */\n+\tint usermatched = 0;\n+\tint pathmatchlen;\n+\n+\tif (!url || !url_prefix || !url->url || !url_prefix->url)\n+\t\treturn 0;\n+\n+\t/* check the scheme */\n+\tif (url_prefix->scheme_len != url->scheme_len ||\n+\t    strncmp(url->url, url_prefix->url, url->scheme_len))\n+\t\treturn 0; /* schemes do not match */\n+\n+\t/* check the user name if url_prefix has one */\n+\tif (url_prefix->user_off) {\n+\t\tif (!url->user_off || url->user_len != url_prefix->user_len ||\n+\t\t    strncmp(url->url + url->user_off,\n+\t\t\t    url_prefix->url + url_prefix->user_off,\n+\t\t\t    url->user_len))\n+\t\t\treturn 0; /* url_prefix has a user but it's not a match */\n+\t\tusermatched = 1;\n+\t}\n+\n+\t/* check the host and port */\n+\tif (url_prefix->host_len != url->host_len ||\n+\t    strncmp(url->url + url->host_off,\n+\t\t    url_prefix->url + url_prefix->host_off, url->host_len))\n+\t\treturn 0; /* host names and/or ports do not match */\n+\n+\t/* check the path */\n+\tpathmatchlen = http_options_url_match_prefix(\n+\t\turl->url + url->path_off,\n+\t\turl_prefix->url + url_prefix->path_off,\n+\t\turl_prefix->url_len - url_prefix->path_off);\n+\n+\tif (pathmatchlen && exactusermatch)\n+\t\t*exactusermatch = usermatched;\n+\treturn pathmatchlen;\n+}\n+\n+static int match_is_ignored(size_t matchlen, int usermatch, enum http_option_type opt)\n {\n \t/*\n-\t * Compare matchlen to the last matched length of option opt and\n+\t * Compare matchlen to the last matched path length of option opt and\n \t * return true if matchlen is shorter than the last matched length\n \t * (meaning the config setting should be ignored).  Upon seeing the\n \t * _same_ key (i.e. new key has the same match length which is therefore\n-\t * not shorter) the new setting will override the previous setting.\n-\t * Otherwise return false and record matchlen as the current last\n-\t * matched length of option opt.\n+\t * not shorter) the new setting will override the previous setting\n+\t * unless the new setting did not match the user and the previous match\n+\t * did.  Otherwise return false and record matchlen as the current last\n+\t * matched path length of option opt and usermatch as the last user\n+\t * matching state for option opt.\n \t */\n \tif (matchlen < http_option_max_matched_len[opt])\n \t\treturn 1;\n-\thttp_option_max_matched_len[opt] = matchlen;\n+\tif (matchlen > http_option_max_matched_len[opt]) {\n+\t\thttp_option_max_matched_len[opt] = matchlen;\n+\t\thttp_option_user_matched[opt] = usermatch;\n+\t\treturn 0;\n+\t}\n+\t/*\n+\t * If a previous match of the same length explicitly matched the user\n+\t * name, but the current match matched on any user, ignore it.\n+\t */\n+\tif (!usermatch && http_option_user_matched[opt])\n+\t\treturn 1;\n+\thttp_option_user_matched[opt] = usermatch;\n \treturn 0;\n }\n \n static int http_options(const char *var, const char *value, void *cb)\n {\n-\tconst char *url = cb;\n+\tconst struct url_info *info = cb;\n \tconst char *key, *dot;\n \tsize_t matchlen = 0;\n+\tint usermatch = 0;\n \n \tkey = skip_prefix(var, \"http.\");\n \tif (!key)\n@@ -532,12 +713,18 @@ static int http_options(const char *var, const char *value, void *cb)\n \t */\n \tdot = strrchr(key, '.');\n \tif (dot) {\n-\t\tchar *config_url = xmemdupz(key, dot - key);\n-\t\tchar *norm_url = http_options_url_normalize(config_url);\n+\t\tchar *config_url;\n+\t\tstruct url_info norm_info;\n+\t\tchar *norm_url;\n+\n+\t\tif (!info || !info->url)\n+\t\t\treturn 0;\n+\t\tconfig_url = xmemdupz(key, dot - key);\n+\t\tnorm_url = http_options_url_normalize(config_url, &norm_info);\n \t\tfree(config_url);\n \t\tif (!norm_url)\n \t\t\treturn 0;\n-\t\tmatchlen = http_options_url_match_prefix(url, norm_url, strlen(norm_url));\n+\t\tmatchlen = http_options_match_urls(info, &norm_info, &usermatch);\n \t\tfree(norm_url);\n \t\tif (!matchlen)\n \t\t\treturn 0;\n@@ -545,49 +732,49 @@ static int http_options(const char *var, const char *value, void *cb)\n \t}\n \n \tif (!strcmp(\"sslverify\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_SSL_VERIFY))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_SSL_VERIFY))\n \t\t\treturn 0;\n \t\tcurl_ssl_verify = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"sslcert\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_SSL_CERT))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_SSL_CERT))\n \t\t\treturn 0;\n \t\treturn git_config_string(&ssl_cert, var, value);\n \t}\n #if LIBCURL_VERSION_NUM >= 0x070903\n \tif (!strcmp(\"sslkey\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_SSL_KEY))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_SSL_KEY))\n \t\t\treturn 0;\n \t\treturn git_config_string(&ssl_key, var, value);\n \t}\n #endif\n #if LIBCURL_VERSION_NUM >= 0x070908\n \tif (!strcmp(\"sslcapath\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_SSL_CAPATH))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_SSL_CAPATH))\n \t\t\treturn 0;\n \t\treturn git_config_string(&ssl_capath, var, value);\n \t}\n #endif\n \tif (!strcmp(\"sslcainfo\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_SSL_CAINFO))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_SSL_CAINFO))\n \t\t\treturn 0;\n \t\treturn git_config_string(&ssl_cainfo, var, value);\n \t}\n \tif (!strcmp(\"sslcertpasswordprotected\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_PASSWD_REQ))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_PASSWD_REQ))\n \t\t\treturn 0;\n \t\tssl_cert_password_required = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"ssltry\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_SSL_TRY))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_SSL_TRY))\n \t\t\treturn 0;\n \t\tcurl_ssl_try = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"minsessions\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_MIN_SESSIONS))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_MIN_SESSIONS))\n \t\t\treturn 0;\n \t\tmin_curl_sessions = git_config_int(var, value);\n #ifndef USE_CURL_MULTI\n@@ -598,45 +785,45 @@ static int http_options(const char *var, const char *value, void *cb)\n \t}\n #ifdef USE_CURL_MULTI\n \tif (!strcmp(\"maxrequests\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_MAX_REQUESTS))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_MAX_REQUESTS))\n \t\t\treturn 0;\n \t\tmax_requests = git_config_int(var, value);\n \t\treturn 0;\n \t}\n #endif\n \tif (!strcmp(\"lowspeedlimit\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_LOW_SPEED))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_LOW_SPEED))\n \t\t\treturn 0;\n \t\tcurl_low_speed_limit = (long)git_config_int(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"lowspeedtime\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_LOW_TIME))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_LOW_TIME))\n \t\t\treturn 0;\n \t\tcurl_low_speed_time = (long)git_config_int(var, value);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(\"noepsv\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_NO_EPSV))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_NO_EPSV))\n \t\t\treturn 0;\n \t\tcurl_ftp_no_epsv = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"proxy\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_HTTP_PROXY))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_HTTP_PROXY))\n \t\t\treturn 0;\n \t\treturn git_config_string(&curl_http_proxy, var, value);\n \t}\n \n \tif (!strcmp(\"cookiefile\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_COOKIE_FILE))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_COOKIE_FILE))\n \t\t\treturn 0;\n \t\treturn git_config_string(&curl_cookie_file, var, value);\n \t}\n \n \tif (!strcmp(\"postbuffer\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_POST_BUFFER))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_POST_BUFFER))\n \t\t\treturn 0;\n \t\thttp_post_buffer = git_config_int(var, value);\n \t\tif (http_post_buffer < LARGE_PACKET_MAX)\n@@ -645,7 +832,7 @@ static int http_options(const char *var, const char *value, void *cb)\n \t}\n \n \tif (!strcmp(\"useragent\", key)) {\n-\t\tif (new_match_is_shorter(matchlen, OPT_USER_AGENT))\n+\t\tif (match_is_ignored(matchlen, usermatch, OPT_USER_AGENT))\n \t\t\treturn 0;\n \t\treturn git_config_string(&user_agent, var, value);\n \t}\n@@ -774,13 +961,15 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)\n {\n \tchar *low_speed_limit;\n \tchar *low_speed_time;\n-\tchar *norm_url = http_options_url_normalize(url);\n+\tstruct url_info info;\n \n \thttp_is_verbose = 0;\n \n \tmemset(http_option_max_matched_len, 0, sizeof(http_option_max_matched_len));\n-\tgit_config(http_options, norm_url);\n-\tfree(norm_url);\n+\tmemset(http_option_user_matched, 0, sizeof(http_option_user_matched));\n+\thttp_options_url_normalize(url, &info);\n+\tgit_config(http_options, &info);\n+\tfree(info.url);\n \n \tcurl_global_init(CURL_GLOBAL_ALL);\n \ndiff --git a/test-url-normalize.c b/test-url-normalize.c\nindex d68312d..f325571 100644\n--- a/test-url-normalize.c\n+++ b/test-url-normalize.c\n@@ -9,7 +9,7 @@ int main()\n \n #include \"http.c\"\n \n-#define url_normalize(u) http_options_url_normalize(u)\n+#define url_normalize(u,i) http_options_url_normalize(u,i)\n \n int main(int argc, char **argv)\n {\n@@ -40,21 +40,22 @@ int main(int argc, char **argv)\n \t\tdie(usage);\n \n \tif (argc == 2) {\n-\t\turl1 = url_normalize(argv[1]);\n+\t\tstruct url_info info;\n+\t\turl1 = url_normalize(argv[1], &info);\n \t\tif (!url1)\n \t\t\treturn 1;\n \t\tif (opt_p)\n \t\t\tprintf(\"%s\\n\", url1);\n \t\tif (opt_l)\n-\t\t\tprintf(\"%u\\n\", (unsigned)strlen(url1));\n+\t\t\tprintf(\"%u\\n\", (unsigned)info.url_len);\n \t\treturn 0;\n \t}\n \n \tif (opt_p || opt_l)\n \t\tdie(usage);\n \n-\turl1 = url_normalize(argv[1]);\n-\turl2 = url_normalize(argv[2]);\n+\turl1 = url_normalize(argv[1], NULL);\n+\turl2 = url_normalize(argv[2], NULL);\n \treturn (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1;\n }\n \n-- \n1.8.3\n"},{"id":"223905","messageId":"7veharqip6.fsf@alter.siamese.dyndns.org","threadId":"34508","inReplyTo":"185ed81d5965642db680fa361ee07ce@f74d39fa044aa309eaea14b9f57fe79","subject":"Re: [PATCH v7 3/4] tests: add new test for the url_normalize function","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-22T05:15:01Z","receivedAt":"2013-07-22T05:15:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Kyle J. McKay\" <mackyle@gmail.com> writes:\n\n> +test_expect_success 'url general escapes' '\n> +\t! test-url-normalize \"http://x.y?%fg\" &&\n> +\ttest \"$(test-url-normalize -p \"X://W/%7e%41^%3a\")\" = \"x://w/~A%5E%3A\" &&\n> +\ttest \"$(test-url-normalize -p \"X://W/:/?#[]@\")\" = \"x://w/:/?#[]@\" &&\n> +\ttest \"$(test-url-normalize -p \"X://W/$&()*+,;=\")\" = \"x://w/$&()*+,;=\" &&\n> +\ttest \"$(test-url-normalize -p \"X://W/'\\''\")\" = \"x://w/'\\''\" &&\n> +\ttest \"$(test-url-normalize -p \"X://W?'\\!'\")\" = \"x://w/?'\\!'\"\n> +';#'\n\nHmm,... what is the magic on the last line about?\n"},{"id":"223906","messageId":"7va9lfqi3b.fsf@alter.siamese.dyndns.org","threadId":"34508","inReplyTo":"b8fedff6e14fded3929e2b835aac6f9@f74d39fa044aa309eaea14b9f57fe79","subject":"Re: [PATCH v7 4/4] config: allow http.<url>.* any user matching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-22T05:28:08Z","receivedAt":"2013-07-22T05:28:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Kyle J. McKay\" <mackyle@gmail.com> writes:\n\n> diff --git a/test-url-normalize.c b/test-url-normalize.c\n> index d68312d..f325571 100644\n> --- a/test-url-normalize.c\n> +++ b/test-url-normalize.c\n> @@ -9,7 +9,7 @@ int main()\n>  \n>  #include \"http.c\"\n>  \n> -#define url_normalize(u) http_options_url_normalize(u)\n> +#define url_normalize(u,i) http_options_url_normalize(u,i)\n>  \n>  int main(int argc, char **argv)\n>  {\n> @@ -40,21 +40,22 @@ int main(int argc, char **argv)\n>  \t\tdie(usage);\n>  \n>  \tif (argc == 2) {\n> -\t\turl1 = url_normalize(argv[1]);\n> +\t\tstruct url_info info;\n> +\t\turl1 = url_normalize(argv[1], &info);\n>  \t\tif (!url1)\n>  \t\t\treturn 1;\n>  \t\tif (opt_p)\n>  \t\t\tprintf(\"%s\\n\", url1);\n>  \t\tif (opt_l)\n> -\t\t\tprintf(\"%u\\n\", (unsigned)strlen(url1));\n> +\t\t\tprintf(\"%u\\n\", (unsigned)info.url_len);\n>  \t\treturn 0;\n>  \t}\n>  \n>  \tif (opt_p || opt_l)\n>  \t\tdie(usage);\n>  \n> -\turl1 = url_normalize(argv[1]);\n> -\turl2 = url_normalize(argv[2]);\n> +\turl1 = url_normalize(argv[1], NULL);\n> +\turl2 = url_normalize(argv[2], NULL);\n>  \treturn (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1;\n>  }\n\nIt looks like that this program could also drive http_options()\ndirectly, or alternatively make a call\n\n\tgit_config(http_options, &info);\n\nin its main() to let the config parser do its work, to make sure\nthat the machinery picks up the right values from the desired entry\nin the configuration file, with a bit more effort.\n\n> +static int match_is_ignored(size_t matchlen, int usermatch, enum http_option_type opt)\n>  {\n>  \t/*\n> -\t * Compare matchlen to the last matched length of option opt and\n> +\t * Compare matchlen to the last matched path length of option opt and\n>  \t * return true if matchlen is shorter than the last matched length\n>  \t * (meaning the config setting should be ignored).  Upon seeing the\n>  \t * _same_ key (i.e. new key has the same match length which is therefore\n> -\t * not shorter) the new setting will override the previous setting.\n> -\t * Otherwise return false and record matchlen as the current last\n> -\t * matched length of option opt.\n> +\t * not shorter) the new setting will override the previous setting\n> +\t * unless the new setting did not match the user and the previous match\n> +\t * did.  Otherwise return false and record matchlen as the current last\n> +\t * matched path length of option opt and usermatch as the last user\n> +\t * matching state for option opt.\n>  \t */\n>  \tif (matchlen < http_option_max_matched_len[opt])\n>  \t\treturn 1;\n> -\thttp_option_max_matched_len[opt] = matchlen;\n> +\tif (matchlen > http_option_max_matched_len[opt]) {\n> +\t\thttp_option_max_matched_len[opt] = matchlen;\n> +\t\thttp_option_user_matched[opt] = usermatch;\n> +\t\treturn 0;\n> +\t}\n> +\t/*\n> +\t * If a previous match of the same length explicitly matched the user\n> +\t * name, but the current match matched on any user, ignore it.\n> +\t */\n> +\tif (!usermatch && http_option_user_matched[opt])\n> +\t\treturn 1;\n> +\thttp_option_user_matched[opt] = usermatch;\n>  \treturn 0;\n>  }\n\nOK, so if there is a configuration for a generic URL that is longer,\nit defeats a shorter URL with a specific username, e.g. when talking\nto host.xz/name as kyle, with configuration for these two keys\nexist:\n\n\tscheme://kyle@host.xz/path\n        scheme://host.xz/path/name\n\nthe value for the latter one is used.\n\nI am not complaining; just making sure that is what we want to give\nusers, as it was not quite clear in the Documentation/config.txt\npart of the patch.\n\nThanks.\n"},{"id":"223907","messageId":"7v61w3qei5.fsf@alter.siamese.dyndns.org","threadId":"34508","inReplyTo":"185ed81d5965642db680fa361ee07ce@f74d39fa044aa309eaea14b9f57fe79","subject":"Re: [PATCH v7 3/4] tests: add new test for the url_normalize function","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-22T06:45:38Z","receivedAt":"2013-07-22T06:45:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"I suspect that files under t/t5200/ were not suiable for e-mail\ntransmission.  Can you regenerate this after running:\n\n    $ echo '/t/t5200/* binary' >>.git/info/attributes\n\nor better yet with:\n\ndiff --git a/t/.gitattributes b/t/.gitattributes\nindex 1b97c54..6240ed2 100644\n--- a/t/.gitattributes\n+++ b/t/.gitattributes\n@@ -1 +1,2 @@\n t[0-9][0-9][0-9][0-9]/* -whitespace\n+t5200/* binary\n"},{"id":"223915","messageId":"278AD60F-D7A7-4ED2-853C-306E8612920E@gmail.com","threadId":"34508","inReplyTo":"7veharqip6.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v7 3/4] tests: add new test for the url_normalize function","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T10:21:54Z","receivedAt":"2013-07-22T10:21:54Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"On Jul 21, 2013, at 22:15, Junio C Hamano wrote:\n> \"Kyle J. McKay\" <mackyle@gmail.com> writes:\n>\n>> +test_expect_success 'url general escapes' '\n>> +\t! test-url-normalize \"http://x.y?%fg\" &&\n>> +\ttest \"$(test-url-normalize -p \"X://W/%7e%41^%3a\")\" = \"x://w/~A%5E \n>> %3A\" &&\n>> +\ttest \"$(test-url-normalize -p \"X://W/:/?#[]@\")\" = \"x://w/:/?#[]@\"  \n>> &&\n>> +\ttest \"$(test-url-normalize -p \"X://W/$&()*+,;=\")\" = \"x://w/$&()* \n>> +,;=\" &&\n>> +\ttest \"$(test-url-normalize -p \"X://W/'\\''\")\" = \"x://w/'\\''\" &&\n>> +\ttest \"$(test-url-normalize -p \"X://W?'\\!'\")\" = \"x://w/?'\\!'\"\n>> +';#'\n>\n> Hmm,... what is the magic on the last line about?\n\nA left-over concession for my stupid editor. ;)  I'll remove it from  \nthe new version.\n"},{"id":"223916","messageId":"35487727-EA84-414D-A7C3-D3C546CF1BEE@gmail.com","threadId":"34508","inReplyTo":"7v61w3qei5.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v7 3/4] tests: add new test for the url_normalize function","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T10:29:44Z","receivedAt":"2013-07-22T10:29:44Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"I was able to apply the patch I got back.  It was marked as  \ncharset=ISO-8859-1 so it should be fine.\n\nI wasn't sure how best to handle those though, so I will send a new  \nversion with the suggested .gitattributes patch included as well.\n\nOn Jul 21, 2013, at 23:45, Junio C Hamano wrote:\n> I suspect that files under t/t5200/ were not suiable for e-mail\n> transmission.  Can you regenerate this after running:\n>\n>    $ echo '/t/t5200/* binary' >>.git/info/attributes\n>\n> or better yet with:\n>\n> diff --git a/t/.gitattributes b/t/.gitattributes\n> index 1b97c54..6240ed2 100644\n> --- a/t/.gitattributes\n> +++ b/t/.gitattributes\n> @@ -1 +1,2 @@\n> t[0-9][0-9][0-9][0-9]/* -whitespace\n> +t5200/* binary\n"},{"id":"223921","messageId":"A7388818-53EA-4D77-9342-B15D74495811@gmail.com","threadId":"34508","inReplyTo":"7va9lfqi3b.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v7 4/4] config: allow http.<url>.* any user matching","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T12:52:49Z","receivedAt":"2013-07-22T12:52:49Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"On Jul 21, 2013, at 22:28, Junio C Hamano wrote:\n> \"Kyle J. McKay\" <mackyle@gmail.com> writes:\n>\n>> diff --git a/test-url-normalize.c b/test-url-normalize.c\n>> index d68312d..f325571 100644\n>> --- a/test-url-normalize.c\n>> +++ b/test-url-normalize.c\n>> @@ -9,7 +9,7 @@ int main()\n>\n> It looks like that this program could also drive http_options()\n> directly, or alternatively make a call\n>\n> \tgit_config(http_options, &info);\n>\n> in its main() to let the config parser do its work, to make sure\n> that the machinery picks up the right values from the desired entry\n> in the configuration file, with a bit more effort.\n\nOK.\n\n> OK, so if there is a configuration for a generic URL that is longer,\n> it defeats a shorter URL with a specific username, e.g. when talking\n> to host.xz/name as kyle, with configuration for these two keys\n> exist:\n>\n> \tscheme://kyle@host.xz/path\n>        scheme://host.xz/path/name\n>\n> the value for the latter one is used.\n>\n> I am not complaining; just making sure that is what we want to give\n> users, as it was not quite clear in the Documentation/config.txt\n> part of the patch.\n\nWill add example to Documentation/config.txt to make it clearer.\n"}]}