{"thread":{"id":"34576","subject":"[PATCH v6 0/6] http.<url>.<key> and friends","startedAt":"2013-07-31T19:26:02Z","lastAt":"2013-08-05T23:57:23Z","messageCount":23,"participants":["Junio C Hamano","Kyle J. McKay","Jeff King"],"isPatch":true,"patchVersion":6,"patchTotal":6},"messages":[{"id":"224374","messageId":"1375298768-7740-1-git-send-email-gitster@pobox.com","threadId":"34576","inReplyTo":null,"subject":"[PATCH v6 0/6] http.<url>.<key> and friends","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-31T19:26:02Z","receivedAt":"2013-07-31T19:26:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This is my attempt to reroll Kyle's http.<url>.<key> series.\n\nIt adds a general <section>.<url>.<key> support at the\ninfrastructure level and then rebuild http.<url>.<key> support on\ntop of it.  A useful side effect of doing it this way is that it\navoids having to touch the two-name parser http_options() at all.\n\nThe same infrastructure is used to add \"--get-urlmatch\" mode to \"git\nconfig\", so that scripted Porcelains can use the same mechanism to\nask for the value for <section>.<key> variable with a URL, and learn\nthe value for <section>.<url>.<key> whose <url> part best matches\nthe given URL.  In a sense, the infrastructure makes <section>.<key>\na \"virtual\" variable that is customized for URL.\n\n * Patch 1/6 is unchanged.\n\n * Patch 2/6 is to add only the two helpers url_normalize and\n   match_urls from the original series by Kyle.\n\n * Patch 3/6 is the general <section>.<url>.<key> support.  The\n   urlmatch_config_entry() wrapper can use existing two-name parser\n   to implement \"virtual\" <section>.<key> variables.\n\n * Patch 4/6 is the rest of Kyle's http.<url>.<key> ported on top of\n   the infrastructure.\n\n * Patch 5/6 is unchanged from the previous round.\n\n * Patch 6/6 teaches \"--get-urlmatch\" to \"git config\"; this time it\n   adds tests and docs.\n\nJunio C Hamano (4):\n  http.c: fix parsing of http.sslCertPasswordProtected variable\n  config: add generic callback shim to parse section.<url>.key\n  builtin/config: refactor collect_config()\n  config: \"git config --get-urlmatch\" parses section.<url>.key\n\nKyle J. McKay (2):\n  config: add helper to normalize and match URLs\n  config: parse http.<url>.<variable> using urlmatch\n\n .gitignore                   |   1 +\n Documentation/config.txt     |  44 ++++\n Documentation/git-config.txt |  29 +++\n Makefile                     |   7 +\n builtin/config.c             | 134 +++++++++--\n http.c                       |  16 +-\n t/.gitattributes             |   1 +\n t/t1300-repo-config.sh       |  25 ++\n t/t5200-url-normalize.sh     | 199 ++++++++++++++++\n t/t5200/README               | Bin 0 -> 644 bytes\n t/t5200/config-1             | Bin 0 -> 180 bytes\n t/t5200/config-2             | Bin 0 -> 80 bytes\n t/t5200/config-3             | Bin 0 -> 118 bytes\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         | 137 +++++++++++\n urlmatch.c                   | 535 +++++++++++++++++++++++++++++++++++++++++++\n urlmatch.h                   |  54 +++++\n 27 files changed, 1158 insertions(+), 24 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 create mode 100644 urlmatch.c\n create mode 100644 urlmatch.h\n\n-- \n1.8.4-rc0-153-g9820077\n"},{"id":"224375","messageId":"1375298768-7740-2-git-send-email-gitster@pobox.com","threadId":"34576","inReplyTo":"1375298768-7740-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 1/6] http.c: fix parsing of http.sslCertPasswordProtected variable","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-31T19:26:03Z","receivedAt":"2013-07-31T19:26:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The existing code triggers only when the configuration variable is\nset to true.  Once the variable is set to true in a more generic\nconfiguration file (e.g. ~/.gitconfig), it cannot be overriden to\nfalse in the repository specific one (e.g. .git/config).\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n http.c | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)\n\ndiff --git a/http.c b/http.c\nindex 92aba59..37986f8 100644\n--- a/http.c\n+++ b/http.c\n@@ -160,8 +160,7 @@ static int http_options(const char *var, const char *value, void *cb)\n \tif (!strcmp(\"http.sslcainfo\", var))\n \t\treturn git_config_string(&ssl_cainfo, var, value);\n \tif (!strcmp(\"http.sslcertpasswordprotected\", var)) {\n-\t\tif (git_config_bool(var, value))\n-\t\t\tssl_cert_password_required = 1;\n+\t\tssl_cert_password_required = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.ssltry\", var)) {\n-- \n1.8.4-rc0-153-g9820077\n"},{"id":"224377","messageId":"1375298768-7740-3-git-send-email-gitster@pobox.com","threadId":"34576","inReplyTo":"1375298768-7740-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 2/6] config: add helper to normalize and match URLs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-31T19:26:04Z","receivedAt":"2013-07-31T19:26:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"From: \"Kyle J. McKay\" <mackyle@gmail.com>\n\nSome http.* configuration variables need to take values customized\nfor the URL we are talking to.  We may want to set http.sslVerify to\ntrue in general but to false only for a certain site, for example,\nwith a configuration file like this:\n\n\t[http]\n\t\tsslVerify = true\n\t[http \"https://weak.example.com\"]\n\t\tsslVerify = false\n\nand let the configuration machinery pick up the latter only when\ntalking to \"https://weak.example.com\".  The latter needs to kick in\nnot only when the URL is exactly \"https://weak.example.com\", but\nalso is anything that \"match\" it, e.g.\n\n\thttps://weak.example.com/test\n\thttps://me@weak.example.com/test\n\nThe <url> in the configuration key consists of the following parts,\nand is considered a match to the URL we are attempting to access\nunder certain conditions:\n\n  . Scheme (e.g., `https` in `https://example.com/`). This field\n    must match exactly between the config key and the URL.\n\n  . 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  . Port number (e.g., `8080` in `http://example.com:8080/`).  This\n    field must match exactly between the config key and the URL.\n    Omitted port numbers are automatically converted to the correct\n    default for the scheme before matching.\n\n  . Path (e.g., `repo.git` in `https://example.com/repo.git`). The\n    path field of the config key must match the path field of the\n    URL either exactly or as a prefix of slash-delimited path\n    elements.  A config key with path `foo/` matches URL path\n    `foo/bar`.  A prefix can only match on a slash (`/`) boundary.\n    Longer matches take precedence (so a config key with path\n    `foo/bar` is a better match to URL path `foo/bar` than a config\n    key with just path `foo/`).\n\n  . User name (e.g., `me` in `https://me@example.com/repo.git`). If\n    the config key has a user name, it must match the user name in\n    the URL exactly. If the config key does not have a user name,\n    that config key will match a URL with any user name (including\n    none).\n\nLonger matches take precedence over shorter matches.\n\nThis step adds two helper functions `url_normalize()` and\n`match_urls()` to help implement the above semantics. The\nnormalization rules are based on RFC 3986 and should result in any\ntwo equivalent urls being a match.\n\nSigned-off-by: Kyle J. McKay <mackyle@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n urlmatch.c | 468 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n urlmatch.h |  36 +++++\n 2 files changed, 504 insertions(+)\n create mode 100644 urlmatch.c\n create mode 100644 urlmatch.h\n\ndiff --git a/urlmatch.c b/urlmatch.c\nnew file mode 100644\nindex 0000000..e1b03ee\n--- /dev/null\n+++ b/urlmatch.c\n@@ -0,0 +1,468 @@\n+#include \"cache.h\"\n+#include \"urlmatch.h\"\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+char *url_normalize(const char *url, struct url_info *out_info)\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 * 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+\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+\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+\tchar *result;\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\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+\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\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+\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\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\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 (!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+\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\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\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\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+\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_off = norm.len;\n+\tpath_start = norm.buf + path_off;\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\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\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\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+\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+\tpath_len = norm.len - path_off;\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\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+\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+\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 (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+\tif (!url || !url_prefix)\n+\t\treturn 0;\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 (strncmp(url, url_prefix, url_prefix_len))\n+\t\treturn 0;\n+\tif ((strlen(url) == url_prefix_len) || (url[url_prefix_len] == '/'))\n+\t\treturn url_prefix_len + 1;\n+\treturn 0;\n+}\n+\n+int match_urls(const struct url_info *url,\n+\t       const struct url_info *url_prefix,\n+\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+}\ndiff --git a/urlmatch.h b/urlmatch.h\nnew file mode 100644\nindex 0000000..b67f57f\n--- /dev/null\n+++ b/urlmatch.h\n@@ -0,0 +1,36 @@\n+#ifndef URL_MATCH_H\n+#include \"string-list.h\"\n+\n+struct url_info {\n+\t/* normalized url on success, must be freed, otherwise NULL */\n+\tchar *url;\n+\t/* if !url, a brief reason for the failure, otherwise NULL */\n+\tconst char *err;\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+extern char *url_normalize(const char *, struct url_info *);\n+extern int match_urls(const struct url_info *url, const struct url_info *url_prefix, int *exactusermatch);\n+\n+#endif /* URL_MATCH_H */\n-- \n1.8.4-rc0-153-g9820077\n"},{"id":"224378","messageId":"1375298768-7740-4-git-send-email-gitster@pobox.com","threadId":"34576","inReplyTo":"1375298768-7740-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 3/6] config: add generic callback wrapper to parse section.<url>.key","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-31T19:26:05Z","receivedAt":"2013-07-31T19:26:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Existing configuration parsing functions (e.g. http_options() in\nhttp.c) know how to parse two-level configuration variable names.\nWe would like to exploit them and parse something like this:\n\n\t[http]\n\t\tsslVerify = true\n\t[http \"https://weak.example.com\"]\n\t\tsslVerify = false\n\nand pretend as if http.sslVerify were set to false when talking to\n\"https://weak.example.com/path\".\n\nIntroduce `urlmatch_config_entry()` wrapper that:\n\n - is called with the target URL (e.g. \"https://weak.example.com/path\"),\n   and the two-level variable parser (e.g. `http_options`);\n\n - uses `url_normalize()` and `match_urls()` to see if configuration\n   data matches the target URL; and\n\n - calls the traditional two-level configuration variable parser\n   only for the configuration data whose <url> part matches the\n   target URL (and if there are multiple matches, only do so if the\n   current match is a better match than the ones previously seen).\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n urlmatch.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n urlmatch.h | 18 +++++++++++++++++\n 2 files changed, 85 insertions(+)\n\ndiff --git a/urlmatch.c b/urlmatch.c\nindex e1b03ee..073fdd3 100644\n--- a/urlmatch.c\n+++ b/urlmatch.c\n@@ -466,3 +466,70 @@ int match_urls(const struct url_info *url,\n \t\t*exactusermatch = usermatched;\n \treturn pathmatchlen;\n }\n+\n+int urlmatch_config_entry(const char *var, const char *value, void *cb)\n+{\n+\tstruct string_list_item *item;\n+\tstruct urlmatch_config *collect = cb;\n+\tstruct urlmatch_item *matched;\n+\tstruct url_info *url = &collect->url;\n+\tconst char *key, *dot;\n+\tstruct strbuf synthkey = STRBUF_INIT;\n+\tsize_t matched_len = 0;\n+\tint user_matched = 0;\n+\tint retval;\n+\n+\tkey = skip_prefix(var, collect->section);\n+\tif (!key || *(key++) != '.') {\n+\t\tif (collect->cascade_fn)\n+\t\t\treturn collect->cascade_fn(var, value, cb);\n+\t\treturn 0; /* not interested */\n+\t}\n+\tdot = strrchr(key, '.');\n+\tif (dot) {\n+\t\tchar *config_url, *norm_url;\n+\t\tstruct url_info norm_info;\n+\n+\t\tconfig_url = xmemdupz(key, dot - key);\n+\t\tnorm_url = url_normalize(config_url, &norm_info);\n+\t\tfree(config_url);\n+\t\tif (!norm_url)\n+\t\t\treturn 0;\n+\t\tmatched_len = match_urls(url, &norm_info, &user_matched);\n+\t\tfree(norm_url);\n+\t\tif (!matched_len)\n+\t\t\treturn 0;\n+\t\tkey = dot + 1;\n+\t}\n+\n+\tif (collect->key && strcmp(key, collect->key))\n+\t\treturn 0;\n+\n+\titem = string_list_insert(&collect->vars, key);\n+\tif (!item->util) {\n+\t\tmatched = xcalloc(1, sizeof(*matched));\n+\t\titem->util = matched;\n+\t} else {\n+\t\tmatched = item->util;\n+\t\t/*\n+\t\t * Is our match shorter?  Is our match the same\n+\t\t * length, and without user while the current\n+\t\t * candidate is with user?  Then we cannot use it.\n+\t\t */\n+\t\tif (matched_len < matched->matched_len ||\n+\t\t    ((matched_len == matched->matched_len) &&\n+\t\t     (!user_matched && matched->user_matched)))\n+\t\t\treturn 0;\n+\t\t/* Otherwise, replace it with this one. */\n+\t}\n+\n+\tmatched->matched_len = matched_len;\n+\tmatched->user_matched = user_matched;\n+\tstrbuf_addstr(&synthkey, collect->section);\n+\tstrbuf_addch(&synthkey, '.');\n+\tstrbuf_addstr(&synthkey, key);\n+\tretval = collect->collect_fn(synthkey.buf, value, collect->cb);\n+\n+\tstrbuf_release(&synthkey);\n+\treturn retval;\n+}\ndiff --git a/urlmatch.h b/urlmatch.h\nindex b67f57f..b461dfd 100644\n--- a/urlmatch.h\n+++ b/urlmatch.h\n@@ -33,4 +33,22 @@ struct url_info {\n extern char *url_normalize(const char *, struct url_info *);\n extern int match_urls(const struct url_info *url, const struct url_info *url_prefix, int *exactusermatch);\n \n+struct urlmatch_item {\n+\tsize_t matched_len;\n+\tchar user_matched;\n+};\n+\n+struct urlmatch_config {\n+\tstruct string_list vars;\n+\tstruct url_info url;\n+\tconst char *section;\n+\tconst char *key;\n+\n+\tvoid *cb;\n+\tint (*collect_fn)(const char *var, const char *value, void *cb);\n+\tint (*cascade_fn)(const char *var, const char *value, void *cb);\n+};\n+\n+extern int urlmatch_config_entry(const char *var, const char *value, void *cb);\n+\n #endif /* URL_MATCH_H */\n-- \n1.8.4-rc0-153-g9820077\n"},{"id":"224376","messageId":"1375298768-7740-5-git-send-email-gitster@pobox.com","threadId":"34576","inReplyTo":"1375298768-7740-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 4/6] config: parse http.<url>.<variable> using urlmatch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-31T19:26:06Z","receivedAt":"2013-07-31T19:26:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"From: \"Kyle J. McKay\" <mackyle@gmail.com>\n\nUse the urlmatch_config_entry() to wrap the underlying\nhttp_options() two-level variable parser in order to set\nhttp.<variable> to the value with the most specific URL in the\nconfiguration.\n\nSigned-off-by: Kyle J. McKay <mackyle@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n .gitignore               |   1 +\n Documentation/config.txt |  44 +++++++++++\n Makefile                 |   7 ++\n http.c                   |  13 +++-\n t/.gitattributes         |   1 +\n t/t5200-url-normalize.sh | 199 +++++++++++++++++++++++++++++++++++++++++++++++\n t/t5200/README           | Bin 0 -> 644 bytes\n t/t5200/config-1         | Bin 0 -> 180 bytes\n t/t5200/config-2         | Bin 0 -> 80 bytes\n t/t5200/config-3         | Bin 0 -> 118 bytes\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     | 137 ++++++++++++++++++++++++++++++++\n 22 files changed, 401 insertions(+), 1 deletion(-)\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\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/Documentation/config.txt b/Documentation/config.txt\nindex 6e53fc5..60c140f 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1513,6 +1513,50 @@ 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 a config key to match a URL, each element of the config key is\n+\tcompared to that of the URL, in the following order:\n++\n+--\n+. Scheme (e.g., `https` in `https://example.com/`). This field\n+  must match exactly between the config key and the URL.\n+\n+. 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+. Port number (e.g., `8080` in `http://example.com:8080/`).\n+  This field must match exactly between the config key and the URL.\n+  Omitted port numbers are automatically converted to the correct\n+  default for the scheme before matching.\n+\n+. Path (e.g., `repo.git` in `https://example.com/repo.git`). The\n+  path field of the config key must match the path field of the URL\n+  either exactly or as a prefix of slash-delimited path elements.  This means\n+  a config key with path `foo/` matches URL path `foo/bar`.  A prefix can only\n+  match on a slash (`/`) boundary.  Longer matches take precedence (so a config\n+  key with path `foo/bar` is a better match to URL path `foo/bar` than a config\n+  key with just path `foo/`).\n+\n+. User name (e.g., `user` in `https://user@example.com/repo.git`). If\n+  the config key has a user name it must match the user name in the\n+  URL exactly. If the config key does not have a user name, that\n+  config key will match a URL with any user name (including none).\n+--\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 user name. For example,\n+if the URL is `https://user@example.com/foo/bar` a config key match of\n+`https://example.com/foo` will be preferred over a config key match of\n+`https://user@example.com`.\n++\n+All URLs are normalized before attempting any matching (the password part,\n+if embedded in the URL, is always ignored for matching purposes) so that\n+equivalent urls that are simply spelled differently will match properly.\n+Environment variable settings always override any matches.  The urls that are\n+matched against are those given directly to Git commands.  This means any URLs\n+visited as a result of a redirection do not participate in matching.\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/Makefile b/Makefile\nindex 0f931a2..ea3edba 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@@ -721,6 +722,7 @@ LIB_H += tree-walk.h\n LIB_H += tree.h\n LIB_H += unpack-trees.h\n LIB_H += url.h\n+LIB_H += urlmatch.h\n LIB_H += userdiff.h\n LIB_H += utf8.h\n LIB_H += varint.h\n@@ -868,6 +870,7 @@ LIB_OBJS += tree.o\n LIB_OBJS += tree-walk.o\n LIB_OBJS += unpack-trees.o\n LIB_OBJS += url.o\n+LIB_OBJS += urlmatch.o\n LIB_OBJS += usage.o\n LIB_OBJS += userdiff.o\n LIB_OBJS += utf8.o\n@@ -2235,6 +2238,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/http.c b/http.c\nindex 37986f8..5eda356 100644\n--- a/http.c\n+++ b/http.c\n@@ -3,6 +3,7 @@\n #include \"sideband.h\"\n #include \"run-command.h\"\n #include \"url.h\"\n+#include \"urlmatch.h\"\n #include \"credential.h\"\n #include \"version.h\"\n #include \"pkt-line.h\"\n@@ -334,10 +335,20 @@ 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 *normalized_url;\n+\tstruct urlmatch_config config = { STRING_LIST_INIT_DUP };\n+\n+\tconfig.section = \"http\";\n+\tconfig.key = NULL;\n+\tconfig.collect_fn = http_options;\n+\tconfig.cascade_fn = git_default_config;\n+\tconfig.cb = NULL;\n \n \thttp_is_verbose = 0;\n+\tnormalized_url = url_normalize(url, &config.url);\n \n-\tgit_config(http_options, NULL);\n+\tgit_config(urlmatch_config_entry, &config);\n+\tfree(normalized_url);\n \n \tcurl_global_init(CURL_GLOBAL_ALL);\n \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..f79bb0f\n--- /dev/null\n+++ b/t/t5200-url-normalize.sh\n@@ -0,0 +1,199 @@\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+\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/README b/t/t5200/README\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..e3a67d94fb52201b565884daf050f7f76294a4f3\nGIT binary patch\nliteral 644\nzcmaKqxl+V142F9@g&u(2St@FJDrTt}C6;3izD9QT;OUW_WmpPmGULekk54&zf>i=o\nzYzU46Rp128a|O#nbIWptIj4sD`t9|l!kL?g*`wgxNU9mv2?WzZaJU>EcZbWP158#=\nzPfkzHLCesnHWel)k_!o>ED-~LV&j}lcCe-*tVUCyJN>;e&rm676mWFDn`}YvJ+}-f\nz1oeYUA=`aDg<|lO*>-0Yj}>H1iPJLTj9lE*!l~DB<58ij^lClzUt9#WkEjYJm`DW;\nz#fhv{;~SOnd7Xues$_wZMGJD^cD<P?TgS`){Dq28rPTT+^!BR83auKgHz`n*s+Q9g\nzd~4-BmobE@%sp<*9VZO1P2hxKRm3B-=?TgH4zu-*__O(#702kAlhVaVyQB}Ro0+?t\nkf;N@o8n6*~JaxDT^{U$O0hW*_weP>ge&O#H1FIUFFGl3$z5oCK\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/config-1 b/t/t5200/config-1\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..8aaf23c41c52c28bea4da92ba172c54e8d2a4926\nGIT binary patch\nliteral 180\nzcma#fC@Cq3<>D+YPAy7IPt7Y)uvN$}$w)2I1@pK#^YUE-g2RBKB}JvFT+txq3Q8cd\nz*h*hNwIVUMASYEXIX_nk%@C**%$VZhoUqiQ%(P0NNok2W#rTZUFGwuOKsOI01~m)-\nD(n&qZ\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/config-2 b/t/t5200/config-2\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..749f4bd5c4e93b6f0e8a18b3efe794fb3b0b5832\nGIT binary patch\nliteral 80\nzcma#fC@CpWPy&&~R{Hv>6^Xe8IjMTd`MLT9i6t3Iv0R*`#i>P!>8W`o3bqPRd0jA{\nXi?g^mCoHunGp!ORm6n)OoXQ0NccUB6\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t5200/config-3 b/t/t5200/config-3\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..5c8d3e85dda86e4e2e36d362a0d16400509a45ea\nGIT binary patch\nliteral 118\nzcma#fC@CpWPy&&~R{Hv>6^Xe8IjMTd`MLT9i6t5Od5O8HO0is=#l<<viJ5t6`3klQ\naX_+~xx`tfQs9H;lQ;QtX^<&j)#03D2JSaT?\n\nliteral 0\nHcmV?d00001\n\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..81d3da9\n--- /dev/null\n+++ b/test-url-normalize.c\n@@ -0,0 +1,137 @@\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 struct url_info *info)\n+{\n+\tstruct strbuf opt_lc;\n+\tsize_t i, len;\n+\tstruct urlmatch_config config = { STRING_LIST_INIT_DUP };\n+\n+\tmemcpy(&config.url, info, sizeof(*info));\n+\tconfig.section = \"http\";\n+\tconfig.collect_fn = http_options;\n+\tconfig.cascade_fn = git_default_config;\n+\tconfig.cb = NULL;\n+\n+\tif (git_config_with_options(urlmatch_config_entry, &config, 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+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\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)info.url_len);\n+\t\tif (opt_c)\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], NULL);\n+\turl2 = url_normalize(argv[2], NULL);\n+\treturn (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1;\n+}\n+\n+#endif /* !NO_CURL */\n-- \n1.8.4-rc0-153-g9820077\n"},{"id":"224379","messageId":"1375298768-7740-6-git-send-email-gitster@pobox.com","threadId":"34576","inReplyTo":"1375298768-7740-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 5/6] builtin/config: refactor collect_config()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-31T19:26:07Z","receivedAt":"2013-07-31T19:26:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"In order to reuse the logic to format the configuration value while\nhonouring the requested type, split this function into two.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/config.c | 42 +++++++++++++++++++++---------------------\n 1 file changed, 21 insertions(+), 21 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 33c9bf9..12c5073 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -100,25 +100,13 @@ struct strbuf_list {\n \tint alloc;\n };\n \n-static int collect_config(const char *key_, const char *value_, void *cb)\n+static int format_config(struct strbuf *buf, const char *key_, const char *value_)\n {\n-\tstruct strbuf_list *values = cb;\n-\tstruct strbuf *buf;\n-\tchar value[256];\n-\tconst char *vptr = value;\n \tint must_free_vptr = 0;\n \tint must_print_delim = 0;\n+\tchar value[256];\n+\tconst char *vptr = value;\n \n-\tif (!use_key_regexp && strcmp(key_, key))\n-\t\treturn 0;\n-\tif (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))\n-\t\treturn 0;\n-\tif (regexp != NULL &&\n-\t    (do_not_match ^ !!regexec(regexp, (value_?value_:\"\"), 0, NULL, 0)))\n-\t\treturn 0;\n-\n-\tALLOC_GROW(values->items, values->nr + 1, values->alloc);\n-\tbuf = &values->items[values->nr++];\n \tstrbuf_init(buf, 0);\n \n \tif (show_keys) {\n@@ -126,7 +114,7 @@ static int collect_config(const char *key_, const char *value_, void *cb)\n \t\tmust_print_delim = 1;\n \t}\n \tif (types == TYPE_INT)\n-\t\tsprintf(value, \"%d\", git_config_int(key_, value_?value_:\"\"));\n+\t\tsprintf(value, \"%d\", git_config_int(key_, value_ ? value_ : \"\"));\n \telse if (types == TYPE_BOOL)\n \t\tvptr = git_config_bool(key_, value_) ? \"true\" : \"false\";\n \telse if (types == TYPE_BOOL_OR_INT) {\n@@ -154,15 +142,27 @@ static int collect_config(const char *key_, const char *value_, void *cb)\n \tstrbuf_addch(buf, term);\n \n \tif (must_free_vptr)\n-\t\t/* If vptr must be freed, it's a pointer to a\n-\t\t * dynamically allocated buffer, it's safe to cast to\n-\t\t * const.\n-\t\t*/\n \t\tfree((char *)vptr);\n-\n \treturn 0;\n }\n \n+static int collect_config(const char *key_, const char *value_, void *cb)\n+{\n+\tstruct strbuf_list *values = cb;\n+\n+\tif (!use_key_regexp && strcmp(key_, key))\n+\t\treturn 0;\n+\tif (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))\n+\t\treturn 0;\n+\tif (regexp != NULL &&\n+\t    (do_not_match ^ !!regexec(regexp, (value_?value_:\"\"), 0, NULL, 0)))\n+\t\treturn 0;\n+\n+\tALLOC_GROW(values->items, values->nr + 1, values->alloc);\n+\n+\treturn format_config(&values->items[values->nr++], key_, value_);\n+}\n+\n static int get_value(const char *key_, const char *regex_)\n {\n \tint ret = CONFIG_GENERIC_ERROR;\n-- \n1.8.4-rc0-153-g9820077\n"},{"id":"224380","messageId":"1375298768-7740-7-git-send-email-gitster@pobox.com","threadId":"34576","inReplyTo":"1375298768-7740-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 6/6] config: \"git config --get-urlmatch\" parses section.<url>.key","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-31T19:26:08Z","receivedAt":"2013-07-31T19:26:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Using the same urlmatch_config_entry() infrastructure, add a new\nmode \"--get-urlmatch\" to the \"git config\" command, to learn values\nfor the \"virtual\" two-level variables customized for the specific\nURL.\n\n    git config [--<type>] --get-urlmatch <section>[.<key>] <url>\n\nWith <section>.<key> fully specified, the configuration data for\n<section>.<urlpattern>.<key> for <urlpattern> that best matches the\ngiven <url> is sought (and if not found, <section>.<key> is used)\nand reported.  For example, with this configuration:\n\n    [http]\n        sslVerify\n    [http \"https://weak.example.com\"]\n        cookieFile = /tmp/cookie.txt\n        sslVerify = false\n\nYou would get\n\n    $ git config --bool --get-urlmatch http.sslVerify https://good.example.com\n    true\n    $ git config --bool --get-urlmatch http.sslVerify https://weak.example.com\n    false\n\nWith only <section> specified, you can get a list of all variables\nin the section with their values that apply to the given URL.  E.g\n\n    $ git config --get-urlmatch http https://weak.example.com\n    http.cookiefile /tmp/cookie.txt\n    http.sslverify false\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-config.txt | 29 ++++++++++++++\n builtin/config.c             | 92 ++++++++++++++++++++++++++++++++++++++++++++\n t/t1300-repo-config.sh       | 25 ++++++++++++\n 3 files changed, 146 insertions(+)\n\ndiff --git a/Documentation/git-config.txt b/Documentation/git-config.txt\nindex d88a6fc..b48e2ec 100644\n--- a/Documentation/git-config.txt\n+++ b/Documentation/git-config.txt\n@@ -15,6 +15,7 @@ SYNOPSIS\n 'git config' [<file-option>] [type] [-z|--null] --get name [value_regex]\n 'git config' [<file-option>] [type] [-z|--null] --get-all name [value_regex]\n 'git config' [<file-option>] [type] [-z|--null] --get-regexp name_regex [value_regex]\n+'git config' [<file-option>] [type] [-z|--null] --get-urlmatch name URL\n 'git config' [<file-option>] --unset name [value_regex]\n 'git config' [<file-option>] --unset-all name [value_regex]\n 'git config' [<file-option>] --rename-section old_name new_name\n@@ -95,6 +96,14 @@ OPTIONS\n \tin which section and variable names are lowercased, but subsection\n \tnames are not.\n \n+--get-urlmatch name URL::\n+\tWhen given a two-part name section.key, the value for\n+\tsection.<url>.key whose <url> part matches the best to the\n+\tgiven URL is returned (if no such key exists, the value for\n+\tsection.key is used as a fallback).  When given just the\n+\tsection as name, do so for all the keys in the section and\n+\tlist them.\n+\n --global::\n \tFor writing options: write to global ~/.gitconfig file rather than\n \tthe repository .git/config, write to $XDG_CONFIG_HOME/git/config file\n@@ -273,6 +282,13 @@ Given a .git/config like this:\n \t\tgitproxy=proxy-command for kernel.org\n \t\tgitproxy=default-proxy ; for all the rest\n \n+\t; HTTP\n+\t[http]\n+\t\tsslVerify\n+\t[http \"https://weak.example.com\"]\n+\t\tsslVerify = false\n+\t\tcookieFile = /tmp/cookie.txt\n+\n you can set the filemode to true with\n \n ------------\n@@ -358,6 +374,19 @@ RESET=$(git config --get-color \"\" \"reset\")\n echo \"${WS}your whitespace color or blue reverse${RESET}\"\n ------------\n \n+For URLs in `https://weak.example.com`, `http.sslVerify` is set to\n+false, while it is set to `true` for all others:\n+\n+------------\n+% git config --bool --get-urlmatch http.sslverify https://good.example.com\n+true\n+% git config --bool --get-urlmatch http.sslverify https://weak.example.com\n+false\n+% git config --get-urlmatch http https://weak.example.com\n+http.cookiefile /tmp/cookie.txt\n+http.sslverify false\n+------------\n+\n include::config.txt[]\n \n GIT\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 12c5073..c35c5be 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -2,6 +2,7 @@\n #include \"cache.h\"\n #include \"color.h\"\n #include \"parse-options.h\"\n+#include \"urlmatch.h\"\n \n static const char *const builtin_config_usage[] = {\n \tN_(\"git config [options]\"),\n@@ -41,6 +42,7 @@ static int respect_includes = -1;\n #define ACTION_SET_ALL (1<<12)\n #define ACTION_GET_COLOR (1<<13)\n #define ACTION_GET_COLORBOOL (1<<14)\n+#define ACTION_GET_URLMATCH (1<<15)\n \n #define TYPE_BOOL (1<<0)\n #define TYPE_INT (1<<1)\n@@ -57,6 +59,7 @@ static struct option builtin_config_options[] = {\n \tOPT_BIT(0, \"get\", &actions, N_(\"get value: name [value-regex]\"), ACTION_GET),\n \tOPT_BIT(0, \"get-all\", &actions, N_(\"get all values: key [value-regex]\"), ACTION_GET_ALL),\n \tOPT_BIT(0, \"get-regexp\", &actions, N_(\"get values for regexp: name-regex [value-regex]\"), ACTION_GET_REGEXP),\n+\tOPT_BIT(0, \"get-urlmatch\", &actions, N_(\"get value specific for the URL: section[.var] URL\"), ACTION_GET_URLMATCH),\n \tOPT_BIT(0, \"replace-all\", &actions, N_(\"replace all matching variables: name value [value_regex]\"), ACTION_REPLACE_ALL),\n \tOPT_BIT(0, \"add\", &actions, N_(\"add a new variable: name value\"), ACTION_ADD),\n \tOPT_BIT(0, \"unset\", &actions, N_(\"remove a variable: name [value-regex]\"), ACTION_UNSET),\n@@ -348,6 +351,91 @@ static int get_colorbool(int print)\n \t\treturn get_colorbool_found ? 0 : 1;\n }\n \n+struct urlmatch_current_candidate_value {\n+\tchar value_is_null;\n+\tstruct strbuf value;\n+};\n+\n+static int urlmatch_collect_fn(const char *var, const char *value, void *cb)\n+{\n+\tstruct string_list *values = cb;\n+\tstruct string_list_item *item = string_list_insert(values, var);\n+\tstruct urlmatch_current_candidate_value *matched = item->util;\n+\n+\tif (!matched) {\n+\t\tmatched = xmalloc(sizeof(*matched));\n+\t\tstrbuf_init(&matched->value, 0);\n+\t\titem->util = matched;\n+\t} else {\n+\t\tstrbuf_reset(&matched->value);\n+\t}\n+\n+\tif (value) {\n+\t\tstrbuf_addstr(&matched->value, value);\n+\t\tmatched->value_is_null = 0;\n+\t} else {\n+\t\tmatched->value_is_null = 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int get_urlmatch(const char *var, const char *url)\n+{\n+\tconst char *section_tail;\n+\tstruct string_list_item *item;\n+\tstruct urlmatch_config config = { STRING_LIST_INIT_DUP };\n+\tstruct string_list values = STRING_LIST_INIT_DUP;\n+\n+\tconfig.collect_fn = urlmatch_collect_fn;\n+\tconfig.cascade_fn = NULL;\n+\tconfig.cb = &values;\n+\n+\tif (!url_normalize(url, &config.url))\n+\t\tdie(config.url.err);\n+\n+\tsection_tail = strchr(var, '.');\n+\tif (section_tail) {\n+\t\tconfig.section = xmemdupz(var, section_tail - var);\n+\t\tconfig.key = strrchr(var, '.') + 1;\n+\t\tshow_keys = 0;\n+\t} else {\n+\t\tconfig.section = var;\n+\t\tconfig.key = NULL;\n+\t\tshow_keys = 1;\n+\t}\n+\n+\tgit_config_with_options(urlmatch_config_entry, &config,\n+\t\t\t\tgiven_config_file, respect_includes);\n+\n+\tfor_each_string_list_item(item, &values) {\n+\t\tstruct urlmatch_current_candidate_value *matched = item->util;\n+\t\tstruct strbuf key = STRBUF_INIT;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tstrbuf_addstr(&key, item->string);\n+\t\tformat_config(&buf, key.buf,\n+\t\t\t      matched->value_is_null ? NULL : matched->value.buf);\n+\t\tfwrite(buf.buf, 1, buf.len, stdout);\n+\t\tstrbuf_release(&key);\n+\t\tstrbuf_release(&buf);\n+\n+\t\tstrbuf_release(&matched->value);\n+\t}\n+\tstring_list_clear(&config.vars, 1);\n+\tstring_list_clear(&values, 1);\n+\tfree(config.url.url);\n+\n+\t/*\n+\t * section name may have been copied to replace the dot, in which\n+\t * case it needs to be freed.  key name is either NULL (e.g. 'http'\n+\t * alone) or points into var (e.g. 'http.savecookies'), and we do\n+\t * not own the storage.\n+\t */\n+\tif (config.section != var)\n+\t\tfree((void *)config.section);\n+\treturn 0;\n+}\n+\n int cmd_config(int argc, const char **argv, const char *prefix)\n {\n \tint nongit = !startup_info->have_repository;\n@@ -499,6 +587,10 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \t\tcheck_argc(argc, 1, 2);\n \t\treturn get_value(argv[0], argv[1]);\n \t}\n+\telse if (actions == ACTION_GET_URLMATCH) {\n+\t\tcheck_argc(argc, 2, 2);\n+\t\treturn get_urlmatch(argv[0], argv[1]);\n+\t}\n \telse if (actions == ACTION_UNSET) {\n \t\tcheck_argc(argc, 1, 2);\n \t\tif (argc == 2)\ndiff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh\nindex c4a7d84..323e880 100755\n--- a/t/t1300-repo-config.sh\n+++ b/t/t1300-repo-config.sh\n@@ -1087,6 +1087,31 @@ test_expect_success 'barf on incomplete string' '\n \tgrep \" line 3 \" error\n '\n \n+test_expect_success 'urlmatch' '\n+\tcat >.git/config <<-\\EOF &&\n+\t[http]\n+\t\tsslVerify\n+\t[http \"https://weak.example.com\"]\n+\t\tsslVerify = false\n+\t\tcookieFile = /tmp/cookie.txt\n+\tEOF\n+\n+\techo true >expect &&\n+\tgit config --bool --get-urlmatch http.sslverify https://good.example.com >actual &&\n+\ttest_cmp expect actual &&\n+\n+\techo false >expect &&\n+\tgit config --bool --get-urlmatch http.sslverify https://weak.example.com >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t{\n+\t\techo http.cookiefile /tmp/cookie.txt &&\n+\t\techo http.sslverify false\n+\t} >expect &&\n+\tgit config --get-urlmatch http https://weak.example.com >actual &&\n+\ttest_cmp expect actual\n+'\n+\n # good section hygiene\n test_expect_failure 'unsetting the last key in a section removes header' '\n \tcat >.git/config <<-\\EOF &&\n-- \n1.8.4-rc0-153-g9820077\n"},{"id":"224387","messageId":"AF82C57D-BE60-420C-AE62-13907D8115C7@gmail.com","threadId":"34576","inReplyTo":"1375298768-7740-3-git-send-email-gitster@pobox.com","subject":"Re: [PATCH v6 2/6] config: add helper to normalize and match URLs","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-31T20:50:58Z","receivedAt":"2013-07-31T20:50:58Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"On Jul 31, 2013, at 12:26, Junio C Hamano wrote:\n\n> From: \"Kyle J. McKay\" <mackyle@gmail.com>\n>\n> Some http.* configuration variables need to take values customized\n> for the URL we are talking to.  We may want to set http.sslVerify to\n> true in general but to false only for a certain site, for example,\n> with a configuration file like this:\n[...]\n> urlmatch.c | 468 ++++++++++++++++++++++++++++++++++++++++++++++++++++ \n> +++++++++\n> urlmatch.h |  36 +++++\n> 2 files changed, 504 insertions(+)\n> create mode 100644 urlmatch.c\n> create mode 100644 urlmatch.h\n>\n> diff --git a/urlmatch.c b/urlmatch.c\n> new file mode 100644\n> index 0000000..e1b03ee\n> --- /dev/null\n> +++ b/urlmatch.c\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> +{\n> +\t/*\n> +\t * url_prefix matches url if url_prefix is an exact match for url  \n> or it\n> +\t * is a prefix of url and the match ends on a path component  \n> boundary.\n> +\t * Both url and url_prefix are considered to have an implicit '/'  \n> on the\n> +\t * end for matching purposes if they do not already.\n\nThis function should probably be renamed to just url_match_prefix  \nsince it isn't part of nor does it depend on the http_options related  \nfiles or functions anymore.\n\nOtherwise looks good to me.\n"},{"id":"224388","messageId":"9DDDFC61-2946-4828-AD9E-52D2F0154479@gmail.com","threadId":"34576","inReplyTo":"1375298768-7740-5-git-send-email-gitster@pobox.com","subject":"Re: [PATCH v6 4/6] config: parse http.<url>.<variable> using urlmatch","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-31T20:51:16Z","receivedAt":"2013-07-31T20:51:16Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"On Jul 31, 2013, at 12:26, Junio C Hamano wrote:\n\n> From: \"Kyle J. McKay\" <mackyle@gmail.com>\n>\n> Use the urlmatch_config_entry() to wrap the underlying\n> http_options() two-level variable parser in order to set\n> http.<variable> to the value with the most specific URL in the\n> configuration.\n>\n> Signed-off-by: Kyle J. McKay <mackyle@gmail.com>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n\nNeeds Peff's Signed-off-by: for the copious amount of text he wrote  \nthat is included verbatim in the documentation part of the patch.  He  \npreviously gave it for this purpose.\n\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index 6e53fc5..60c140f 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -1513,6 +1513,50 @@ http.useragent::\n> \tof common USER_AGENT strings (but not including those like git/ \n> 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  \n> urls.\n> +\tFor a config key to match a URL, each element of the config key is\n> +\tcompared to that of the URL, in the following order:\n> ++\n> +--\n> +. Scheme (e.g., `https` in `https://example.com/`). This field\n> +  must match exactly between the config key and the URL.\n> +\n> +. 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> +. Port number (e.g., `8080` in `http://example.com:8080/`).\n> +  This field must match exactly between the config key and the URL.\n> +  Omitted port numbers are automatically converted to the correct\n> +  default for the scheme before matching.\n> +\n> +. Path (e.g., `repo.git` in `https://example.com/repo.git`). The\n> +  path field of the config key must match the path field of the URL\n> +  either exactly or as a prefix of slash-delimited path elements.   \n> This means\n> +  a config key with path `foo/` matches URL path `foo/bar`.  A  \n> prefix can only\n> +  match on a slash (`/`) boundary.  Longer matches take precedence  \n> (so a config\n> +  key with path `foo/bar` is a better match to URL path `foo/bar`  \n> than a config\n> +  key with just path `foo/`).\n> +\n> +. User name (e.g., `user` in `https://user@example.com/repo.git`). If\n> +  the config key has a user name it must match the user name in the\n> +  URL exactly. If the config key does not have a user name, that\n> +  config key will match a URL with any user name (including none).\n\nMissing the single line follow-up patch:\n\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index 0dd5566..f2ed9ef 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -1568,7 +1568,8 @@ http.<url>.*::\n> . User name (e.g., `user` in `https://user@example.com/repo.git`). If\n>   the config key has a user name it must match the user name in the\n>   URL exactly. If the config key does not have a user name, that\n> -  config key will match a URL with any user name (including none).\n> +  config key will match a URL with any user name (including none),\n> +  but at a lower precedence than a config key with a user name.\n> --\n\n\n\n> diff --git a/test-url-normalize.c b/test-url-normalize.c\n> new file mode 100644\n> index 0000000..81d3da9\n> --- /dev/null\n> +++ b/test-url-normalize.c\n> @@ -0,0 +1,137 @@\n> +#ifdef NO_CURL\n> +\n> +int main()\n\nNeed's Ramsey's patch here:\n\n-int main()\n+int main(void)\n\n> +static int run_http_options(const char *file,\n> +\t\t\t    const char *opt,\n> +\t\t\t    const struct url_info *info)\n> +{\n> +\tstruct strbuf opt_lc;\n> +\tsize_t i, len;\n> +\tstruct urlmatch_config config = { STRING_LIST_INIT_DUP };\n> +\n> +\tmemcpy(&config.url, info, sizeof(*info));\n> +\tconfig.section = \"http\";\n> +\tconfig.collect_fn = http_options;\n> +\tconfig.cascade_fn = git_default_config;\n> +\tconfig.cb = NULL;\n> +\n> +\tif (git_config_with_options(urlmatch_config_entry, &config, file,  \n> 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\nAnd here\n\n+#ifdef USE_CURL_MULTI\n> +\telse if (!strcmp(\"maxrequests\", opt_lc.buf))\n> +\t\tprintf(\"%d\\n\", max_requests);\n+#endif\n> +\telse if (!strcmp(\"lowspeedlimit\", opt_lc.buf))\n> +\t\tprintf(\"%ld\\n\", curl_low_speed_limit);\n\nOtherwise looks good to me.\n"},{"id":"224389","messageId":"72dacc4d84caf9831f5e12ece7f38f2@f74d39fa044aa309eaea14b9f57fe79","threadId":"34576","inReplyTo":"1375298768-7740-3-git-send-email-gitster@pobox.com","subject":"[PATCH ALTERNATIVE v6 0/2] http.<url>.<key> and friends","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-31T20:51:59Z","receivedAt":"2013-07-31T20:51:59Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"This patch simply provides two alternative versions of the 2/6 and 4/6\npatches previously sent as part of the:\n\n  [PATCH v6 0/6] http.<url>.<key> and friends\n\nseries.  They are intended simply as complete alternatives to parts 2 and 4\nthat include the following changes:\n\n2/4 - Include 1-line documentation update in log comment and rename static\n      function from http_options_url_match_prefix to url_match_prefix.\n      \n4/4 - Include 1-line documentation update together with Peff's previously\n      provided Signed-off-by for the copious amount of documentation text he\n      has provided that has been included verbatim.  Also include the minor\n      fixes from Ramsay Jones for compilation of test-url-normalize when\n      NO_CURL is defined.\n\nIf simply squashing the patches in instead is desired, here's the patch for\npart 2/4:\n\ndiff --git a/urlmatch.c b/urlmatch.c\nindex e1b03ee7..4f38cc7b 100644\n--- a/urlmatch.c\n+++ b/urlmatch.c\n@@ -376,9 +376,9 @@ char *url_normalize(const char *url, struct url_info *out_info)\n \treturn result;\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+static size_t url_match_prefix(const char *url,\n+\t\t\t       const char *url_prefix,\n+\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@@ -457,7 +457,7 @@ int match_urls(const struct url_info *url,\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+\tpathmatchlen = 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\nAnd here's the patch for part 4/4:\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 60c140f1..8cc0fd78 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1542,6 +1542,8 @@ http.<url>.*::\n   the config key has a user name it must match the user name in the\n   URL exactly. If the config key does not have a user name, that\n   config key will match a URL with any user name (including none).\n+  config key will match a URL with any user name (including none),\n+  but at a lower precedence than a config key with a user name.\n --\n +\n The list above is ordered by decreasing precedence; a URL that matches\ndiff --git a/test-url-normalize.c b/test-url-normalize.c\nindex 81d3da90..80437217 100644\n--- a/test-url-normalize.c\n+++ b/test-url-normalize.c\n@@ -1,6 +1,6 @@\n #ifdef NO_CURL\n \n-int main()\n+int main(void)\n {\n \treturn 125;\n }\n@@ -52,8 +52,10 @@ static int run_http_options(const char *file,\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+#ifdef USE_CURL_MULTI\n \telse if (!strcmp(\"maxrequests\", opt_lc.buf))\n \t\tprintf(\"%d\\n\", max_requests);\n+#endif\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---\n"},{"id":"224391","messageId":"00149fa65749f81830e791397e4f492@f74d39fa044aa309eaea14b9f57fe79","threadId":"34576","inReplyTo":"72dacc4d84caf9831f5e12ece7f38f2@f74d39fa044aa309eaea14b9f57fe79","subject":"[PATCH ALTERNATIVE v6 2/4] config: add helper to normalize and match URLs","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-31T20:52:00Z","receivedAt":"2013-07-31T20:52:00Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"Some http.* configuration variables need to take values customized\nfor the URL we are talking to.  We may want to set http.sslVerify to\ntrue in general but to false only for a certain site, for example,\nwith a configuration file like this:\n\n\t[http]\n\t\tsslVerify = true\n\t[http \"https://weak.example.com\"]\n\t\tsslVerify = false\n\nand let the configuration machinery pick up the latter only when\ntalking to \"https://weak.example.com\".  The latter needs to kick in\nnot only when the URL is exactly \"https://weak.example.com\", but\nalso is anything that \"match\" it, e.g.\n\n\thttps://weak.example.com/test\n\thttps://me@weak.example.com/test\n\nThe <url> in the configuration key consists of the following parts,\nand is considered a match to the URL we are attempting to access\nunder certain conditions:\n\n  . Scheme (e.g., `https` in `https://example.com/`). This field\n    must match exactly between the config key and the URL.\n\n  . 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  . Port number (e.g., `8080` in `http://example.com:8080/`).  This\n    field must match exactly between the config key and the URL.\n    Omitted port numbers are automatically converted to the correct\n    default for the scheme before matching.\n\n  . Path (e.g., `repo.git` in `https://example.com/repo.git`). The\n    path field of the config key must match the path field of the\n    URL either exactly or as a prefix of slash-delimited path\n    elements.  A config key with path `foo/` matches URL path\n    `foo/bar`.  A prefix can only match on a slash (`/`) boundary.\n    Longer matches take precedence (so a config key with path\n    `foo/bar` is a better match to URL path `foo/bar` than a config\n    key with just path `foo/`).\n\n  . User name (e.g., `me` in `https://me@example.com/repo.git`). If\n    the config key has a user name, it must match the user name in\n    the URL exactly. If the config key does not have a user name,\n    that config key will match a URL with any user name (including\n    none), but at a lower precedence than a config key with a user\n    name.\n\nLonger matches take precedence over shorter matches.\n\nThis step adds two helper functions `url_normalize()` and\n`match_urls()` to help implement the above semantics. The\nnormalization rules are based on RFC 3986 and should result in any\ntwo equivalent urls being a match.\n\nSigned-off-by: Kyle J. McKay <mackyle@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n urlmatch.c | 468 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n urlmatch.h |  36 +++++\n 2 files changed, 504 insertions(+)\n create mode 100644 urlmatch.c\n create mode 100644 urlmatch.h\n\ndiff --git a/urlmatch.c b/urlmatch.c\nnew file mode 100644\nindex 00000000..4f38cc7b\n--- /dev/null\n+++ b/urlmatch.c\n@@ -0,0 +1,468 @@\n+#include \"cache.h\"\n+#include \"urlmatch.h\"\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+char *url_normalize(const char *url, struct url_info *out_info)\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 * 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+\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+\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+\tchar *result;\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\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+\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\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+\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\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\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 (!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+\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\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\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\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+\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_off = norm.len;\n+\tpath_start = norm.buf + path_off;\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\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\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\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+\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+\tpath_len = norm.len - path_off;\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\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+\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 url_match_prefix(const char *url,\n+\t\t\t       const char *url_prefix,\n+\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 (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+\tif (!url || !url_prefix)\n+\t\treturn 0;\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 (strncmp(url, url_prefix, url_prefix_len))\n+\t\treturn 0;\n+\tif ((strlen(url) == url_prefix_len) || (url[url_prefix_len] == '/'))\n+\t\treturn url_prefix_len + 1;\n+\treturn 0;\n+}\n+\n+int match_urls(const struct url_info *url,\n+\t       const struct url_info *url_prefix,\n+\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 = 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+}\ndiff --git a/urlmatch.h b/urlmatch.h\nnew file mode 100644\nindex 00000000..b67f57f8\n--- /dev/null\n+++ b/urlmatch.h\n@@ -0,0 +1,36 @@\n+#ifndef URL_MATCH_H\n+#include \"string-list.h\"\n+\n+struct url_info {\n+\t/* normalized url on success, must be freed, otherwise NULL */\n+\tchar *url;\n+\t/* if !url, a brief reason for the failure, otherwise NULL */\n+\tconst char *err;\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+extern char *url_normalize(const char *, struct url_info *);\n+extern int match_urls(const struct url_info *url, const struct url_info *url_prefix, int *exactusermatch);\n+\n+#endif /* URL_MATCH_H */\n-- \n1.8.3\n"},{"id":"224390","messageId":"bf9bd095c5098d39fd670d21f42044b@f74d39fa044aa309eaea14b9f57fe79","threadId":"34576","inReplyTo":"72dacc4d84caf9831f5e12ece7f38f2@f74d39fa044aa309eaea14b9f57fe79","subject":"[PATCH ALTERNATIVE v6 4/4] config: parse http.<url>.<variable> using urlmatch","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-31T20:52:01Z","receivedAt":"2013-07-31T20:52:01Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"Use the urlmatch_config_entry() to wrap the underlying\nhttp_options() two-level variable parser in order to set\nhttp.<variable> to the value with the most specific URL in the\nconfiguration.\n\nSigned-off-by: Jeff King <peff@peff.net>\nSigned-off-by: Kyle J. McKay <mackyle@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n .gitignore               |   1 +\n Documentation/config.txt |  46 +++++++++++\n Makefile                 |   7 ++\n http.c                   |  13 +++-\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     | 139 +++++++++++++++++++++++++++++++++\n 22 files changed, 438 insertions(+), 1 deletion(-)\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\ndiff --git a/.gitignore b/.gitignore\nindex 6669bf0c..cd97e16a 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/Documentation/config.txt b/Documentation/config.txt\nindex 6e53fc50..8cc0fd78 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1513,6 +1513,52 @@ 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 a config key to match a URL, each element of the config key is\n+\tcompared to that of the URL, in the following order:\n++\n+--\n+. Scheme (e.g., `https` in `https://example.com/`). This field\n+  must match exactly between the config key and the URL.\n+\n+. 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+. Port number (e.g., `8080` in `http://example.com:8080/`).\n+  This field must match exactly between the config key and the URL.\n+  Omitted port numbers are automatically converted to the correct\n+  default for the scheme before matching.\n+\n+. Path (e.g., `repo.git` in `https://example.com/repo.git`). The\n+  path field of the config key must match the path field of the URL\n+  either exactly or as a prefix of slash-delimited path elements.  This means\n+  a config key with path `foo/` matches URL path `foo/bar`.  A prefix can only\n+  match on a slash (`/`) boundary.  Longer matches take precedence (so a config\n+  key with path `foo/bar` is a better match to URL path `foo/bar` than a config\n+  key with just path `foo/`).\n+\n+. User name (e.g., `user` in `https://user@example.com/repo.git`). If\n+  the config key has a user name it must match the user name in the\n+  URL exactly. If the config key does not have a user name, that\n+  config key will match a URL with any user name (including none).\n+  config key will match a URL with any user name (including none),\n+  but at a lower precedence than a config key with a user name.\n+--\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 user name. For example,\n+if the URL is `https://user@example.com/foo/bar` a config key match of\n+`https://example.com/foo` will be preferred over a config key match of\n+`https://user@example.com`.\n++\n+All URLs are normalized before attempting any matching (the password part,\n+if embedded in the URL, is always ignored for matching purposes) so that\n+equivalent urls that are simply spelled differently will match properly.\n+Environment variable settings always override any matches.  The urls that are\n+matched against are those given directly to Git commands.  This means any URLs\n+visited as a result of a redirection do not participate in matching.\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/Makefile b/Makefile\nindex 0f931a20..ea3edbae 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@@ -721,6 +722,7 @@ LIB_H += tree-walk.h\n LIB_H += tree.h\n LIB_H += unpack-trees.h\n LIB_H += url.h\n+LIB_H += urlmatch.h\n LIB_H += userdiff.h\n LIB_H += utf8.h\n LIB_H += varint.h\n@@ -868,6 +870,7 @@ LIB_OBJS += tree.o\n LIB_OBJS += tree-walk.o\n LIB_OBJS += unpack-trees.o\n LIB_OBJS += url.o\n+LIB_OBJS += urlmatch.o\n LIB_OBJS += usage.o\n LIB_OBJS += userdiff.o\n LIB_OBJS += utf8.o\n@@ -2235,6 +2238,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/http.c b/http.c\nindex 37986f82..5eda356f 100644\n--- a/http.c\n+++ b/http.c\n@@ -3,6 +3,7 @@\n #include \"sideband.h\"\n #include \"run-command.h\"\n #include \"url.h\"\n+#include \"urlmatch.h\"\n #include \"credential.h\"\n #include \"version.h\"\n #include \"pkt-line.h\"\n@@ -334,10 +335,20 @@ 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 *normalized_url;\n+\tstruct urlmatch_config config = { STRING_LIST_INIT_DUP };\n+\n+\tconfig.section = \"http\";\n+\tconfig.key = NULL;\n+\tconfig.collect_fn = http_options;\n+\tconfig.cascade_fn = git_default_config;\n+\tconfig.cb = NULL;\n \n \thttp_is_verbose = 0;\n+\tnormalized_url = url_normalize(url, &config.url);\n \n-\tgit_config(http_options, NULL);\n+\tgit_config(urlmatch_config_entry, &config);\n+\tfree(normalized_url);\n \n \tcurl_global_init(CURL_GLOBAL_ALL);\n \ndiff --git a/t/.gitattributes b/t/.gitattributes\nindex 1b97c546..f6f1df35 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 00000000..f79bb0fb\n--- /dev/null\n+++ b/t/t5200-url-normalize.sh\n@@ -0,0 +1,199 @@\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+\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/README b/t/t5200/README\nnew file mode 100644\nindex 00000000..e3a67d94\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 00000000..8aaf23c4\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 00000000..749f4bd5\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/config-3 b/t/t5200/config-3\nnew file mode 100644\nindex 00000000..5c8d3e85\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/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 00000000..80437217\n--- /dev/null\n+++ b/test-url-normalize.c\n@@ -0,0 +1,139 @@\n+#ifdef NO_CURL\n+\n+int main(void)\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 struct url_info *info)\n+{\n+\tstruct strbuf opt_lc;\n+\tsize_t i, len;\n+\tstruct urlmatch_config config = { STRING_LIST_INIT_DUP };\n+\n+\tmemcpy(&config.url, info, sizeof(*info));\n+\tconfig.section = \"http\";\n+\tconfig.collect_fn = http_options;\n+\tconfig.cascade_fn = git_default_config;\n+\tconfig.cb = NULL;\n+\n+\tif (git_config_with_options(urlmatch_config_entry, &config, 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+#ifdef USE_CURL_MULTI\n+\telse if (!strcmp(\"maxrequests\", opt_lc.buf))\n+\t\tprintf(\"%d\\n\", max_requests);\n+#endif\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+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\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)info.url_len);\n+\t\tif (opt_c)\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], NULL);\n+\turl2 = url_normalize(argv[2], NULL);\n+\treturn (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1;\n+}\n+\n+#endif /* !NO_CURL */\n-- \n1.8.3\n"},{"id":"224401","messageId":"7vr4eetmm0.fsf@alter.siamese.dyndns.org","threadId":"34576","inReplyTo":"72dacc4d84caf9831f5e12ece7f38f2@f74d39fa044aa309eaea14b9f57fe79","subject":"Re: [PATCH ALTERNATIVE v6 0/2] http.<url>.<key> and friends","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-31T22:01:43Z","receivedAt":"2013-07-31T22:01:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks for a quick turnaround.  Will replace the two patches with\nthese and queue.\n"},{"id":"224404","messageId":"bcd3ddc8b538b5b074153cb729d0e78@f74d39fa044aa309eaea14b9f57fe79","threadId":"34576","inReplyTo":"7vr4eetmm0.fsf@alter.siamese.dyndns.org","subject":"[PATCH ALTERNATIVE v6.v2 4/6] config: parse http.<url>.<variable> using urlmatch","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-31T22:41:09Z","receivedAt":"2013-07-31T22:41:09Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"Use the urlmatch_config_entry() to wrap the underlying\nhttp_options() two-level variable parser in order to set\nhttp.<variable> to the value with the most specific URL in the\nconfiguration.\n\nSigned-off-by: Jeff King <peff@peff.net>\nSigned-off-by: Kyle J. McKay <mackyle@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\nSomehow I managed to screw up the previous version of 4/6 by\nduplicating a line in the config.txt patch.  Arrggh!  Anyhow,\nhere's a corrected version of 4/6 with the duplicate line\nremoved.  I apologize for my clumsiness.\n\n .gitignore               |   1 +\n Documentation/config.txt |  46 +++++++++++\n Makefile                 |   7 ++\n http.c                   |  13 +++-\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     | 139 +++++++++++++++++++++++++++++++++\n 22 files changed, 438 insertions(+), 1 deletion(-)\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\ndiff --git a/.gitignore b/.gitignore\nindex 6669bf0c..cd97e16a 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/Documentation/config.txt b/Documentation/config.txt\nindex 6e53fc50..8cc0fd78 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1513,6 +1513,51 @@ 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 a config key to match a URL, each element of the config key is\n+\tcompared to that of the URL, in the following order:\n++\n+--\n+. Scheme (e.g., `https` in `https://example.com/`). This field\n+  must match exactly between the config key and the URL.\n+\n+. 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+. Port number (e.g., `8080` in `http://example.com:8080/`).\n+  This field must match exactly between the config key and the URL.\n+  Omitted port numbers are automatically converted to the correct\n+  default for the scheme before matching.\n+\n+. Path (e.g., `repo.git` in `https://example.com/repo.git`). The\n+  path field of the config key must match the path field of the URL\n+  either exactly or as a prefix of slash-delimited path elements.  This means\n+  a config key with path `foo/` matches URL path `foo/bar`.  A prefix can only\n+  match on a slash (`/`) boundary.  Longer matches take precedence (so a config\n+  key with path `foo/bar` is a better match to URL path `foo/bar` than a config\n+  key with just path `foo/`).\n+\n+. User name (e.g., `user` in `https://user@example.com/repo.git`). If\n+  the config key has a user name it must match the user name in the\n+  URL exactly. If the config key does not have a user name, that\n+  config key will match a URL with any user name (including none),\n+  but at a lower precedence than a config key with a user name.\n+--\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 user name. For example,\n+if the URL is `https://user@example.com/foo/bar` a config key match of\n+`https://example.com/foo` will be preferred over a config key match of\n+`https://user@example.com`.\n++\n+All URLs are normalized before attempting any matching (the password part,\n+if embedded in the URL, is always ignored for matching purposes) so that\n+equivalent urls that are simply spelled differently will match properly.\n+Environment variable settings always override any matches.  The urls that are\n+matched against are those given directly to Git commands.  This means any URLs\n+visited as a result of a redirection do not participate in matching.\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/Makefile b/Makefile\nindex 0f931a20..ea3edbae 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@@ -721,6 +722,7 @@ LIB_H += tree-walk.h\n LIB_H += tree.h\n LIB_H += unpack-trees.h\n LIB_H += url.h\n+LIB_H += urlmatch.h\n LIB_H += userdiff.h\n LIB_H += utf8.h\n LIB_H += varint.h\n@@ -868,6 +870,7 @@ LIB_OBJS += tree.o\n LIB_OBJS += tree-walk.o\n LIB_OBJS += unpack-trees.o\n LIB_OBJS += url.o\n+LIB_OBJS += urlmatch.o\n LIB_OBJS += usage.o\n LIB_OBJS += userdiff.o\n LIB_OBJS += utf8.o\n@@ -2235,6 +2238,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/http.c b/http.c\nindex 37986f82..5eda356f 100644\n--- a/http.c\n+++ b/http.c\n@@ -3,6 +3,7 @@\n #include \"sideband.h\"\n #include \"run-command.h\"\n #include \"url.h\"\n+#include \"urlmatch.h\"\n #include \"credential.h\"\n #include \"version.h\"\n #include \"pkt-line.h\"\n@@ -334,10 +335,20 @@ 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 *normalized_url;\n+\tstruct urlmatch_config config = { STRING_LIST_INIT_DUP };\n+\n+\tconfig.section = \"http\";\n+\tconfig.key = NULL;\n+\tconfig.collect_fn = http_options;\n+\tconfig.cascade_fn = git_default_config;\n+\tconfig.cb = NULL;\n \n \thttp_is_verbose = 0;\n+\tnormalized_url = url_normalize(url, &config.url);\n \n-\tgit_config(http_options, NULL);\n+\tgit_config(urlmatch_config_entry, &config);\n+\tfree(normalized_url);\n \n \tcurl_global_init(CURL_GLOBAL_ALL);\n \ndiff --git a/t/.gitattributes b/t/.gitattributes\nindex 1b97c546..f6f1df35 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 00000000..f79bb0fb\n--- /dev/null\n+++ b/t/t5200-url-normalize.sh\n@@ -0,0 +1,199 @@\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+\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/README b/t/t5200/README\nnew file mode 100644\nindex 00000000..e3a67d94\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 00000000..8aaf23c4\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 00000000..749f4bd5\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/config-3 b/t/t5200/config-3\nnew file mode 100644\nindex 00000000..5c8d3e85\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/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 00000000..80437217\n--- /dev/null\n+++ b/test-url-normalize.c\n@@ -0,0 +1,139 @@\n+#ifdef NO_CURL\n+\n+int main(void)\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 struct url_info *info)\n+{\n+\tstruct strbuf opt_lc;\n+\tsize_t i, len;\n+\tstruct urlmatch_config config = { STRING_LIST_INIT_DUP };\n+\n+\tmemcpy(&config.url, info, sizeof(*info));\n+\tconfig.section = \"http\";\n+\tconfig.collect_fn = http_options;\n+\tconfig.cascade_fn = git_default_config;\n+\tconfig.cb = NULL;\n+\n+\tif (git_config_with_options(urlmatch_config_entry, &config, 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+#ifdef USE_CURL_MULTI\n+\telse if (!strcmp(\"maxrequests\", opt_lc.buf))\n+\t\tprintf(\"%d\\n\", max_requests);\n+#endif\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+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\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)info.url_len);\n+\t\tif (opt_c)\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], NULL);\n+\turl2 = url_normalize(argv[2], NULL);\n+\treturn (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1;\n+}\n+\n+#endif /* !NO_CURL */\n-- \n1.8.3\n"},{"id":"224405","messageId":"20130731224511.GA25882@sigill.intra.peff.net","threadId":"34576","inReplyTo":"1375298768-7740-7-git-send-email-gitster@pobox.com","subject":"Re: [PATCH v6 6/6] config: \"git config --get-urlmatch\" parses section.<url>.key","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2013-07-31T22:45:13Z","receivedAt":"2013-07-31T22:45:13Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jul 31, 2013 at 12:26:08PM -0700, Junio C Hamano wrote:\n\n> Using the same urlmatch_config_entry() infrastructure, add a new\n> mode \"--get-urlmatch\" to the \"git config\" command, to learn values\n> for the \"virtual\" two-level variables customized for the specific\n> URL.\n> \n>     git config [--<type>] --get-urlmatch <section>[.<key>] <url>\n\nDo we want something like this on top, to convert the third form of\ntest-url-normalize into git-config calls?\n\nIt would be nicer squashed in, but we the tests are added earlier in the\nseries than \"--get-urlmatch\", so we would have to rip the tests out of\nthe earlier patches and have a \"[PATCH 7/6] add tests for url\nnormalizing\".\n\nTwo things to note about my test conversion:\n\n  1. Git-config expects pre-canonicalized variable names (so http.noepsv\n     instead of \"http.noEPSV\"). I think the \"git config --get\" code path\n     does this for the caller, so we should probably do the same for\n     \"--get-urlmatch\". And it is even easier here, because we know that\n     \"http.noEPSV\" does not contain a case-sensitive middle part. :)\n\n  2. I turned the many 'test \"$(git foo)\" = \"bar\"' invocations into a\n     wrapper function that uses test_cmp. This helped immensely with\n     debugging (1).\n\n     The wrapper is a little ugly. I do wonder if we actually need all\n     of these tests (i.e., it is not clear to me what different things\n     each is testing, and if it is not simply trying to exercise the\n     different variable names, which now all follow the same code path,\n     because git-config does not care about the particular names).\n\n---\n t/t5200-url-normalize.sh | 40 ++++++++-------\n test-url-normalize.c     | 93 ++---------------------------------\n 2 files changed, 27 insertions(+), 106 deletions(-)\n\ndiff --git a/t/t5200-url-normalize.sh b/t/t5200-url-normalize.sh\nindex f79bb0f..a5190f7 100755\n--- a/t/t5200-url-normalize.sh\n+++ b/t/t5200-url-normalize.sh\n@@ -3,11 +3,6 @@ test_description='url normalization'\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@@ -182,18 +177,27 @@ test_expect_success 'url equivalents' '\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-\ttest \"$(test-url-normalize -c \"$tc-3\" \"sslcainfo\" \"https://user@example.com/path/name/here\")\" = \"file-1\"\n-'\n+check_url_config() {\n+\tconfig=$1; shift\n+\texpect=$1; shift\n+\n+\ttest_expect_success \"config normalization ($*)\" \"\n+\t\techo '$expect' >expect &&\n+\t\tgit config --file=\"\\$config\" --get-urlmatch $* >actual &&\n+\t\ttest_cmp expect actual\n+\t\"\n+}\n+\n+check_url_config \"$tc-1\" other-agent http.useragent https://other.example.com/\n+check_url_config \"$tc-1\" example-agent http.useragent https://example.com/\n+check_url_config \"$tc-1\" false --bool http.sslverify https://example.com/\n+check_url_config \"$tc-1\" path-agent http.useragent https://example.com/path/sub\n+check_url_config \"$tc-1\" false --bool http.sslverify https://example.com/path/sub\n+check_url_config \"$tc-1\" true --bool http.noepsv https://elsewhere.com/\n+check_url_config \"$tc-1\" true --bool http.noepsv https://example.com\n+check_url_config \"$tc-1\" true --bool http.noepsv https://example.com/path\n+check_url_config \"$tc-2\" example-agent http.useragent HTTPS://example.COM/p%61th\n+check_url_config \"$tc-2\" false --bool http.sslverify HTTPS://example.COM/p%61th\n+check_url_config \"$tc-3\" file-1 http.sslcainfo https://user@example.com/path/name/here\n \n test_done\ndiff --git a/test-url-normalize.c b/test-url-normalize.c\nindex 81d3da9..34a1a67 100644\n--- a/test-url-normalize.c\n+++ b/test-url-normalize.c\n@@ -1,84 +1,11 @@ int main(int argc, char **argv)\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 struct url_info *info)\n-{\n-\tstruct strbuf opt_lc;\n-\tsize_t i, len;\n-\tstruct urlmatch_config config = { STRING_LIST_INIT_DUP };\n-\n-\tmemcpy(&config.url, info, sizeof(*info));\n-\tconfig.section = \"http\";\n-\tconfig.collect_fn = http_options;\n-\tconfig.cascade_fn = git_default_config;\n-\tconfig.cb = NULL;\n-\n-\tif (git_config_with_options(urlmatch_config_entry, &config, 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+#include \"git-compat-util.h\"\n+#include \"urlmatch.h\"\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+\tconst char *usage = \"test-url-normalize [-p | -l] <url1> | <url1> <url2>\";\n \tchar *url1, *url2;\n-\tint opt_p = 0, opt_l = 0, opt_c = 0;\n-\tchar *file = NULL, *optname = NULL;\n+\tint opt_p = 0, opt_l = 0;\n \n \t/*\n \t * For one url, succeed if url_normalize succeeds on it, fail otherwise.\n@@ -101,12 +28,6 @@ int main(int argc, char **argv)\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@@ -121,17 +42,13 @@ int main(int argc, char **argv)\n \t\t\tprintf(\"%s\\n\", url1);\n \t\tif (opt_l)\n \t\t\tprintf(\"%u\\n\", (unsigned)info.url_len);\n-\t\tif (opt_c)\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+\tif (opt_p || opt_l)\n \t\tdie(usage);\n \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-#endif /* !NO_CURL */\n"},{"id":"224408","messageId":"62E17EDB-B24D-4F37-95F8-E2E26118E5E9@gmail.com","threadId":"34576","inReplyTo":"20130731224511.GA25882@sigill.intra.peff.net","subject":"Re: [PATCH v6 6/6] config: \"git config --get-urlmatch\" parses section.<url>.key","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-07-31T23:03:01Z","receivedAt":"2013-07-31T23:03:01Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"On Jul 31, 2013, at 15:45, Jeff King wrote:\n\n> On Wed, Jul 31, 2013 at 12:26:08PM -0700, Junio C Hamano wrote:\n>\n>> Using the same urlmatch_config_entry() infrastructure, add a new\n>> mode \"--get-urlmatch\" to the \"git config\" command, to learn values\n>> for the \"virtual\" two-level variables customized for the specific\n>> URL.\n>>\n>>    git config [--<type>] --get-urlmatch <section>[.<key>] <url>\n>\n> Do we want something like this on top, to convert the third form of\n> test-url-normalize into git-config calls?\n>\n> It would be nicer squashed in, but we the tests are added earlier in  \n> the\n> series than \"--get-urlmatch\", so we would have to rip the tests out of\n> the earlier patches and have a \"[PATCH 7/6] add tests for url\n> normalizing\".\n>\n> Two things to note about my test conversion:\n>\n>  1. Git-config expects pre-canonicalized variable names (so  \n> http.noepsv\n>     instead of \"http.noEPSV\"). I think the \"git config --get\" code  \n> path\n>     does this for the caller, so we should probably do the same for\n>     \"--get-urlmatch\". And it is even easier here, because we know that\n>     \"http.noEPSV\" does not contain a case-sensitive middle part. :)\n\nThe test was testing that too, which I think is a good thing.  Your  \nreplacement does not test that.  With a fix for --get-urlmatch as you  \nmention above, the tests can check that again.\n\n>  2. I turned the many 'test \"$(git foo)\" = \"bar\"' invocations into a\n>     wrapper function that uses test_cmp. This helped immensely with\n>     debugging (1).\n>\n>     The wrapper is a little ugly. I do wonder if we actually need all\n>     of these tests (i.e., it is not clear to me what different things\n>     each is testing, and if it is not simply trying to exercise the\n>     different variable names, which now all follow the same code path,\n>     because git-config does not care about the particular names).\n\nEach one tests a different item from the \"$tc-n\" config file to make  \nsure that everything that's in each config file actually behaves as  \nexpected.\n\nIf we do this (and I don't really have any objection except for the  \npoint noted above), then the tests really need to move out from t5200  \nas they're not tied to the http operations anymore.  Also the Makefile  \nrule for test-url-normalize.c needs to be simplified since it won't  \nneed the extra options to make it link since it's no longer including  \nhttp.c.\n\nThe README has this:\n\n> First digit tells the family:\n>\n>         0 - the absolute basics and global stuff\n>         1 - the basic commands concerning database\n>         2 - the basic commands concerning the working tree\n>         3 - the other basic commands (e.g. ls-files)\n>         4 - the diff commands\n>         5 - the pull and exporting commands\n>         6 - the revision tree commands (even e.g. merge-base)\n>         7 - the porcelainish commands concerning the working tree\n>         8 - the porcelainish commands concerning forensics\n>         9 - the git tools\n\nBut the best choice does not immediately jump out at me.  However,  \nlooking at the other tests that are there, I think perhaps 1307-config- \nurl might be a reasonable choice.\n"},{"id":"224411","messageId":"20130731234448.GA8764@sigill.intra.peff.net","threadId":"34576","inReplyTo":"62E17EDB-B24D-4F37-95F8-E2E26118E5E9@gmail.com","subject":"Re: [PATCH v6 6/6] config: \"git config --get-urlmatch\" parses section.<url>.key","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2013-07-31T23:44:48Z","receivedAt":"2013-07-31T23:44:48Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jul 31, 2013 at 04:03:01PM -0700, Kyle J. McKay wrote:\n\n> > 1. Git-config expects pre-canonicalized variable names (so\n> >http.noepsv\n> >    instead of \"http.noEPSV\"). I think the \"git config --get\" code\n> >path\n> >    does this for the caller, so we should probably do the same for\n> >    \"--get-urlmatch\". And it is even easier here, because we know that\n> >    \"http.noEPSV\" does not contain a case-sensitive middle part. :)\n> \n> The test was testing that too, which I think is a good thing.  Your\n> replacement does not test that.  With a fix for --get-urlmatch as you\n> mention above, the tests can check that again.\n\nI do not think the existing tests were checking anything interesting in\nthat respect. The url-matching code does not do the canonicalization,\nand nor should it (the internal callbacks for all variables should use\nthe canonical lowercase version). So we were only testing that\ntest-url-normalize lowercased them, which is not something we actually\ncare about (nobody but the test script should ever call it).\n\nThat being said, git-config _should_ be lowercasing to match the normal\n--get code path. I think the fix (squashable on top of 6/6 + my earlier\npatch) is just:\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex c35c5be..9328a90 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -589,7 +589,9 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \t}\n \telse if (actions == ACTION_GET_URLMATCH) {\n \t\tcheck_argc(argc, 2, 2);\n-\t\treturn get_urlmatch(argv[0], argv[1]);\n+\t\tif (git_config_parse_key(argv[0], &key, NULL))\n+\t\t\treturn CONFIG_INVALID_KEY;\n+\t\treturn get_urlmatch(key, argv[1]);\n \t}\n \telse if (actions == ACTION_UNSET) {\n \t\tcheck_argc(argc, 1, 2);\ndiff --git a/t/t5200-url-normalize.sh b/t/t5200-url-normalize.sh\nindex a5190f7..7284dc6 100755\n--- a/t/t5200-url-normalize.sh\n+++ b/t/t5200-url-normalize.sh\n@@ -190,14 +190,14 @@ check_url_config \"$tc-2\" example-agent http.useragent HTTPS://example.COM/p%61th\n \n check_url_config \"$tc-1\" other-agent http.useragent https://other.example.com/\n check_url_config \"$tc-1\" example-agent http.useragent https://example.com/\n-check_url_config \"$tc-1\" false --bool http.sslverify https://example.com/\n+check_url_config \"$tc-1\" false --bool http.sslVerify https://example.com/\n check_url_config \"$tc-1\" path-agent http.useragent https://example.com/path/sub\n-check_url_config \"$tc-1\" false --bool http.sslverify https://example.com/path/sub\n-check_url_config \"$tc-1\" true --bool http.noepsv https://elsewhere.com/\n-check_url_config \"$tc-1\" true --bool http.noepsv https://example.com\n-check_url_config \"$tc-1\" true --bool http.noepsv https://example.com/path\n+check_url_config \"$tc-1\" false --bool http.sslVerify https://example.com/path/sub\n+check_url_config \"$tc-1\" true --bool http.noEPSV https://elsewhere.com/\n+check_url_config \"$tc-1\" true --bool http.noEPSV https://example.com\n+check_url_config \"$tc-1\" true --bool http.noEPSV https://example.com/path\n check_url_config \"$tc-2\" example-agent http.useragent HTTPS://example.COM/p%61th\n-check_url_config \"$tc-2\" false --bool http.sslverify HTTPS://example.COM/p%61th\n+check_url_config \"$tc-2\" false --bool http.sslVerify HTTPS://example.COM/p%61th\n check_url_config \"$tc-3\" file-1 http.sslcainfo https://user@example.com/path/name/here\n \n test_done\n\n> >    The wrapper is a little ugly. I do wonder if we actually need all\n> >    of these tests (i.e., it is not clear to me what different things\n> >    each is testing, and if it is not simply trying to exercise the\n> >    different variable names, which now all follow the same code path,\n> >    because git-config does not care about the particular names).\n> \n> Each one tests a different item from the \"$tc-n\" config file to make\n> sure that everything that's in each config file actually behaves as\n> expected.\n\nI guess I don't understand why we have so many items in each file. That\nis, we have:\n\n\t\"$tc-1\" \"useragent\" \"https://other.example.com/\" = \"other-agent\"\n\t\"$tc-1\" \"useragent\" \"https://example.com/\" = \"example-agent\"\n\nThe first checks that we do not apply within a sub-domain (but fall back\nto http.useragent), and the second checks that we do properly apply the\nfull domain.\n\n\t\"$tc-1\" \"sslVerify\" \"https://example.com/\" = \"false\"\n\nThis check seems redundant with the second one above.\n\n\t\"$tc-1\" \"useragent\" \"https://example.com/path/sub\" = \"path-agent\"\n\t\"$tc-1\" \"sslVerify\" \"https://example.com/path/sub\" = \"false\"\n\nHere we make sure paths are preferred over non-paths (the first one),\nbut that config keys with non-paths are still used (the second).\n\n\t\"$tc-1\" \"noEPSV\" \"https://elsewhere.com/\" = \"true\"\n\nThis seems redundant with the first test (check that we do not match and\nfallback to http.noepsv).\n\n\t\"$tc-1\" \"noEPSV\" \"https://example.com\" = \"true\"\n\nNot sure what we are testing here; there is no variable besides the\none in http.noepsv. Somewhat redundant with the first test.\n\n\t\"$tc-1\" \"noEPSV\" \"https://example.com/path\" = \"true\"\n\nDitto.\n\n\t\"$tc-2\" \"useragent\" \"HTTPS://example.COM/p%61th\" = \"example-agent\"\n\t\"$tc-2\" \"sslVerify\" \"HTTPS://example.COM/p%61th\" = \"false\"\n\nTesting normalization, though they seem redundant with each other.\n\n\t\"$tc-3\" \"sslcainfo\" \"https://user@example.com/path/name/here\" = \"file-1\"\n\nTesting specific pathnames preferred to usernames, which is useful.\n\nI don't mean to nitpick. It was just hard as a reader to figure out what\nspecifically each one was interested in checking, which means it may be\nsimilarly hard if one of the tests is later broken for the investigator\nto figure out what was happening. I don't know if it is worth putting\neach in its own test and annotating what each is looking for (it may\nalso help show gaps; e.g., we check that longer pathnames trump\nusernames, but we do not check that the same pathname prefers the\nversion with the username).\n\n> If we do this (and I don't really have any objection except for the\n> point noted above), then the tests really need to move out from t5200\n> [...]\n> But the best choice does not immediately jump out at me.  However,\n> looking at the other tests that are there, I think perhaps\n> 1307-config-url might be a reasonable choice.\n\nYes, that makes sense to me; all of the other config is in the t1300\nseries.\n\n-Peff\n"},{"id":"224412","messageId":"7vzjt2s35v.fsf@alter.siamese.dyndns.org","threadId":"34576","inReplyTo":"20130731224511.GA25882@sigill.intra.peff.net","subject":"Re: [PATCH v6 6/6] config: \"git config --get-urlmatch\" parses section.<url>.key","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-31T23:47:08Z","receivedAt":"2013-07-31T23:47:08Z","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. Git-config expects pre-canonicalized variable names (so http.noepsv\n>      instead of \"http.noEPSV\"). I think the \"git config --get\" code path\n>      does this for the caller, so we should probably do the same for\n>      \"--get-urlmatch\". And it is even easier here, because we know that\n>      \"http.noEPSV\" does not contain a case-sensitive middle part. :)\n\nI'll squash these in later, but here is from my working copy.\nThanks for spotting.\n\n builtin/config.c       | 32 +++++++++++++++++++-------------\n t/t1300-repo-config.sh |  4 ++--\n 2 files changed, 21 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex c35c5be..c046f54 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -379,9 +379,22 @@ static int urlmatch_collect_fn(const char *var, const char *value, void *cb)\n \treturn 0;\n }\n \n+static char *dup_downcase(const char *string)\n+{\n+\tchar *result;\n+\tsize_t len, i;\n+\n+\tlen = strlen(string);\n+\tresult = xmalloc(len + 1);\n+\tfor (i = 0; i < len; i++)\n+\t\tresult[i] = tolower(string[i]);\n+\tresult[i] = '\\0';\n+\treturn result;\n+}\n+\n static int get_urlmatch(const char *var, const char *url)\n {\n-\tconst char *section_tail;\n+\tchar *section_tail;\n \tstruct string_list_item *item;\n \tstruct urlmatch_config config = { STRING_LIST_INIT_DUP };\n \tstruct string_list values = STRING_LIST_INIT_DUP;\n@@ -393,13 +406,13 @@ static int get_urlmatch(const char *var, const char *url)\n \tif (!url_normalize(url, &config.url))\n \t\tdie(config.url.err);\n \n-\tsection_tail = strchr(var, '.');\n+\tconfig.section = dup_downcase(var);\n+\tsection_tail = strchr(config.section, '.');\n \tif (section_tail) {\n-\t\tconfig.section = xmemdupz(var, section_tail - var);\n-\t\tconfig.key = strrchr(var, '.') + 1;\n+\t\t*section_tail = '\\0';\n+\t\tconfig.key = section_tail + 1;\n \t\tshow_keys = 0;\n \t} else {\n-\t\tconfig.section = var;\n \t\tconfig.key = NULL;\n \t\tshow_keys = 1;\n \t}\n@@ -425,14 +438,7 @@ static int get_urlmatch(const char *var, const char *url)\n \tstring_list_clear(&values, 1);\n \tfree(config.url.url);\n \n-\t/*\n-\t * section name may have been copied to replace the dot, in which\n-\t * case it needs to be freed.  key name is either NULL (e.g. 'http'\n-\t * alone) or points into var (e.g. 'http.savecookies'), and we do\n-\t * not own the storage.\n-\t */\n-\tif (config.section != var)\n-\t\tfree((void *)config.section);\n+\tfree((void *)config.section);\n \treturn 0;\n }\n \ndiff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh\nindex 323e880..c23f478 100755\n--- a/t/t1300-repo-config.sh\n+++ b/t/t1300-repo-config.sh\n@@ -1097,7 +1097,7 @@ test_expect_success 'urlmatch' '\n \tEOF\n \n \techo true >expect &&\n-\tgit config --bool --get-urlmatch http.sslverify https://good.example.com >actual &&\n+\tgit config --bool --get-urlmatch http.SSLverify https://good.example.com >actual &&\n \ttest_cmp expect actual &&\n \n \techo false >expect &&\n@@ -1108,7 +1108,7 @@ test_expect_success 'urlmatch' '\n \t\techo http.cookiefile /tmp/cookie.txt &&\n \t\techo http.sslverify false\n \t} >expect &&\n-\tgit config --get-urlmatch http https://weak.example.com >actual &&\n+\tgit config --get-urlmatch HTTP https://weak.example.com >actual &&\n \ttest_cmp expect actual\n '\n \n"},{"id":"224422","messageId":"7vob9hs4qu.fsf@alter.siamese.dyndns.org","threadId":"34576","inReplyTo":"20130731234448.GA8764@sigill.intra.peff.net","subject":"Re: [PATCH v6 6/6] config: \"git config --get-urlmatch\" parses section.<url>.key","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-01T17:25:13Z","receivedAt":"2013-08-01T17:25:13Z","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 being said, git-config _should_ be lowercasing to match the normal\n> --get code path. I think the fix (squashable on top of 6/6 + my earlier\n> patch) is just:\n>\n> diff --git a/builtin/config.c b/builtin/config.c\n> index c35c5be..9328a90 100644\n> --- a/builtin/config.c\n> +++ b/builtin/config.c\n> @@ -589,7 +589,9 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n>  \t}\n>  \telse if (actions == ACTION_GET_URLMATCH) {\n>  \t\tcheck_argc(argc, 2, 2);\n> -\t\treturn get_urlmatch(argv[0], argv[1]);\n> +\t\tif (git_config_parse_key(argv[0], &key, NULL))\n> +\t\t\treturn CONFIG_INVALID_KEY;\n> +\t\treturn get_urlmatch(key, argv[1]);\n>  \t}\n>  \telse if (actions == ACTION_UNSET) {\n>  \t\tcheck_argc(argc, 1, 2);\n\nIf we drop the \"list every key in section.*\" mode, the above should\nbe sufficient, I think.\n\nI do not know how useful it would be to be for a scripted Porcelain\nto be able to ask\n\n    $ git config --get-urlmatch http https://weak.example.com/path/to/git.git\n\nand get all the \"http.*\" variables that will apply to the given URL.\n"},{"id":"224423","messageId":"20130801173010.GB16562@sigill.intra.peff.net","threadId":"34576","inReplyTo":"7vob9hs4qu.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v6 6/6] config: \"git config --get-urlmatch\" parses section.<url>.key","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2013-08-01T17:30:11Z","receivedAt":"2013-08-01T17:30:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Aug 01, 2013 at 10:25:13AM -0700, Junio C Hamano wrote:\n\n> > diff --git a/builtin/config.c b/builtin/config.c\n> > index c35c5be..9328a90 100644\n> > --- a/builtin/config.c\n> > +++ b/builtin/config.c\n> > @@ -589,7 +589,9 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n> >  \t}\n> >  \telse if (actions == ACTION_GET_URLMATCH) {\n> >  \t\tcheck_argc(argc, 2, 2);\n> > -\t\treturn get_urlmatch(argv[0], argv[1]);\n> > +\t\tif (git_config_parse_key(argv[0], &key, NULL))\n> > +\t\t\treturn CONFIG_INVALID_KEY;\n> > +\t\treturn get_urlmatch(key, argv[1]);\n> >  \t}\n> >  \telse if (actions == ACTION_UNSET) {\n> >  \t\tcheck_argc(argc, 1, 2);\n> \n> If we drop the \"list every key in section.*\" mode, the above should\n> be sufficient, I think.\n\nAh, right, forgot about that again.\n\n> I do not know how useful it would be to be for a scripted Porcelain\n> to be able to ask\n> \n>     $ git config --get-urlmatch http https://weak.example.com/path/to/git.git\n> \n> and get all the \"http.*\" variables that will apply to the given URL.\n\nI do not think it is all that useful, but I don't think it hurts too\nmuch to have it (and it would be a hard operation to do otherwise). The\nimplementation you posted that downcases as we memdup the key makes\nsense to me.\n\nThe key thing is that it is done only by git-config, as we would not\nwant to pay the allocation cost for every config key we look at during a\ngit_config() run, but I think your implementation is fine in that\nrespect.\n\n-Peff\n"},{"id":"224626","messageId":"5acf57129e37a87611ed03648f7b368@f74d39fa044aa309eaea14b9f57fe79","threadId":"34576","inReplyTo":"62E17EDB-B24D-4F37-95F8-E2E26118E5E9@gmail.com","subject":"[PATCH ALTERNATIVE v6.v3 4/6] config: parse http.<url>.<variable> using urlmatch","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-08-05T20:20:36Z","receivedAt":"2013-08-05T20:20:36Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"Use the urlmatch_config_entry() to wrap the underlying\nhttp_options() two-level variable parser in order to set\nhttp.<variable> to the value with the most specific URL in the\nconfiguration.\n\nSigned-off-by: Jeff King <peff@peff.net>\nSigned-off-by: Kyle J. McKay <mackyle@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\nThis version of 4/6 moves the tests to t0110 since urlmatch is now global.\nThe config tests are removed since part 6/6 already has those and they no\nlonger belong with the urlmatch normalization tests.\n\nThe Makefile rule has been removed since it's no longer needed to build\ncorrectly as the test program no longer includes http.c.\n\nOther than those changes (and a minor rename to reflect the new location),\nthis patch is identical to the previous v6.v2 4/6.\n\n .gitignore                        |   1 +\n Documentation/config.txt          |  45 ++++++++++\n Makefile                          |   3 +\n http.c                            |  13 ++-\n t/.gitattributes                  |   1 +\n t/t0110-urlmatch-normalization.sh | 177 ++++++++++++++++++++++++++++++++++++++\n t/t0110/README                    |   9 ++\n t/t0110/url-1                     | Bin 0 -> 20 bytes\n t/t0110/url-10                    | Bin 0 -> 23 bytes\n t/t0110/url-11                    | Bin 0 -> 25 bytes\n t/t0110/url-2                     | Bin 0 -> 20 bytes\n t/t0110/url-3                     | Bin 0 -> 23 bytes\n t/t0110/url-4                     | Bin 0 -> 23 bytes\n t/t0110/url-5                     | Bin 0 -> 23 bytes\n t/t0110/url-6                     | Bin 0 -> 23 bytes\n t/t0110/url-7                     | Bin 0 -> 23 bytes\n t/t0110/url-8                     | Bin 0 -> 23 bytes\n t/t0110/url-9                     | Bin 0 -> 23 bytes\n test-urlmatch-normalization.c     |  50 +++++++++++\n 19 files changed, 298 insertions(+), 1 deletion(-)\n create mode 100755 t/t0110-urlmatch-normalization.sh\n create mode 100644 t/t0110/README\n create mode 100644 t/t0110/url-1\n create mode 100644 t/t0110/url-10\n create mode 100644 t/t0110/url-11\n create mode 100644 t/t0110/url-2\n create mode 100644 t/t0110/url-3\n create mode 100644 t/t0110/url-4\n create mode 100644 t/t0110/url-5\n create mode 100644 t/t0110/url-6\n create mode 100644 t/t0110/url-7\n create mode 100644 t/t0110/url-8\n create mode 100644 t/t0110/url-9\n create mode 100644 test-urlmatch-normalization.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 6669bf0c..b8524bfe 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-urlmatch-normalization\n /test-wildmatch\n /common-cmds.h\n *.tar.gz\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 6e53fc50..a81f3ab7 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1513,6 +1513,51 @@ 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 a config key to match a URL, each element of the config key is\n+\tcompared to that of the URL, in the following order:\n++\n+--\n+. Scheme (e.g., `https` in `https://example.com/`). This field\n+  must match exactly between the config key and the URL.\n+\n+. 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+. Port number (e.g., `8080` in `http://example.com:8080/`).\n+  This field must match exactly between the config key and the URL.\n+  Omitted port numbers are automatically converted to the correct\n+  default for the scheme before matching.\n+\n+. Path (e.g., `repo.git` in `https://example.com/repo.git`). The\n+  path field of the config key must match the path field of the URL\n+  either exactly or as a prefix of slash-delimited path elements.  This means\n+  a config key with path `foo/` matches URL path `foo/bar`.  A prefix can only\n+  match on a slash (`/`) boundary.  Longer matches take precedence (so a config\n+  key with path `foo/bar` is a better match to URL path `foo/bar` than a config\n+  key with just path `foo/`).\n+\n+. User name (e.g., `user` in `https://user@example.com/repo.git`). If\n+  the config key has a user name it must match the user name in the\n+  URL exactly. If the config key does not have a user name, that\n+  config key will match a URL with any user name (including none),\n+  but at a lower precedence than a config key with a user name.\n+--\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 user name. For example,\n+if the URL is `https://user@example.com/foo/bar` a config key match of\n+`https://example.com/foo` will be preferred over a config key match of\n+`https://user@example.com`.\n++\n+All URLs are normalized before attempting any matching (the password part,\n+if embedded in the URL, is always ignored for matching purposes) so that\n+equivalent urls that are simply spelled differently will match properly.\n+Environment variable settings always override any matches.  The urls that are\n+matched against are those given directly to Git commands.  This means any URLs\n+visited as a result of a redirection do not participate in matching.\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/Makefile b/Makefile\nindex 0f931a20..2df742c2 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-urlmatch-normalization\n TEST_PROGRAMS_NEED_X += test-wildmatch\n \n TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))\n@@ -721,6 +722,7 @@ LIB_H += tree-walk.h\n LIB_H += tree.h\n LIB_H += unpack-trees.h\n LIB_H += url.h\n+LIB_H += urlmatch.h\n LIB_H += userdiff.h\n LIB_H += utf8.h\n LIB_H += varint.h\n@@ -868,6 +870,7 @@ LIB_OBJS += tree.o\n LIB_OBJS += tree-walk.o\n LIB_OBJS += unpack-trees.o\n LIB_OBJS += url.o\n+LIB_OBJS += urlmatch.o\n LIB_OBJS += usage.o\n LIB_OBJS += userdiff.o\n LIB_OBJS += utf8.o\ndiff --git a/http.c b/http.c\nindex 37986f82..5eda356f 100644\n--- a/http.c\n+++ b/http.c\n@@ -3,6 +3,7 @@\n #include \"sideband.h\"\n #include \"run-command.h\"\n #include \"url.h\"\n+#include \"urlmatch.h\"\n #include \"credential.h\"\n #include \"version.h\"\n #include \"pkt-line.h\"\n@@ -334,10 +335,20 @@ 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 *normalized_url;\n+\tstruct urlmatch_config config = { STRING_LIST_INIT_DUP };\n+\n+\tconfig.section = \"http\";\n+\tconfig.key = NULL;\n+\tconfig.collect_fn = http_options;\n+\tconfig.cascade_fn = git_default_config;\n+\tconfig.cb = NULL;\n \n \thttp_is_verbose = 0;\n+\tnormalized_url = url_normalize(url, &config.url);\n \n-\tgit_config(http_options, NULL);\n+\tgit_config(urlmatch_config_entry, &config);\n+\tfree(normalized_url);\n \n \tcurl_global_init(CURL_GLOBAL_ALL);\n \ndiff --git a/t/.gitattributes b/t/.gitattributes\nindex 1b97c546..2d44088f 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+t0110/url-* binary\ndiff --git a/t/t0110-urlmatch-normalization.sh b/t/t0110-urlmatch-normalization.sh\nnew file mode 100755\nindex 00000000..8d6096d4\n--- /dev/null\n+++ b/t/t0110-urlmatch-normalization.sh\n@@ -0,0 +1,177 @@\n+#!/bin/sh\n+\n+test_description='urlmatch URL normalization'\n+. ./test-lib.sh\n+\n+# The base name of the test url files\n+tu=\"$TEST_DIRECTORY/t0110/url\"\n+\n+# Note that only file: URLs should be allowed without a host\n+\n+test_expect_success 'url scheme' '\n+\t! test-urlmatch-normalization \"\" &&\n+\t! test-urlmatch-normalization \"_\" &&\n+\t! test-urlmatch-normalization \"scheme\" &&\n+\t! test-urlmatch-normalization \"scheme:\" &&\n+\t! test-urlmatch-normalization \"scheme:/\" &&\n+\t! test-urlmatch-normalization \"scheme://\" &&\n+\t! test-urlmatch-normalization \"file\" &&\n+\t! test-urlmatch-normalization \"file:\" &&\n+\t! test-urlmatch-normalization \"file:/\" &&\n+\ttest-urlmatch-normalization \"file://\" &&\n+\t! test-urlmatch-normalization \"://acme.co\" &&\n+\t! test-urlmatch-normalization \"x_test://acme.co\" &&\n+\t! test-urlmatch-normalization \"-test://acme.co\" &&\n+\t! test-urlmatch-normalization \"0test://acme.co\" &&\n+\t! test-urlmatch-normalization \"+test://acme.co\" &&\n+\t! test-urlmatch-normalization \".test://acme.co\" &&\n+\t! test-urlmatch-normalization \"schem%6e://\" &&\n+\ttest-urlmatch-normalization \"x-Test+v1.0://acme.co\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"AbCdeF://x.Y\")\" = \"abcdef://x.y/\"\n+'\n+\n+test_expect_success 'url authority' '\n+\t! test-urlmatch-normalization \"scheme://user:pass@\" &&\n+\t! test-urlmatch-normalization \"scheme://?\" &&\n+\t! test-urlmatch-normalization \"scheme://#\" &&\n+\t! test-urlmatch-normalization \"scheme:///\" &&\n+\t! test-urlmatch-normalization \"scheme://:\" &&\n+\t! test-urlmatch-normalization \"scheme://:555\" &&\n+\ttest-urlmatch-normalization \"file://user:pass@\" &&\n+\ttest-urlmatch-normalization \"file://?\" &&\n+\ttest-urlmatch-normalization \"file://#\" &&\n+\ttest-urlmatch-normalization \"file:///\" &&\n+\ttest-urlmatch-normalization \"file://:\" &&\n+\t! test-urlmatch-normalization \"file://:555\" &&\n+\ttest-urlmatch-normalization \"scheme://user:pass@host\" &&\n+\ttest-urlmatch-normalization \"scheme://@host\" &&\n+\ttest-urlmatch-normalization \"scheme://%00@host\" &&\n+\t! test-urlmatch-normalization \"scheme://%%@host\" &&\n+\t! test-urlmatch-normalization \"scheme://host_\" &&\n+\ttest-urlmatch-normalization \"scheme://user:pass@host/\" &&\n+\ttest-urlmatch-normalization \"scheme://@host/\" &&\n+\ttest-urlmatch-normalization \"scheme://host/\" &&\n+\ttest-urlmatch-normalization \"scheme://host?x\" &&\n+\ttest-urlmatch-normalization \"scheme://host#x\" &&\n+\ttest-urlmatch-normalization \"scheme://host/@\" &&\n+\ttest-urlmatch-normalization \"scheme://host?@x\" &&\n+\ttest-urlmatch-normalization \"scheme://host#@x\" &&\n+\ttest-urlmatch-normalization \"scheme://[::1]\" &&\n+\ttest-urlmatch-normalization \"scheme://[::1]/\" &&\n+\t! test-urlmatch-normalization \"scheme://hos%41/\" &&\n+\ttest-urlmatch-normalization \"scheme://[invalid....:/\" &&\n+\ttest-urlmatch-normalization \"scheme://invalid....:]/\" &&\n+\t! test-urlmatch-normalization \"scheme://invalid....:[/\" &&\n+\t! test-urlmatch-normalization \"scheme://invalid....:[\"\n+'\n+\n+test_expect_success 'url port checks' '\n+\ttest-urlmatch-normalization \"xyz://q@some.host:\" &&\n+\ttest-urlmatch-normalization \"xyz://q@some.host:456/\" &&\n+\t! test-urlmatch-normalization \"xyz://q@some.host:0\" &&\n+\t! test-urlmatch-normalization \"xyz://q@some.host:0000000\" &&\n+\ttest-urlmatch-normalization \"xyz://q@some.host:0000001?\" &&\n+\ttest-urlmatch-normalization \"xyz://q@some.host:065535#\" &&\n+\ttest-urlmatch-normalization \"xyz://q@some.host:65535\" &&\n+\t! test-urlmatch-normalization \"xyz://q@some.host:65536\" &&\n+\t! test-urlmatch-normalization \"xyz://q@some.host:99999\" &&\n+\t! test-urlmatch-normalization \"xyz://q@some.host:100000\" &&\n+\t! test-urlmatch-normalization \"xyz://q@some.host:100001\" &&\n+\ttest-urlmatch-normalization \"http://q@some.host:80\" &&\n+\ttest-urlmatch-normalization \"https://q@some.host:443\" &&\n+\ttest-urlmatch-normalization \"http://q@some.host:80/\" &&\n+\ttest-urlmatch-normalization \"https://q@some.host:443?\" &&\n+\t! test-urlmatch-normalization \"http://q@:8008\" &&\n+\t! test-urlmatch-normalization \"http://:8080\" &&\n+\t! test-urlmatch-normalization \"http://:\" &&\n+\ttest-urlmatch-normalization \"xyz://q@some.host:456/\" &&\n+\ttest-urlmatch-normalization \"xyz://[::1]:456/\" &&\n+\ttest-urlmatch-normalization \"xyz://[::1]:/\" &&\n+\t! test-urlmatch-normalization \"xyz://[::1]:000/\" &&\n+\t! test-urlmatch-normalization \"xyz://[::1]:0%300/\" &&\n+\t! test-urlmatch-normalization \"xyz://[::1]:0x80/\" &&\n+\t! test-urlmatch-normalization \"xyz://[::1]:4294967297/\" &&\n+\t! test-urlmatch-normalization \"xyz://[::1]:030f/\"\n+'\n+\n+test_expect_success 'url port normalization' '\n+\ttest \"$(test-urlmatch-normalization -p \"http://x:800\")\" = \"http://x:800/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"http://x:0800\")\" = \"http://x:800/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"http://x:00000800\")\" = \"http://x:800/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"http://x:065535\")\" = \"http://x:65535/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"http://x:1\")\" = \"http://x:1/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"http://x:80\")\" = \"http://x/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"http://x:080\")\" = \"http://x/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"http://x:000000080\")\" = \"http://x/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"https://x:443\")\" = \"https://x/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"https://x:0443\")\" = \"https://x/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"https://x:000000443\")\" = \"https://x/\"\n+'\n+\n+test_expect_success 'url general escapes' '\n+\t! test-urlmatch-normalization \"http://x.y?%fg\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"X://W/%7e%41^%3a\")\" = \"x://w/~A%5E%3A\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"X://W/:/?#[]@\")\" = \"x://w/:/?#[]@\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"X://W/$&()*+,;=\")\" = \"x://w/$&()*+,;=\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"X://W/'\\''\")\" = \"x://w/'\\''\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"X://W?'\\!'\")\" = \"x://w/?'\\!'\"\n+'\n+\n+test_expect_success 'url high-bit escapes' '\n+\ttest \"$(test-urlmatch-normalization -p \"$(cat \"$tu-1\")\")\" = \"x://q/%01%02%03%04%05%06%07%08%0E%0F%10%11%12\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"$(cat \"$tu-2\")\")\" = \"x://q/%13%14%15%16%17%18%19%1B%1C%1D%1E%1F%7F\" &&\n+\ttest \"$(test-urlmatch-normalization -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-urlmatch-normalization -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-urlmatch-normalization -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-urlmatch-normalization -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-urlmatch-normalization -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-urlmatch-normalization -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-urlmatch-normalization -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-urlmatch-normalization -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-urlmatch-normalization -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-urlmatch-normalization -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-urlmatch-normalization -l \"Http://%4d%65:%4d^%70@The.Host\")\" = 25 &&\n+\ttest \"$(test-urlmatch-normalization -l \"http://%41:%42@x.y/%61/\")\" = 17 &&\n+\ttest \"$(test-urlmatch-normalization -l \"http://@x.y/^\")\" = 15\n+'\n+\n+test_expect_success 'url . and .. segments' '\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/.\")\" = \"x://y/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/./\")\" = \"x://y/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/a/.\")\" = \"x://y/a\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/a/./\")\" = \"x://y/a/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/.?\")\" = \"x://y/?\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/./?\")\" = \"x://y/?\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/a/.?\")\" = \"x://y/a?\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/a/./?\")\" = \"x://y/a/?\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/a/./b/.././../c\")\" = \"x://y/c\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/a/./b/../.././c/\")\" = \"x://y/c/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/a/./b/.././../c/././.././.\")\" = \"x://y/\" &&\n+\t! test-urlmatch-normalization \"x://y/a/./b/.././../c/././.././..\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/a/./?/././..\")\" = \"x://y/a/?/././..\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/%2e/\")\" = \"x://y/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/%2E/\")\" = \"x://y/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/a/%2e./\")\" = \"x://y/\" &&\n+\ttest \"$(test-urlmatch-normalization -p \"x://y/b/.%2E/\")\" = \"x://y/\" &&\n+\ttest \"$(test-urlmatch-normalization -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-urlmatch-normalization \"httP://x\" \"Http://X/\" &&\n+\ttest-urlmatch-normalization \"Http://%4d%65:%4d^%70@The.Host\" \"hTTP://Me:%4D^p@the.HOST:80/\" &&\n+\t! test-urlmatch-normalization \"https://@x.y/^\" \"httpS://x.y:443/^\" &&\n+\ttest-urlmatch-normalization \"https://@x.y/^\" \"httpS://@x.y:0443/^\" &&\n+\ttest-urlmatch-normalization \"https://@x.y/^/../abc\" \"httpS://@x.y:0443/abc\" &&\n+\ttest-urlmatch-normalization \"https://@x.y/^/..\" \"httpS://@x.y:0443/\"\n+'\n+\n+test_done\ndiff --git a/t/t0110/README b/t/t0110/README\nnew file mode 100644\nindex 00000000..ad4a50ec\n--- /dev/null\n+++ b/t/t0110/README\n@@ -0,0 +1,9 @@\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.\ndiff --git a/t/t0110/url-1 b/t/t0110/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/t0110/url-10 b/t/t0110/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/t0110/url-11 b/t/t0110/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/t0110/url-2 b/t/t0110/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/t0110/url-3 b/t/t0110/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/t0110/url-4 b/t/t0110/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/t0110/url-5 b/t/t0110/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/t0110/url-6 b/t/t0110/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/t0110/url-7 b/t/t0110/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/t0110/url-8 b/t/t0110/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/t0110/url-9 b/t/t0110/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-urlmatch-normalization.c b/test-urlmatch-normalization.c\nnew file mode 100644\nindex 00000000..2603899b\n--- /dev/null\n+++ b/test-urlmatch-normalization.c\n@@ -0,0 +1,50 @@\n+#include \"git-compat-util.h\"\n+#include \"urlmatch.h\"\n+\n+int main(int argc, char **argv)\n+{\n+\tconst char *usage = \"test-urlmatch-normalization [-p | -l] <url1> | <url1> <url2>\";\n+\tchar *url1, *url2;\n+\tint opt_p = 0, opt_l = 0;\n+\n+\t/*\n+\t * For one url, succeed if url_normalize succeeds on it, fail otherwise.\n+\t * For two urls, succeed only if url_normalize succeeds on both and\n+\t * the results compare equal with strcmp.  If -p is given (one url only)\n+\t * and url_normalize succeeds, print the result followed by \"\\n\".  If\n+\t * -l is given (one url only) and url_normalize succeeds, print the\n+\t * returned length in decimal followed by \"\\n\".\n+\t */\n+\n+\tif (argc > 1 && !strcmp(argv[1], \"-p\")) {\n+\t\topt_p = 1;\n+\t\targc--;\n+\t\targv++;\n+\t} else if (argc > 1 && !strcmp(argv[1], \"-l\")) {\n+\t\topt_l = 1;\n+\t\targc--;\n+\t\targv++;\n+\t}\n+\n+\tif (argc < 2 || argc > 3)\n+\t\tdie(usage);\n+\n+\tif (argc == 2) {\n+\t\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)info.url_len);\n+\t\treturn 0;\n+\t}\n+\n+\tif (opt_p || opt_l)\n+\t\tdie(usage);\n+\n+\turl1 = url_normalize(argv[1], NULL);\n+\turl2 = url_normalize(argv[2], NULL);\n+\treturn (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1;\n+}\n-- \n1.8.3\n"},{"id":"224630","messageId":"7vsiyniw63.fsf@alter.siamese.dyndns.org","threadId":"34576","inReplyTo":"5acf57129e37a87611ed03648f7b368@f74d39fa044aa309eaea14b9f57fe79","subject":"Re: [PATCH ALTERNATIVE v6.v3 4/6] config: parse http.<url>.<variable> using urlmatch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-05T22:56:36Z","receivedAt":"2013-08-05T22:56:36Z","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> Use the urlmatch_config_entry() to wrap the underlying\n> http_options() two-level variable parser in order to set\n> http.<variable> to the value with the most specific URL in the\n> configuration.\n>\n> Signed-off-by: Jeff King <peff@peff.net>\n> Signed-off-by: Kyle J. McKay <mackyle@gmail.com>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n\nOops, what did we sign-off?\n\n> This version of 4/6 moves the tests to t0110 since urlmatch is now global.\n> The config tests are removed since part 6/6 already has those and they no\n> longer belong with the urlmatch normalization tests.\n>\n> The Makefile rule has been removed since it's no longer needed to build\n> correctly as the test program no longer includes http.c.\n>\n> Other than those changes (and a minor rename to reflect the new location),\n> this patch is identical to the previous v6.v2 4/6.\n\nAhh, figures.  Thanks.\n\nPeff, any comments?\n\n> diff --git a/t/t0110-urlmatch-normalization.sh b/t/t0110-urlmatch-normalization.sh\n> new file mode 100755\n> index 00000000..8d6096d4\n> --- /dev/null\n> +++ b/t/t0110-urlmatch-normalization.sh\n> @@ -0,0 +1,177 @@\n> +#!/bin/sh\n> +\n> +test_description='urlmatch URL normalization'\n> +. ./test-lib.sh\n> +\n> +# The base name of the test url files\n> +tu=\"$TEST_DIRECTORY/t0110/url\"\n> +\n> +# Note that only file: URLs should be allowed without a host\n\nIt is somewhat unfortunate that the form most commonly used for\npushing is not supported at all, i.e.\n\n\thost:path\n\nCurrent configuration set may not have anything interesting to\naffect the git-over-ssh push codepath, so in practice it may not\nmatter, though.\n\n> +test_expect_success 'url authority' '\n\n\"authority\" refers to the host part? (not a complaint, but is a\nquestion)\n\n> +test_expect_success 'url port checks' '\n> +\ttest-urlmatch-normalization \"xyz://q@some.host:\" &&\n\nThis is presumably replaced by a default port for xyz:// scheme,\nwhatever the default port is, in other words, it is as if no colon\nis given at the end?\n\n> +\ttest-urlmatch-normalization \"xyz://q@some.host:456/\" &&\n> +\t! test-urlmatch-normalization \"xyz://q@some.host:0\" &&\n> +\t! test-urlmatch-normalization \"xyz://q@some.host:0000000\" &&\n\nPort #0 is disallowed?\n\n> +\ttest-urlmatch-normalization \"xyz://q@some.host:0000001?\" &&\n\nIs it the same as specifying \"xyz://q@some.host:1?\" and does it\nmatch \"xyz://q@some.host:1\"?\n\n> +\ttest-urlmatch-normalization \"xyz://q@some.host:065535#\" &&\n\nDitto, for 65535 and without #-fragment at the end?\n\n> +test_expect_success 'url port normalization' '\n> +\ttest \"$(test-urlmatch-normalization -p \"http://x:800\")\" = \"http://x:800/\" &&\n> +\ttest \"$(test-urlmatch-normalization -p \"http://x:0800\")\" = \"http://x:800/\" &&\n> +\ttest \"$(test-urlmatch-normalization -p \"http://x:00000800\")\" = \"http://x:800/\" &&\n> +\ttest \"$(test-urlmatch-normalization -p \"http://x:065535\")\" = \"http://x:65535/\" &&\n> +\ttest \"$(test-urlmatch-normalization -p \"http://x:1\")\" = \"http://x:1/\" &&\n> +\ttest \"$(test-urlmatch-normalization -p \"http://x:80\")\" = \"http://x/\" &&\n> +\ttest \"$(test-urlmatch-normalization -p \"http://x:080\")\" = \"http://x/\" &&\n> +\ttest \"$(test-urlmatch-normalization -p \"http://x:000000080\")\" = \"http://x/\" &&\n> +\ttest \"$(test-urlmatch-normalization -p \"https://x:443\")\" = \"https://x/\" &&\n> +\ttest \"$(test-urlmatch-normalization -p \"https://x:0443\")\" = \"https://x/\" &&\n> +\ttest \"$(test-urlmatch-normalization -p \"https://x:000000443\")\" = \"https://x/\"\n> +'\n\nOK, these answer most of the previous questions.\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-urlmatch-normalization \"httP://x\" \"Http://X/\" &&\n> +\ttest-urlmatch-normalization \"Http://%4d%65:%4d^%70@The.Host\" \"hTTP://Me:%4D^p@the.HOST:80/\" &&\n> +\t! test-urlmatch-normalization \"https://@x.y/^\" \"httpS://x.y:443/^\" &&\n\nThe comment is about this test, which seems to make sense.  What is\n\"^\"?  Just a random valid character that can appear in the path?\n(not a complaint, but is a question).\n"},{"id":"224633","messageId":"503E9E2D-93E5-43CB-BB61-499052B35F4F@gmail.com","threadId":"34576","inReplyTo":"7vsiyniw63.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH ALTERNATIVE v6.v3 4/6] config: parse http.<url>.<variable> using urlmatch","fromName":"Kyle J. McKay","fromEmail":"mackyle@gmail.com","sentAt":"2013-08-05T23:57:23Z","receivedAt":"2013-08-05T23:57:23Z","isPatch":true,"sender":{"key":"mackyle@gmail.com","avatar":"https://avatars.githubusercontent.com/u/813346?v=4"},"body":"On Aug 5, 2013, at 15:56, Junio C Hamano wrote:\n> \"Kyle J. McKay\" <mackyle@gmail.com> writes:\n>\n>> Use the urlmatch_config_entry() to wrap the underlying\n>> http_options() two-level variable parser in order to set\n>> http.<variable> to the value with the most specific URL in the\n>> configuration.\n>>\n>> Signed-off-by: Jeff King <peff@peff.net>\n>> Signed-off-by: Kyle J. McKay <mackyle@gmail.com>\n>> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n>> ---\n>\n> Oops, what did we sign-off?\n\nSome code removal.  No new additions.  Actually this:\n\nOn Aug 1, 2013, at 14:44, Junio C Hamano wrote:\n\n> * jc/url-match (2013-07-31) 6 commits\n> - config: \"git config --get-urlmatch\" parses section.<url>.key\n> - builtin/config: refactor collect_config()\n> - config: parse http.<url>.<variable> using urlmatch\n> - config: add generic callback wrapper to parse section.<url>.key\n> - config: add helper to normalize and match URLs\n> - http.c: fix parsing of http.sslCertPasswordProtected variable\n>\n> Reroll of km/http-curl-config-per-url topic.  Peff raises many good\n> points about the tests for http.* variables.\n>\n> Waiting for the discussion to conclude, hopefully with a replacement  \n> test.\n\nAs requested.\n\n>> This version of 4/6 moves the tests to t0110 since urlmatch is now  \n>> global.\n>> The config tests are removed since part 6/6 already has those and  \n>> they no\n>> longer belong with the urlmatch normalization tests.\n>>\n>> The Makefile rule has been removed since it's no longer needed to  \n>> build\n>> correctly as the test program no longer includes http.c.\n>>\n>> Other than those changes (and a minor rename to reflect the new  \n>> location),\n>> this patch is identical to the previous v6.v2 4/6.\n>\n> Ahh, figures.  Thanks.\n\nThe remaining tests, by the way, have not changed.  They are identical  \nto previous versions.\n\n> Peff, any comments?\n>\n>> diff --git a/t/t0110-urlmatch-normalization.sh b/t/t0110-urlmatch- \n>> normalization.sh\n>> new file mode 100755\n>> index 00000000..8d6096d4\n>> --- /dev/null\n>> +++ b/t/t0110-urlmatch-normalization.sh\n>> @@ -0,0 +1,177 @@\n>> +#!/bin/sh\n>> +\n>> +test_description='urlmatch URL normalization'\n>> +. ./test-lib.sh\n>> +\n>> +# The base name of the test url files\n>> +tu=\"$TEST_DIRECTORY/t0110/url\"\n>> +\n>> +# Note that only file: URLs should be allowed without a host\n>\n> It is somewhat unfortunate that the form most commonly used for\n> pushing is not supported at all, i.e.\n>\n> \thost:path\n\nThat is an SSH extension and they are certainly not URLs according to  \nRFC 3986 because that would require every host to be its own scheme.\n\nAlso, host:path cannot in the general case, be unambiguously  \ntranslated to a URL.\n\nFor example, repo.or.cz:srv/git/alt-git, has no translation.  It is  \ndifferent from repo.or.cz:/srv/git/alt-git which does have a  \ntranslation.  There's no guarantee that inserting a '/' will not  \nchange the meaning of the URL (that only happens to be the case on  \nrepo.or.cz because all the ssh git users in the chroot jail have a '/'  \nhome directory).\n\n> Current configuration set may not have anything interesting to\n> affect the git-over-ssh push codepath, so in practice it may not\n> matter, though.\n>\n>> +test_expect_success 'url authority' '\n>\n> \"authority\" refers to the host part? (not a complaint, but is a\n> question)\n\nIt refers to this production from RFC 3986 Section \"3.2 Authority\":\n\nauthority = [ userinfo \"@\" ] host [ \":\" port ]\n\n>> +test_expect_success 'url port checks' '\n>> +\ttest-urlmatch-normalization \"xyz://q@some.host:\" &&\n>\n> This is presumably replaced by a default port for xyz:// scheme,\n> whatever the default port is, in other words, it is as if no colon\n> is given at the end?\n\nYes.\n\nThe \"port\" production above is:\n\nport = *DIGIT\n\nwhich means 0 or more digits.\n\n>> +\ttest-urlmatch-normalization \"xyz://q@some.host:456/\" &&\n>> +\t! test-urlmatch-normalization \"xyz://q@some.host:0\" &&\n>> +\t! test-urlmatch-normalization \"xyz://q@some.host:0000000\" &&\n>\n> Port #0 is disallowed?\n\nIntentionally so.\n\nThe comments from urlmatch.c talk about this:\n\n/*\n  * Port number must be all digits with leading 0s removed\n  * and since all the protocols we deal with have a 16-bit\n  * port number it must also be in the range 1..65535\n  * 0 is not allowed because that means \"next available\"\n  * on just about every system and therefore cannot be used\n  */\n\n>> +\ttest-urlmatch-normalization \"xyz://q@some.host:0000001?\" &&\n>\n> Is it the same as specifying \"xyz://q@some.host:1?\" and does it\n> match \"xyz://q@some.host:1\"?\n>\n>> +\ttest-urlmatch-normalization \"xyz://q@some.host:065535#\" &&\n>\n> Ditto, for 65535 and without #-fragment at the end?\n>\n>> +test_expect_success 'url port normalization' '\n>> +\ttest \"$(test-urlmatch-normalization -p \"http://x:800\")\" = \"http:// \n>> x:800/\" &&\n>> +\ttest \"$(test-urlmatch-normalization -p \"http://x:0800\")\" =  \n>> \"http://x:800/\" &&\n>> +\ttest \"$(test-urlmatch-normalization -p \"http://x:00000800\")\" =  \n>> \"http://x:800/\" &&\n>> +\ttest \"$(test-urlmatch-normalization -p \"http://x:065535\")\" =  \n>> \"http://x:65535/\" &&\n>> +\ttest \"$(test-urlmatch-normalization -p \"http://x:1\")\" = \"http://x: \n>> 1/\" &&\n>> +\ttest \"$(test-urlmatch-normalization -p \"http://x:80\")\" = \"http:// \n>> x/\" &&\n>> +\ttest \"$(test-urlmatch-normalization -p \"http://x:080\")\" = \"http:// \n>> x/\" &&\n>> +\ttest \"$(test-urlmatch-normalization -p \"http://x:000000080\")\" =  \n>> \"http://x/\" &&\n>> +\ttest \"$(test-urlmatch-normalization -p \"https://x:443\")\" = \"https://x/ \n>> \" &&\n>> +\ttest \"$(test-urlmatch-normalization -p \"https://x:0443\")\" = \"https://x/ \n>> \" &&\n>> +\ttest \"$(test-urlmatch-normalization -p \"https://x:000000443\")\" = \"https://x/ \n>> \"\n>> +'\n>\n> OK, these answer most of the previous questions.\n>\n>> +# http://@foo specifies an empty user name but does not specify a  \n>> 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-urlmatch-normalization \"httP://x\" \"Http://X/\" &&\n>> +\ttest-urlmatch-normalization \"Http://%4d%65:%4d^%70@The.Host\" \"hTTP://Me \n>> :%4D^p@the.HOST:80/\" &&\n>> +\t! test-urlmatch-normalization \"https://@x.y/^\" \"httpS://x.y:443/ \n>> ^\" &&\n>\n> The comment is about this test, which seems to make sense.  What is\n> \"^\"?  Just a random valid character that can appear in the path?\n> (not a complaint, but is a question).\n\nThe character '^' is one of the always-unsafe characters that must  \nalways be escaped.  It's also one of the always-unsafe characters  \nthat's easy to include in the tests as it doesn't require escaping or  \nbackslashing or binary includes.  It doesn't otherwise have any  \nspecial meaning.\n"}]}