{"thread":{"id":"34511","subject":"[PATCH v8 0/4] config: add support for http.<url>.* settings","startedAt":"2013-07-22T12:56:40Z","lastAt":"2013-07-24T19:03:56Z","messageCount":19,"participants":["Kyle J. McKay","Junio C Hamano","Jeff King"],"isPatch":true,"patchVersion":8,"patchTotal":4},"messages":[{"id":"223922","messageId":"3c7fc982841069ce79faf227e007815@f74d39fa044aa309eaea14b9f57fe79","threadId":"34511","inReplyTo":null,"subject":"[PATCH v8 0/4] config: add support for http.<url>.* settings","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T12:56:40Z","receivedAt":"2013-07-22T12:56:40Z","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 v7:\n\n1/4 - No changes since v7's 1/4\n\n2/4 - No changes since v7's 2/4\n\n3/4 - Updated from v7's 3/4:\n\n* Add a binary attribute for the url-* files (feedback-jh)\n* Make test-url-normalize.c able to run http_options (feedback-jh)\n* Add additional tests and corresponding config files (as t/t5200/config-*)\n* Remove extraneous comment from t5200-url-normalize.sh (feedback-jh)\n\n4/4 - Updated from v7's 4/4:\n\n* Update http.<url>.* documentation with another example (feedback-jh)\n* Add another url normalization config test to match the new example\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 |  25 ++\n Makefile                 |   5 +\n http.c                   | 666 +++++++++++++++++++++++++++++++++++++++++++++--\n t/.gitattributes         |   1 +\n t/t5200-url-normalize.sh | 199 ++++++++++++++\n t/t5200/README           |  18 ++\n t/t5200/config-1         |   8 +\n t/t5200/config-2         |   3 +\n t/t5200/config-3         |   4 +\n t/t5200/url-1            | Bin 0 -> 20 bytes\n t/t5200/url-10           | Bin 0 -> 23 bytes\n t/t5200/url-11           | Bin 0 -> 25 bytes\n t/t5200/url-2            | Bin 0 -> 20 bytes\n t/t5200/url-3            | Bin 0 -> 23 bytes\n t/t5200/url-4            | Bin 0 -> 23 bytes\n t/t5200/url-5            | Bin 0 -> 23 bytes\n t/t5200/url-6            | Bin 0 -> 23 bytes\n t/t5200/url-7            | Bin 0 -> 23 bytes\n t/t5200/url-8            | Bin 0 -> 23 bytes\n t/t5200/url-9            | Bin 0 -> 23 bytes\n test-url-normalize.c     | 132 ++++++++++\n 22 files changed, 1045 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/config-1\n create mode 100644 t/t5200/config-2\n create mode 100644 t/t5200/config-3\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":"223925","messageId":"30e562871d9632141d612876db3147e@f74d39fa044aa309eaea14b9f57fe79","threadId":"34511","inReplyTo":"3c7fc982841069ce79faf227e007815@f74d39fa044aa309eaea14b9f57fe79","subject":"[PATCH v8 1/4] config: add support for http.<url>.* settings","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T12:56:41Z","receivedAt":"2013-07-22T12:56:41Z","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":"223923","messageId":"5c7eeb6b52a5468aeca40cf2e6ae579@f74d39fa044aa309eaea14b9f57fe79","threadId":"34511","inReplyTo":"3c7fc982841069ce79faf227e007815@f74d39fa044aa309eaea14b9f57fe79","subject":"[PATCH v8 2/4] config: improve support for http.<url>.* settings","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T12:56:42Z","receivedAt":"2013-07-22T12:56:42Z","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":"223924","messageId":"60d85be89d27515d913ae15e10c332f@f74d39fa044aa309eaea14b9f57fe79","threadId":"34511","inReplyTo":"3c7fc982841069ce79faf227e007815@f74d39fa044aa309eaea14b9f57fe79","subject":"[PATCH v8 3/4] tests: add new test for the url_normalize function","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T12:56:43Z","receivedAt":"2013-07-22T12:56:43Z","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/.gitattributes         |   1 +\n t/t5200-url-normalize.sh | 198 +++++++++++++++++++++++++++++++++++++++++++++++\n t/t5200/README           |  18 +++++\n t/t5200/config-1         |   8 ++\n t/t5200/config-2         |   3 +\n t/t5200/url-1            | Bin 0 -> 20 bytes\n t/t5200/url-10           | Bin 0 -> 23 bytes\n t/t5200/url-11           | Bin 0 -> 25 bytes\n t/t5200/url-2            | Bin 0 -> 20 bytes\n t/t5200/url-3            | Bin 0 -> 23 bytes\n t/t5200/url-4            | Bin 0 -> 23 bytes\n t/t5200/url-5            | Bin 0 -> 23 bytes\n t/t5200/url-6            | Bin 0 -> 23 bytes\n t/t5200/url-7            | Bin 0 -> 23 bytes\n t/t5200/url-8            | Bin 0 -> 23 bytes\n t/t5200/url-9            | Bin 0 -> 23 bytes\n test-url-normalize.c     | 131 +++++++++++++++++++++++++++++++\n 19 files changed, 365 insertions(+)\n create mode 100755 t/t5200-url-normalize.sh\n create mode 100644 t/t5200/README\n create mode 100644 t/t5200/config-1\n create mode 100644 t/t5200/config-2\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/.gitattributes b/t/.gitattributes\nindex 1b97c54..f6f1df3 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/url-* binary\ndiff --git a/t/t5200-url-normalize.sh b/t/t5200-url-normalize.sh\nnew file mode 100755\nindex 0000000..d2dd886\n--- /dev/null\n+++ b/t/t5200-url-normalize.sh\n@@ -0,0 +1,198 @@\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+# The base name of the test config files\n+tc=\"$TEST_DIRECTORY/t5200/config\"\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_expect_success 'url config normalization matching' '\n+\ttest \"$(test-url-normalize -c \"$tc-1\" \"useragent\" \"https://other.example.com/\")\" = \"other-agent\" &&\n+\ttest \"$(test-url-normalize -c \"$tc-1\" \"useragent\" \"https://example.com/\")\" = \"example-agent\" &&\n+\ttest \"$(test-url-normalize -c \"$tc-1\" \"sslVerify\" \"https://example.com/\")\" = \"false\" &&\n+\ttest \"$(test-url-normalize -c \"$tc-1\" \"useragent\" \"https://example.com/path/sub\")\" = \"path-agent\" &&\n+\ttest \"$(test-url-normalize -c \"$tc-1\" \"sslVerify\" \"https://example.com/path/sub\")\" = \"false\" &&\n+\ttest \"$(test-url-normalize -c \"$tc-1\" \"noEPSV\" \"https://elsewhere.com/\")\" = \"true\" &&\n+\ttest \"$(test-url-normalize -c \"$tc-1\" \"noEPSV\" \"https://example.com\")\" = \"true\" &&\n+\ttest \"$(test-url-normalize -c \"$tc-1\" \"noEPSV\" \"https://example.com/path\")\" = \"true\" &&\n+\ttest \"$(test-url-normalize -c \"$tc-2\" \"useragent\" \"HTTPS://example.COM/p%61th\")\" = \"example-agent\" &&\n+\ttest \"$(test-url-normalize -c \"$tc-2\" \"sslVerify\" \"HTTPS://example.COM/p%61th\")\" = \"false\"\n+'\n+\n+test_done\ndiff --git a/t/t5200/README b/t/t5200/README\nnew file mode 100644\nindex 0000000..e3a67d9\n--- /dev/null\n+++ b/t/t5200/README\n@@ -0,0 +1,18 @@\n+The url data files in this directory contain URLs with characters\n+in 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 help\n+avoid problems running the test itself.\n+\n+The urls are in test files in this directory rather than being\n+embedded in the test script for portability.\n+\n+The config data files in this directory represent configurations\n+to be parsed by http_options so that the final option value can be\n+tested.\n+\n+The config files may contain more than one same-named section to\n+simulate having a system, global and .git config file.\n+\n+\ndiff --git a/t/t5200/config-1 b/t/t5200/config-1\nnew file mode 100644\nindex 0000000..8aaf23c\n--- /dev/null\n+++ b/t/t5200/config-1\n@@ -0,0 +1,8 @@\n+[http]\n+\tuseragent = other-agent\n+\tnoEPSV = true\n+[http \"https://example.com\"]\n+\tuseragent = example-agent\n+\tsslVerify = false\n+[http \"https://example.com/path\"]\n+\tuseragent = path-agent\ndiff --git a/t/t5200/config-2 b/t/t5200/config-2\nnew file mode 100644\nindex 0000000..749f4bd\n--- /dev/null\n+++ b/t/t5200/config-2\n@@ -0,0 +1,3 @@\n+[http \"https://example.com/path\"]\n+\tuseragent = example-agent\n+\tsslVerify = false\ndiff --git a/t/t5200/url-1 b/t/t5200/url-1\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..519019c5ce6c58478f048a2f39e2321370d318c6\nGIT binary patch\nliteral 20\nbcmb=h($_E4XJle#VP#|I;Nuq%6ygE^Admtt\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/url-10 b/t/t5200/url-10\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..b9965de6a5d74b122179821212b2c27c8ae03e80\nGIT binary patch\nliteral 23\nhcmV+y0O<dCIxjDAFYxj5^Yr!h_xSnx`~3a>{|dCd5i<Y)\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/url-11 b/t/t5200/url-11\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..f0a50f10096a20d597f40c775f09a71276e0050a\nGIT binary patch\nliteral 25\nhcmb=h($_E4Kh$u4|APe$@AvQhFrlI0!}|Suxd5(W4xs=5\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/url-2 b/t/t5200/url-2\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..43334b05b2de3794d6020abd96e634a4e9e49cb0\nGIT binary patch\nliteral 20\nbcmb=h($_E47Zwo}6PJ*bmXVc{ujc{)C{+Vx\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/url-3 b/t/t5200/url-3\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..7378c7bec247b996bc67b00a05ed89cf47d4b7a7\nGIT binary patch\nliteral 23\necmb=h($_E4Z)j|4ZfR|6@96C6?&<C8=K=t7Jqj}b\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/url-4 b/t/t5200/url-4\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..220b198c97f942fea4960f51a2105cc42261061a\nGIT binary patch\nliteral 23\nhcmV+y0O<dCIxjDAFOZRvla!T~mzbHFo1C4Vp9*`u3o`%!\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/url-5 b/t/t5200/url-5\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..1ccd9277792840955bb124bdde21f4b08bcccb63\nGIT binary patch\nliteral 23\nhcmV+y0O<dCIxjDAFQB2Kqok##r>Lo_tE{cAuL^}d3^M=#\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/url-6 b/t/t5200/url-6\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..e8283aac6dff049d3e02454db6e684c5790a5996\nGIT binary patch\nliteral 23\nhcmV+y0O<dCIxjDAFR-z)v$VCgx45~wyS%-=zY31M4Kn}$\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/url-7 b/t/t5200/url-7\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..fa7c10b615259deefd15b638b021da7c60eba1b2\nGIT binary patch\nliteral 23\nhcmV+y0O<dCIxjDAFTlaV!^FkL$H>Xb%goKr&kC454l@7%\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/url-8 b/t/t5200/url-8\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..79a0ba836f5b8886b0a73f161eb292af2b105e65\nGIT binary patch\nliteral 23\nhcmV+y0O<dCIxjDAFVNA_)6~`0*Vx(G+uYsW-wL6<4>JG&\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/url-9 b/t/t5200/url-9\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..8b44bec48b94467c63e8e1ad18162e465da6d6dd\nGIT binary patch\nliteral 23\nhcmV+y0O<dCIxjDAFW}+g<K*S$=jiF`>+J3B?+U9u5HkP(\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/test-url-normalize.c b/test-url-normalize.c\nnew file mode 100644\nindex 0000000..f18bd88\n--- /dev/null\n+++ b/test-url-normalize.c\n@@ -0,0 +1,131 @@\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+static int run_http_options(const char *file,\n+\t\t\t    const char *opt,\n+\t\t\t    const char *url)\n+{\n+\tstruct strbuf opt_lc;\n+\tsize_t i, len;\n+\n+\tif (git_config_with_options(http_options, (void *)url, file, 0))\n+\t\treturn 1;\n+\n+\tlen = strlen(opt);\n+\tstrbuf_init(&opt_lc, len);\n+\tfor (i = 0; i < len; ++i) {\n+\t\tstrbuf_addch(&opt_lc, tolower(opt[i]));\n+\t}\n+\n+\tif (!strcmp(\"sslverify\", opt_lc.buf))\n+\t\tprintf(\"%s\\n\", curl_ssl_verify ? \"true\" : \"false\");\n+\telse if (!strcmp(\"sslcert\", opt_lc.buf))\n+\t\tprintf(\"%s\\n\", ssl_cert);\n+#if LIBCURL_VERSION_NUM >= 0x070903\n+\telse if (!strcmp(\"sslkey\", opt_lc.buf))\n+\t\tprintf(\"%s\\n\", ssl_key);\n+#endif\n+#if LIBCURL_VERSION_NUM >= 0x070908\n+\telse if (!strcmp(\"sslcapath\", opt_lc.buf))\n+\t\tprintf(\"%s\\n\", ssl_capath);\n+#endif\n+\telse if (!strcmp(\"sslcainfo\", opt_lc.buf))\n+\t\tprintf(\"%s\\n\", ssl_cainfo);\n+\telse if (!strcmp(\"sslcertpasswordprotected\", opt_lc.buf))\n+\t\tprintf(\"%s\\n\", ssl_cert_password_required ? \"true\" : \"false\");\n+\telse if (!strcmp(\"ssltry\", opt_lc.buf))\n+\t\tprintf(\"%s\\n\", curl_ssl_try ? \"true\" : \"false\");\n+\telse if (!strcmp(\"minsessions\", opt_lc.buf))\n+\t\tprintf(\"%d\\n\", min_curl_sessions);\n+\telse if (!strcmp(\"maxrequests\", opt_lc.buf))\n+\t\tprintf(\"%d\\n\", max_requests);\n+\telse if (!strcmp(\"lowspeedlimit\", opt_lc.buf))\n+\t\tprintf(\"%ld\\n\", curl_low_speed_limit);\n+\telse if (!strcmp(\"lowspeedtime\", opt_lc.buf))\n+\t\tprintf(\"%ld\\n\", curl_low_speed_time);\n+\telse if (!strcmp(\"noepsv\", opt_lc.buf))\n+\t\tprintf(\"%s\\n\", curl_ftp_no_epsv ? \"true\" : \"false\");\n+\telse if (!strcmp(\"proxy\", opt_lc.buf))\n+\t\tprintf(\"%s\\n\", curl_http_proxy);\n+\telse if (!strcmp(\"cookiefile\", opt_lc.buf))\n+\t\tprintf(\"%s\\n\", curl_cookie_file);\n+\telse if (!strcmp(\"postbuffer\", opt_lc.buf))\n+\t\tprintf(\"%u\\n\", (unsigned)http_post_buffer);\n+\telse if (!strcmp(\"useragent\", opt_lc.buf))\n+\t\tprintf(\"%s\\n\", user_agent);\n+\n+\treturn 0;\n+}\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+\t\t\" | -c file option <url1>\";\n+\tchar *url1, *url2;\n+\tint opt_p = 0, opt_l = 0, opt_c = 0;\n+\tchar *file = NULL, *optname = NULL;\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 * If -c is given, call git_config_with_options using the specified file\n+\t * and http_options and passing the normalized value of the url.  Then\n+\t * print the value of 'option' afterwards.  'option' must be one of the\n+\t * valid 'http.*' options.\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} else if (argc > 3 && !strcmp(argv[1], \"-c\")) {\n+\t\topt_c = 1;\n+\t\tfile = argv[2];\n+\t\toptname = argv[3];\n+\t\targc -= 3;\n+\t\targv += 3;\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\tif (opt_c)\n+\t\t\treturn run_http_options(file, optname, url1);\n+\t\treturn 0;\n+\t}\n+\n+\tif (opt_p || opt_l || opt_c)\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":"223926","messageId":"7dfaadb69accede33b88ae2d9e47a48@f74d39fa044aa309eaea14b9f57fe79","threadId":"34511","inReplyTo":"3c7fc982841069ce79faf227e007815@f74d39fa044aa309eaea14b9f57fe79","subject":"[PATCH v8 4/4] config: allow http.<url>.* any user matching","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T12:56:44Z","receivedAt":"2013-07-22T12:56:44Z","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 |  29 +++--\n http.c                   | 281 +++++++++++++++++++++++++++++++++++++++--------\n t/t5200-url-normalize.sh |   3 +-\n t/t5200/config-3         |   4 +\n test-url-normalize.c     |  17 +--\n 5 files changed, 270 insertions(+), 64 deletions(-)\n create mode 100644 t/t5200/config-3\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e461f32..c418adf 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1517,15 +1517,26 @@ 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 example, if both \"https://user@example.com/path\" and\n+\t\"https://example.com/path/name\" are used as a config <url> value and\n+\tthen \"https://user@example.com/path/name/here\" is passed to a git\n+\tcommand, the settings in the \"https://example.com/path/name\" section\n+\twill be preferred because that <url> has a longer path length match than\n+\t\"https://user@example.com/path\" even though the latter did match the\n+\tuser.  For same length matches, the last one wins except that a same\n+\tlength <url> match that includes a user name will be preferred over a\n+\tsame 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/t/t5200-url-normalize.sh b/t/t5200-url-normalize.sh\nindex d2dd886..f79bb0f 100755\n--- a/t/t5200-url-normalize.sh\n+++ b/t/t5200-url-normalize.sh\n@@ -192,7 +192,8 @@ test_expect_success 'url config normalization matching' '\n \ttest \"$(test-url-normalize -c \"$tc-1\" \"noEPSV\" \"https://example.com\")\" = \"true\" &&\n \ttest \"$(test-url-normalize -c \"$tc-1\" \"noEPSV\" \"https://example.com/path\")\" = \"true\" &&\n \ttest \"$(test-url-normalize -c \"$tc-2\" \"useragent\" \"HTTPS://example.COM/p%61th\")\" = \"example-agent\" &&\n-\ttest \"$(test-url-normalize -c \"$tc-2\" \"sslVerify\" \"HTTPS://example.COM/p%61th\")\" = \"false\"\n+\ttest \"$(test-url-normalize -c \"$tc-2\" \"sslVerify\" \"HTTPS://example.COM/p%61th\")\" = \"false\" &&\n+\ttest \"$(test-url-normalize -c \"$tc-3\" \"sslcainfo\" \"https://user@example.com/path/name/here\")\" = \"file-1\"\n '\n \n test_done\ndiff --git a/t/t5200/config-3 b/t/t5200/config-3\nnew file mode 100644\nindex 0000000..5c8d3e8\n--- /dev/null\n+++ b/t/t5200/config-3\n@@ -0,0 +1,4 @@\n+[http \"https://example.com/path/name\"]\n+\tsslcainfo = file-1\n+[http \"https://user@example.com/path\"]\n+\tsslcainfo = file-2\ndiff --git a/test-url-normalize.c b/test-url-normalize.c\nindex f18bd88..4f870dc 100644\n--- a/test-url-normalize.c\n+++ b/test-url-normalize.c\n@@ -11,12 +11,12 @@ int main()\n \n static int run_http_options(const char *file,\n \t\t\t    const char *opt,\n-\t\t\t    const char *url)\n+\t\t\t    const struct url_info *info)\n {\n \tstruct strbuf opt_lc;\n \tsize_t i, len;\n \n-\tif (git_config_with_options(http_options, (void *)url, file, 0))\n+\tif (git_config_with_options(http_options, (void *)info, file, 0))\n \t\treturn 1;\n \n \tlen = strlen(opt);\n@@ -65,7 +65,7 @@ static int run_http_options(const char *file,\n \treturn 0;\n }\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@@ -108,23 +108,24 @@ 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\tif (opt_c)\n-\t\t\treturn run_http_options(file, optname, url1);\n+\t\t\treturn run_http_options(file, optname, &info);\n \t\treturn 0;\n \t}\n \n \tif (opt_p || opt_l || opt_c)\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":"223929","messageId":"7vehaqcw66.fsf@alter.siamese.dyndns.org","threadId":"34511","inReplyTo":"7dfaadb69accede33b88ae2d9e47a48@f74d39fa044aa309eaea14b9f57fe79","subject":"Re: [PATCH v8 4/4] config: allow http.<url>.* any user matching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-22T18:00:01Z","receivedAt":"2013-07-22T18:00: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> Previously the <url> had to specify an exactly matching user name\n> and password if those were present in the url being matched against.\n>\n> Now the password portion is always ignored and omitting the user\n> name from <url> allows it to match against any user name.\n>\n> Signed-off-by: Kyle J. McKay <mackyle@gmail.com>\n> ---\n>  Documentation/config.txt |  29 +++--\n\nThanks.\n\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index e461f32..c418adf 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -1517,15 +1517,26 @@ 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 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 example, if both \"https://user@example.com/path\" and\n> +\t\"https://example.com/path/name\" are used as a config <url> value and\n> +\tthen \"https://user@example.com/path/name/here\" is passed to a git\n> +\tcommand, the settings in the \"https://example.com/path/name\" section\n> +\twill be preferred because that <url> has a longer path length match than\n> +\t\"https://user@example.com/path\" even though the latter did match the\n> +\tuser.  For same length matches, the last one wins except that a same\n> +\tlength <url> match that includes a user name will be preferred over a\n> +\tsame 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\nA solid wall of text is somewhat hard to read, so I'd queue the\nequivalent of the following \"git diff -w\" output on top.  I also was\ntrying to see if we can clarify the \"length comparison\" only refers\nto the length of the path part, excluding the length of \"user@\"\n(i.e. when comparing \"https://user@example.com/path\" with\n\"https://example.com/path\", they are of the same length), which you\ncan see in the first three lines below.\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex c418adf..635ed5d 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1521,9 +1521,11 @@ http.<url>.*::\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 example, if both \"https://user@example.com/path\" and\n+\tpassword part, if present in the url, is always ignored).  A <url>\n+\twith longer path matches take precedence over shorter matches no matter\n+\twhat order they occur in the configuration file.\n++\n+For example, if both \"https://user@example.com/path\" and\n \"https://example.com/path/name\" are used as a config <url> value and\n then \"https://user@example.com/path/name/here\" is passed to a git\n command, the settings in the \"https://example.com/path/name\" section\n\nI am not yet convinced that the precedence rule specified in this\nwhat we want (I do not have an example why it is *not* what we want,\neither).  Another definition could be \"if user@ is present in the\nrequest, give lower precedence to config entries for the site\nwithout user@ than entries with user@\", and I do not have a strong\nopinion myself which one between the two is better (and there may be\nthird and other possible rule).\n\nComments?\n"},{"id":"223934","messageId":"DF5B20F8-33C2-4F72-A78B-97EE1FB4A522@gmail.com","threadId":"34511","inReplyTo":"7vehaqcw66.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v8 4/4] config: allow http.<url>.* any user matching","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T20:24:06Z","receivedAt":"2013-07-22T20:24:06Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"On Jul 22, 2013, at 11:00, Junio C Hamano wrote:\n> \"Kyle J. McKay\" <mackyle@gmail.com> writes:\n>\n>> diff --git a/Documentation/config.txt b/Documentation/config.txt\n>> index e461f32..c418adf 100644\n>> --- a/Documentation/config.txt\n>> +++ b/Documentation/config.txt\n>> @@ -1517,15 +1517,26 @@ http.<url>.*::\n>> \tAny of the http.* options above can be applied selectively to some  \n>> 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 refers to the same scheme, host and port and  \n>> 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  \n>> 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  \n>> <url>\n>> +\tpath matches take precedence over shorter matches no matter what  \n>> order\n>> +\tthey occur in.  For example, if both \"https://user@example.com/ \n>> path\" and\n>> +\t\"https://example.com/path/name\" are used as a config <url> value  \n>> and\n>> +\tthen \"https://user@example.com/path/name/here\" is passed to a git\n>> +\tcommand, the settings in the \"https://example.com/path/name\"  \n>> section\n>> +\twill be preferred because that <url> has a longer path length  \n>> match than\n>> +\t\"https://user@example.com/path\" even though the latter did match  \n>> the\n>> +\tuser.  For same length matches, the last one wins except that a  \n>> same\n>> +\tlength <url> match that includes a user name will be preferred  \n>> over a\n>> +\tsame length <url> match that does not.  The urls are normalized  \n>> before\n>> +\tmatching so that equivalent urls that are simply spelled  \n>> differently\n>> +\twill match properly.  Environment variable settings always  \n>> override any\n>> +\tmatches.  The urls that are matched against are those given  \n>> directly to\n>> +\tgit commands.  This means any urls visited as a result of a  \n>> redirection\n>> +\tdo not participate in matching.\n>\n> A solid wall of text is somewhat hard to read, so I'd queue the\n> equivalent of the following \"git diff -w\" output on top.\n\nCan I send out the change as a 'fixup!' patch?  Or do I need to send a  \nnew v9 patch series with the documentation update?\n\n> I also was\n> trying to see if we can clarify the \"length comparison\" only refers\n> to the length of the path part, excluding the length of \"user@\"\n> (i.e. when comparing \"https://user@example.com/path\" with\n> \"https://example.com/path\", they are of the same length), which you\n> can see in the first three lines below.\n>\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index c418adf..635ed5d 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -1521,9 +1521,11 @@ http.<url>.*::\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  \n> <url>\n> -\tpath matches take precedence over shorter matches no matter what  \n> order\n> -\tthey occur in.  For example, if both \"https://user@example.com/ \n> path\" and\n> +\tpassword part, if present in the url, is always ignored).  A <url>\n> +\twith longer path matches take precedence over shorter matches no  \n> matter\n> +\twhat order they occur in the configuration file.\n> ++\n> +For example, if both \"https://user@example.com/path\" and\n> \"https://example.com/path/name\" are used as a config <url> value and\n> then \"https://user@example.com/path/name/here\" is passed to a git\n> command, the settings in the \"https://example.com/path/name\" section\n\nOK.\n\n> I am not yet convinced that the precedence rule specified in this\n> what we want (I do not have an example why it is *not* what we want,\n> either).  Another definition could be \"if user@ is present in the\n> request, give lower precedence to config entries for the site\n> without user@ than entries with user@\", and I do not have a strong\n> opinion myself which one between the two is better (and there may be\n> third and other possible rule).\n>\n> Comments?\n\nConsider this site:\n\nexample.com/\nexample.com/dir\nexample.com/dir/sub\nexample.com/dir/sub/public\n\nSuppose I want to configure a particular key for example.com/dir/sub  \nfor a particular user, say \"contractor\".\n\nI can configure:\n\n[http \"https://contractor@example.com/dir/sub\"]\n   sslkey=contractor_key\n\nBut then I want to configure a completely different key for the public  \narea example.com/dir/sub/public no matter what user.  I just add this:\n\n[http \"https://example.com/dir/sub/public\"]\n   sslkey=public_key\n\nBut if entries with usernames take precedence it won't work.  The only  \nway to do it is to list every possible user for \"example.com/dir1/sub1/ \npublic\" in its own new section and then continue to add a new config  \nsection every time a new user name is encountered.\n\nConversely, if a special key is supposed to be used for the user  \n\"contractor\" everywhere on the site at or below \"example.com/dir/sub\",  \nthen the above configuration doesn't work unless user matches take  \nprecedence.  With the current code, one additional entry would have to  \nbe added like so:\n\n[http \"https://contractor@example.com/dir/sub/public\"]\n   sslkey=contractor_key\n\nSo my thinking was that having user matches take precedence over path  \nlength matches can result in endless additions to the config file  \n(because you have to list all the other users to override a sub area  \nand that could be a large list) whereas having path length matches  \ntake precedence over user matches will only result in a few, finite  \nadditions to the config file (the number of already-configured items  \nwith a longer path).\n"},{"id":"223941","messageId":"7v1u6qclfx.fsf@alter.siamese.dyndns.org","threadId":"34511","inReplyTo":"DF5B20F8-33C2-4F72-A78B-97EE1FB4A522@gmail.com","subject":"Re: [PATCH v8 4/4] config: allow http.<url>.* any user matching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-22T21:51:46Z","receivedAt":"2013-07-22T21:51:46Z","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>> A solid wall of text is somewhat hard to read, so I'd queue the\n>> equivalent of the following \"git diff -w\" output on top.\n>\n> Can I send out the change as a 'fixup!' patch?  Or do I need to send a\n> new v9 patch series with the documentation update?\n\nIf you are OK with splitting it into two paragraphs with the\n\"longest\" clarification tweak (the \"patch\" I showed you), just\nsaying so and I can squash ;-) so there is no need to resend.\n\n>> diff --git a/Documentation/config.txt b/Documentation/config.txt\n>> index c418adf..635ed5d 100644\n>> --- a/Documentation/config.txt\n>> +++ b/Documentation/config.txt\n>> @@ -1521,9 +1521,11 @@ http.<url>.*::\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).\n>> Longer <url>\n>> -\tpath matches take precedence over shorter matches no matter\n>> what order\n>> -\tthey occur in.  For example, if both\n>> \"https://user@example.com/\n>> path\" and\n>> +\tpassword part, if present in the url, is always ignored).  A <url>\n>> +\twith longer path matches take precedence over shorter matches\n>> no matter\n>> +\twhat order they occur in the configuration file.\n>> ++\n>> +For example, if both \"https://user@example.com/path\" and\n>> \"https://example.com/path/name\" are used as a config <url> value and\n>> then \"https://user@example.com/path/name/here\" is passed to a git\n>> command, the settings in the \"https://example.com/path/name\" section\n>\n> OK.\n\n... which essentially is your \"OK\" ;-)\n\n>> I am not yet convinced that the precedence rule specified in this\n>> what we want (I do not have an example why it is *not* what we want,\n>> either).  Another definition could be \"if user@ is present in the\n>> request, give lower precedence to config entries for the site\n>> without user@ than entries with user@\", and I do not have a strong\n>> opinion myself which one between the two is better (and there may be\n>> third and other possible rule).\n>>\n>> Comments?\n>\n> Consider this site:\n> ...\n> So my thinking was that having user matches take precedence over path\n> length matches can result in endless additions to the config file\n> (because you have to list all the other users to override a sub area\n> and that could be a large list) whereas having path length matches\n> take precedence over user matches will only result in a few, finite\n> additions to the config file (the number of already-configured items\n> with a longer path).\n"},{"id":"223944","messageId":"14CC7E43-3588-4B36-A8E9-79C22D548D8D@gmail.com","threadId":"34511","inReplyTo":"7v1u6qclfx.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v8 4/4] config: allow http.<url>.* any user matching","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-22T22:18:00Z","receivedAt":"2013-07-22T22:18:00Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"\nOn Jul 22, 2013, at 14:51, Junio C Hamano wrote:\n\n> \"Kyle J. McKay\" <mackyle@gmail.com> writes:\n>\n>>> A solid wall of text is somewhat hard to read, so I'd queue the\n>>> equivalent of the following \"git diff -w\" output on top.\n>>\n>> Can I send out the change as a 'fixup!' patch?  Or do I need to  \n>> send a\n>> new v9 patch series with the documentation update?\n>\n> If you are OK with splitting it into two paragraphs with the\n> \"longest\" clarification tweak (the \"patch\" I showed you), just\n> saying so and I can squash ;-) so there is no need to resend.\n\nThe wording of:\n\n\"+\tpassword part, if present in the url, is always ignored).  A <url>\n+\twith longer path matches take precedence over shorter matches no  \nmatter\n+\twhat order they occur in the configuration file.\n\nneeds to be fixed first.  Replace \"take\" with \"takes\" and you can go  \nahead and squash it in.  :)\n\n\n>>> diff --git a/Documentation/config.txt b/Documentation/config.txt\n>>> index c418adf..635ed5d 100644\n>>> --- a/Documentation/config.txt\n>>> +++ b/Documentation/config.txt\n>>> @@ -1521,9 +1521,11 @@ http.<url>.*::\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  \n>>> 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).\n>>> Longer <url>\n>>> -\tpath matches take precedence over shorter matches no matter\n>>> what order\n>>> -\tthey occur in.  For example, if both\n>>> \"https://user@example.com/\n>>> path\" and\n>>> +\tpassword part, if present in the url, is always ignored).  A <url>\n>>> +\twith longer path matches take precedence over shorter matches\n>>> no matter\n>>> +\twhat order they occur in the configuration file.\n>>> ++\n>>> +For example, if both \"https://user@example.com/path\" and\n>>> \"https://example.com/path/name\" are used as a config <url> value and\n>>> then \"https://user@example.com/path/name/here\" is passed to a git\n>>> command, the settings in the \"https://example.com/path/name\" section\n>>\n>> OK.\n>\n> ... which essentially is your \"OK\" ;-)\n\nYes, I meant that as \"OK I will send out an update that includes  \nsomething like this.\"\n\nAfter replacing \"take\" with \"takes\" in the change I'm good with just  \nsquashing that diff in.\n"},{"id":"223951","messageId":"7vob9u9qc8.fsf@alter.siamese.dyndns.org","threadId":"34511","inReplyTo":"14CC7E43-3588-4B36-A8E9-79C22D548D8D@gmail.com","subject":"Re: [PATCH v8 4/4] config: allow http.<url>.* any user matching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-22T22:34:15Z","receivedAt":"2013-07-22T22:34:15Z","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> After replacing \"take\" with \"takes\" in the change I'm good with just\n> squashing that diff in.\n\nThanks for proofreading.  Then let's omit an extra back-and-forth.\n"},{"id":"224020","messageId":"20130724064258.GA30074@sigill.intra.peff.net","threadId":"34511","inReplyTo":"DF5B20F8-33C2-4F72-A78B-97EE1FB4A522@gmail.com","subject":"Re: [PATCH v8 4/4] config: allow http.<url>.* any user matching","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2013-07-24T06:42:59Z","receivedAt":"2013-07-24T06:42:59Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 22, 2013 at 01:24:06PM -0700, Kyle J. McKay wrote:\n\n> >I am not yet convinced that the precedence rule specified in this\n> >what we want (I do not have an example why it is *not* what we want,\n> >either).  Another definition could be \"if user@ is present in the\n> >request, give lower precedence to config entries for the site\n> >without user@ than entries with user@\", and I do not have a strong\n> >opinion myself which one between the two is better (and there may be\n> >third and other possible rule).\n> >\n> >Comments?\n> \n> Consider this site:\n> [...]\n\nThanks for explaining, and sorry I missed out on the last few rounds of\nreview.\n\nI think your scheme (normalization plus special handling of the username\nfield) addresses my biggest concern, which is matching in the face of\noptional usernames. The only two things that make me wary are:\n\n  1. The explanation and special-casing of username is a little\n     complicated to explain.\n\n  2. The behavior for resolving the value when faced with multiple\n     possibilities is completely unlike the rest of the config system\n     (both dropping last-one-wins, and unlike the URL matching for\n     credentials).\n\nI think we can decide that (2) is worth it if your semantics are more\nflexible in practice. It would be nice to see real-world feedback on how\npeople use it before setting the behavior in stone, but there's sort of\na chicken and egg problem there.\n\nFor (1), I wonder if the explanation would be simpler if the precedences\nof each sub-part were simply laid out. That is, would it be correct to\nsay something like:\n\n  For a config key to match a URL, each element of the config key (if\n  present) is compared to that of the URL, in the following order:\n\n    1. Protocol (e.g., `https` in `https://example.com/`). This field\n       must match exactly between the config key and the URL.\n\n    2. Host/domain name (e.g., `example.com` in `https://example.com/`).\n       This field must match exactly between the config key and the URL.\n\n    3. Path (e.g., `repo.git` in `https://example.com/repo.git`). This\n       field is prefix-matched by slash-delimited path elements, so that\n       config key `foo/` matches URL `foo/bar`. Longer matches take\n       precedence (so `foo/bar`, if it exists, is a better match than\n       just `foo/`).\n\n    4. Username (e.g., `user` in `https://user@example.com/repo.git`).\n\n  The list above is ordered by decreasing precedence; a URL that matches\n  a config key's path is preferred to one that matches its username.\n\nI don't know if that is more or less clear of an explanation. It makes\nmore sense to me, but that is probably because I wrote it. I'm also not\n100% sure it describes your implementation, but I think it is equivalent\nto the prefix matching with normalization.\n\nI have a few other comments on specific patches; I'll send them\nseparately.\n\n-Peff\n"},{"id":"224021","messageId":"20130724064447.GB30074@sigill.intra.peff.net","threadId":"34511","inReplyTo":"7dfaadb69accede33b88ae2d9e47a48@f74d39fa044aa309eaea14b9f57fe79","subject":"Re: [PATCH v8 4/4] config: allow http.<url>.* any user matching","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2013-07-24T06:44:48Z","receivedAt":"2013-07-24T06:44:48Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 22, 2013 at 05:56:44AM -0700, Kyle J. McKay wrote:\n\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 example, if both \"https://user@example.com/path\" and\n> +\t\"https://example.com/path/name\" are used as a config <url> value and\n> +\tthen \"https://user@example.com/path/name/here\" is passed to a git\n> +\tcommand, the settings in the \"https://example.com/path/name\" section\n\nThese \"https://...\" should probably be `https://...`, to mark them in\nasciidoc as literals.\n\n-Peff\n"},{"id":"224022","messageId":"20130724065933.GC30074@sigill.intra.peff.net","threadId":"34511","inReplyTo":"60d85be89d27515d913ae15e10c332f@f74d39fa044aa309eaea14b9f57fe79","subject":"Re: [PATCH v8 3/4] tests: add new test for the url_normalize function","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2013-07-24T06:59:33Z","receivedAt":"2013-07-24T06:59:33Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 22, 2013 at 05:56:43AM -0700, Kyle J. McKay wrote:\n\n> In order to perform sane URL matching for http.<url>.* options,\n> http.c normalizes URLs before performing matches.\n> \n> A new test-url-normalize test program is introduced along with\n> a new t5200-url-normalize.sh script to run the tests.\n\nWhile looking at test-url-normalize (and wanting to experiment some with\nyour series to see how it handled a few behaviors), I wondered if we\nshouldn't be exposing this selection process to the user somehow via\ngit-config.\n\nThat is, would a shell script ever want to say \"what is the value of\nthis config variable for url $X\"? Certainly our test scripts want to,\nand having a test-* program covers that, but might user scripts want to\ndo the same? Or even to introduce its own URL-matched config options?\n\nHow hard would it be to convert the \"-c\" option of test-url-normalize\ninto something like:\n\n  git config --file=foo --url http noepsv $URL\n\nwhich would look for http.$URL.noepsv matches.\n\nIf we want to go that route, we don't have to do it now (the test-*\ninterface is unpublished, and the git-config interface can simply come\nlater than the internal feature).  But it may be worth thinking about\nnow while it is in your head.\n\n> diff --git a/test-url-normalize.c b/test-url-normalize.c\n> new file mode 100644\n> index 0000000..f18bd88\n> --- /dev/null\n> +++ b/test-url-normalize.c\n> [...]\n> +\tif (!strcmp(\"sslverify\", opt_lc.buf))\n> +\t\tprintf(\"%s\\n\", curl_ssl_verify ? \"true\" : \"false\");\n> +\telse if (!strcmp(\"sslcert\", opt_lc.buf))\n> +\t\tprintf(\"%s\\n\", ssl_cert);\n> +#if LIBCURL_VERSION_NUM >= 0x070903\n> +\telse if (!strcmp(\"sslkey\", opt_lc.buf))\n> +\t\tprintf(\"%s\\n\", ssl_key);\n> +#endif\n> +#if LIBCURL_VERSION_NUM >= 0x070908\n> +\telse if (!strcmp(\"sslcapath\", opt_lc.buf))\n> +\t\tprintf(\"%s\\n\", ssl_capath);\n> +#endif\n> [...]\n\nDo we need to have the complete list of options here, including curl\nversion limitations? It seems like this will eventually get out of date\nwith the list of options. Would it be sufficient to test just one (or\neven just handle a fake \"http.$URL.foo\" variable)?\n\n> +#define url_normalize(u) http_options_url_normalize(u)\n\nDoes this macro do anything besides shorten the name? Is the extra\nlevel of indirection to the reader worth it?\n\n-Peff\n"},{"id":"224023","messageId":"20130724071226.GD30074@sigill.intra.peff.net","threadId":"34511","inReplyTo":"30e562871d9632141d612876db3147e@f74d39fa044aa309eaea14b9f57fe79","subject":"Re: [PATCH v8 1/4] config: add support for http.<url>.* settings","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2013-07-24T07:12:26Z","receivedAt":"2013-07-24T07:12:26Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 22, 2013 at 05:56:41AM -0700, Kyle J. McKay wrote:\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\nIt's frustrating that we now have an extra spot to add new options to\n(e.g., somebody else is adding http.savecookies in another thread, and\nthe merge will need to not just resolve the textual conflicts, but add\nit to this other list).\n\nMight it be simpler to just keep a dynamic list indexed by option name\n(either a hash table, or a sorted string_list)? We only incur a lookup\nwhen we find an actual config entry, so the number of lookups (and\nentries) should be minuscule and not affect performance. And as a bonus,\nit lets us handle arbitrary keys if we want to allow \"git config\" to\nlearn about url matching for user-specified keys.\n\n-Peff\n"},{"id":"224043","messageId":"7vfvv46m0q.fsf@alter.siamese.dyndns.org","threadId":"34511","inReplyTo":"20130724064258.GA30074@sigill.intra.peff.net","subject":"Re: [PATCH v8 4/4] config: allow http.<url>.* any user matching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-24T15:00:21Z","receivedAt":"2013-07-24T15:00:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n>   1. The explanation and special-casing of username is a little\n>      complicated to explain.\n>\n>   2. The behavior for resolving the value when faced with multiple\n>      possibilities is completely unlike the rest of the config system\n>      (both dropping last-one-wins, and unlike the URL matching for\n>      credentials).\n>\n> I think we can decide that (2) is worth it if your semantics are more\n> flexible in practice. It would be nice to see real-world feedback on how\n> people use it before setting the behavior in stone, but there's sort of\n> a chicken and egg problem there.\n>\n> For (1), I wonder if the explanation would be simpler if the precedences\n> of each sub-part were simply laid out. That is, would it be correct to\n> say something like:\n>\n>   For a config key to match a URL, each element of the config key (if\n>   present) is compared to that of the URL, in the following order:\n>\n>     1. Protocol (e.g., `https` in `https://example.com/`). This field\n>        must match exactly between the config key and the URL.\n>\n>     2. Host/domain name (e.g., `example.com` in `https://example.com/`).\n>        This field must match exactly between the config key and the URL.\n>\n>     3. Path (e.g., `repo.git` in `https://example.com/repo.git`). This\n>        field is prefix-matched by slash-delimited path elements, so that\n>        config key `foo/` matches URL `foo/bar`. Longer matches take\n>        precedence (so `foo/bar`, if it exists, is a better match than\n>        just `foo/`).\n>\n>     4. Username (e.g., `user` in `https://user@example.com/repo.git`).\n>\n>   The list above is ordered by decreasing precedence; a URL that matches\n>   a config key's path is preferred to one that matches its username.\n>\n> I don't know if that is more or less clear of an explanation. It makes\n> more sense to me, but that is probably because I wrote it. \n\nThe above reads very clearly to me, too.  We may want to add that\nthe Username, if exists on the configuration key name, must match\nexactly to the one in the URL the variable is queried for\n(i.e. anonymous requests never match configuration for a specific\nuser).\n"},{"id":"224049","messageId":"7vy58v6fsc.fsf@alter.siamese.dyndns.org","threadId":"34511","inReplyTo":"20130724065933.GC30074@sigill.intra.peff.net","subject":"Re: [PATCH v8 3/4] tests: add new test for the url_normalize function","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-24T17:14:59Z","receivedAt":"2013-07-24T17:14:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> That is, would a shell script ever want to say \"what is the value of\n> this config variable for url $X\"? Certainly our test scripts want to,\n> and having a test-* program covers that, but might user scripts want to\n> do the same? Or even to introduce its own URL-matched config options?\n>\n> How hard would it be to convert the \"-c\" option of test-url-normalize\n> into something like:\n>\n>   git config --file=foo --url http noepsv $URL\n>\n> which would look for http.$URL.noepsv matches.\n\nLovely.\n\n>> +#if LIBCURL_VERSION_NUM >= 0x070903\n>> +\telse if (!strcmp(\"sslkey\", opt_lc.buf))\n>> +\t\tprintf(\"%s\\n\", ssl_key);\n>> +#endif\n>> +#if LIBCURL_VERSION_NUM >= 0x070908\n>> +\telse if (!strcmp(\"sslcapath\", opt_lc.buf))\n>> +\t\tprintf(\"%s\\n\", ssl_capath);\n>> +#endif\n>> [...]\n>\n> Do we need to have the complete list of options here, including curl\n> version limitations? It seems like this will eventually get out of date\n> with the list of options. Would it be sufficient to test just one (or\n> even just handle a fake \"http.$URL.foo\" variable)?\n\nYeah, and that will be in line with \"git config --url\" direction.\n\nAnother thing we may want to consider is to see if we can\nrestructure the http_options interface a bit, so that the caller can\nbe agonistic to the actual meaning of the key.  For example,\n\n  \"git config --url http notknownyet $URL\" \n\nmay want to be able to show the value for http.<pattern>.notknownyet\nfor the most matching <pattern> for a given $URL, without knowing\nwhat the variable means, just like any other configuration that is\nqueried via the \"git config\" program.  The caller may want to pass\nfurther type information like --bool, --int and --path as needed.\n\n>> +#define url_normalize(u) http_options_url_normalize(u)\n>\n> Does this macro do anything besides shorten the name? Is the extra\n> level of indirection to the reader worth it?\n\nProbably not.\n\nThanks.\n"},{"id":"224051","messageId":"D00D01B3-6B93-4881-9CA3-4745799373C5@gmail.com","threadId":"34511","inReplyTo":"7vy58v6fsc.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v8 3/4] tests: add new test for the url_normalize function","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-24T18:43:59Z","receivedAt":"2013-07-24T18:43:59Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"On Jul 24, 2013, at 10:14, Junio C Hamano wrote:\n> Jeff King <peff@peff.net> writes:\n>\n>> How hard would it be to convert the \"-c\" option of test-url-normalize\n>> into something like:\n>>\n>>  git config --file=foo --url http noepsv $URL\n>>\n>> which would look for http.$URL.noepsv matches.\n>\n> Lovely.\n\n[snip]\n\n> Another thing we may want to consider is to see if we can\n> restructure the http_options interface a bit, so that the caller can\n> be agonistic to the actual meaning of the key.  For example,\n>\n>  \"git config --url http notknownyet $URL\"\n>\n> may want to be able to show the value for http.<pattern>.notknownyet\n> for the most matching <pattern> for a given $URL, without knowing\n> what the variable means, just like any other configuration that is\n> queried via the \"git config\" program.  The caller may want to pass\n> further type information like --bool, --int and --path as needed.\n>\n>>> +#define url_normalize(u) http_options_url_normalize(u)\n>>\n>> Does this macro do anything besides shorten the name? Is the extra\n>> level of indirection to the reader worth it?\n>\n> Probably not.\n\nIt's a hint that the name of the function might change.  If it's going  \nto be used in a more generic fashion it may not belong in http.c  \nanymore in which case the name will likely change along with the  \nlocation.  Nothing about the normalization function requires CURL or  \nhttp/https, so it would work equally well on rsync, ftp, smtp etc.  \nurls and could just move into url.{h,c} as url_normalize.\n"},{"id":"224052","messageId":"D8BC48D1-195D-4F08-876C-FA592802A12C@gmail.com","threadId":"34511","inReplyTo":"20130724065933.GC30074@sigill.intra.peff.net","subject":"Re: [PATCH v8 3/4] tests: add new test for the url_normalize function","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-24T19:01:26Z","receivedAt":"2013-07-24T19:01:26Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"On Jul 23, 2013, at 23:59, Jeff King wrote:\n\n>> diff --git a/test-url-normalize.c b/test-url-normalize.c\n>> new file mode 100644\n>> index 0000000..f18bd88\n>> --- /dev/null\n>> +++ b/test-url-normalize.c\n>> [...]\n>> +\tif (!strcmp(\"sslverify\", opt_lc.buf))\n>> +\t\tprintf(\"%s\\n\", curl_ssl_verify ? \"true\" : \"false\");\n>> +\telse if (!strcmp(\"sslcert\", opt_lc.buf))\n>> +\t\tprintf(\"%s\\n\", ssl_cert);\n>> +#if LIBCURL_VERSION_NUM >= 0x070903\n>> +\telse if (!strcmp(\"sslkey\", opt_lc.buf))\n>> +\t\tprintf(\"%s\\n\", ssl_key);\n>> +#endif\n>> +#if LIBCURL_VERSION_NUM >= 0x070908\n>> +\telse if (!strcmp(\"sslcapath\", opt_lc.buf))\n>> +\t\tprintf(\"%s\\n\", ssl_capath);\n>> +#endif\n>> [...]\n>\n> Do we need to have the complete list of options here, including curl\n> version limitations? It seems like this will eventually get out of  \n> date\n> with the list of options. Would it be sufficient to test just one (or\n> even just handle a fake \"http.$URL.foo\" variable)?\n\nRight now, the values are only available as various strings, ints,  \nlongs etc. which have to be formatted differently for output.  The  \noriginal string value they were converted from is gone.  The snippet  \nshown above only shows some of the \"%s\" formatters.\n\nEither the original value will have to be kept around or a  \nreconstituted string depending on what:\n\ngit config --file=foo --url http noepsv $URL\n\nshould output.  If the original value was 0 or 1, should it output  \nthat or \"false\" or \"true\"?  The test-url-normalize code for \"-c\"  \nnormalizes the output to \"false\" or \"true\" for all boolean values and  \nreconverts ints/longs to strings.\n"},{"id":"224053","messageId":"20130724190356.GA11444@sigill.intra.peff.net","threadId":"34511","inReplyTo":"D8BC48D1-195D-4F08-876C-FA592802A12C@gmail.com","subject":"Re: [PATCH v8 3/4] tests: add new test for the url_normalize function","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2013-07-24T19:03:56Z","receivedAt":"2013-07-24T19:03:56Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jul 24, 2013 at 12:01:26PM -0700, Kyle J. McKay wrote:\n\n> Right now, the values are only available as various strings, ints,\n> longs etc. which have to be formatted differently for output.  The\n> original string value they were converted from is gone.  The snippet\n> shown above only shows some of the \"%s\" formatters.\n> \n> Either the original value will have to be kept around or a\n> reconstituted string depending on what:\n> \n> git config --file=foo --url http noepsv $URL\n> \n> should output.  If the original value was 0 or 1, should it output\n> that or \"false\" or \"true\"?  The test-url-normalize code for \"-c\"\n> normalizes the output to \"false\" or \"true\" for all boolean values and\n> reconverts ints/longs to strings.\n\nI think it would be the responsibility of the caller to specify what\nthey are looking for. I.e., add \"--bool\" to the git-config command line\nas appropriate.\n\n-Peff\n"}]}