* [PATCH v5 0/5] config: add support for http.<url>.* settings
@ 2013-07-15 9:50 Kyle J. McKay
2013-07-15 9:50 ` [PATCH v5 1/5] http.c: fix parsing of http.sslCertPasswordProtected variable Kyle J. McKay
` (4 more replies)
0 siblings, 5 replies; 9+ messages in thread
From: Kyle J. McKay @ 2013-07-15 9:50 UTC (permalink / raw)
To: git
Cc: David Aguilar, Petr Baudis, Junio C Hamano, Richard Hartmann,
Jeff King, Daniel Knittl-Frank, Jan Krüger, Alejandro Mery,
Aaron Schrab
This patch series adds support for url-specific http.* settings.
It has been suggested that a preparatory patch to address the way the
http.sslCertPasswordProtected variable is handled be included.
I'm not sure if the intent was to make that a separate patch or include it
in a patch series together with the http.<url>.* settings updates. I have,
however, included it here this time along with a second patch that does the
same thing for the GIT_SSL_CERT_PASSWORD_PROTECTED environment variable.
There is talk about possibly altering GIT_SSL_NO_VERIFY, GIT_CURL_FTP_NO_EPSV
and GIT_CURL_VERBOSE to behave similarly. No patch is included here for that.
The http.<url>.* settings support addresses various feedback. To better
support matching URLs that are equivalent but spelled differently, a
url_normalize function has been added. Currently this patch leaves it in
http.c as http_options_url_normalize as I am unclear whether it should go into
url.{h,c} at this time since only http.c uses.
Since the url_normalize function's behavior is non-trivial, it is presented as
a separate patch on top of the basic http.<url>.* settings support. A new test
for it has also been included as the final patch. I am unclear on the proper
number for this test, but have gone ahead and put it with the other http tests
since this patch series places the url_normalize function into http.c.
Junio C Hamano (1):
http.c: fix parsing of http.sslCertPasswordProtected variable
Kyle J. McKay (4):
http.c: fix parsing of GIT_SSL_CERT_PASSWORD_PROTECTED variable
config: add support for http.<url>.* settings
config: improve support for http.<url>.* settings
tests: add new test for the url_normalize function
.gitignore | 1 +
Documentation/config.txt | 14 ++
Makefile | 5 +
http.c | 397 ++++++++++++++++++++++++++++++++++++++++++++---
t/t5200-url-normalize.sh | 109 +++++++++++++
test-url-normalize.c | 62 ++++++++
6 files changed, 565 insertions(+), 23 deletions(-)
create mode 100755 t/t5200-url-normalize.sh
create mode 100644 test-url-normalize.c
--
1.8.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 1/5] http.c: fix parsing of http.sslCertPasswordProtected variable
2013-07-15 9:50 [PATCH v5 0/5] config: add support for http.<url>.* settings Kyle J. McKay
@ 2013-07-15 9:50 ` Kyle J. McKay
2013-07-15 9:50 ` [PATCH v5 2/5] http.c: fix parsing of GIT_SSL_CERT_PASSWORD_PROTECTED variable Kyle J. McKay
` (3 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Kyle J. McKay @ 2013-07-15 9:50 UTC (permalink / raw)
To: git
Cc: David Aguilar, Petr Baudis, Junio C Hamano, Richard Hartmann,
Jeff King, Daniel Knittl-Frank, Jan Krüger, Alejandro Mery,
Aaron Schrab
From: Junio C Hamano <gitster@pobox.com>
The existing code triggers only when the configuration variable is
set to true. Once the variable is set to true in a more generic
configuration file (e.g. ~/.gitconfig), it cannot be overriden to
false in the repository specific one (e.g. .git/config).
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
http.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/http.c b/http.c
index 2d086ae..e65661e 100644
--- a/http.c
+++ b/http.c
@@ -160,8 +160,7 @@ static int http_options(const char *var, const char *value, void *cb)
if (!strcmp("http.sslcainfo", var))
return git_config_string(&ssl_cainfo, var, value);
if (!strcmp("http.sslcertpasswordprotected", var)) {
- if (git_config_bool(var, value))
- ssl_cert_password_required = 1;
+ ssl_cert_password_required = git_config_bool(var, value);
return 0;
}
if (!strcmp("http.ssltry", var)) {
--
1.8.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v5 2/5] http.c: fix parsing of GIT_SSL_CERT_PASSWORD_PROTECTED variable
2013-07-15 9:50 [PATCH v5 0/5] config: add support for http.<url>.* settings Kyle J. McKay
2013-07-15 9:50 ` [PATCH v5 1/5] http.c: fix parsing of http.sslCertPasswordProtected variable Kyle J. McKay
@ 2013-07-15 9:50 ` Kyle J. McKay
2013-07-15 9:51 ` [PATCH v5 3/5] config: add support for http.<url>.* settings Kyle J. McKay
` (2 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Kyle J. McKay @ 2013-07-15 9:50 UTC (permalink / raw)
To: git
Cc: David Aguilar, Petr Baudis, Junio C Hamano, Richard Hartmann,
Jeff King, Daniel Knittl-Frank, Jan Krüger, Alejandro Mery,
Aaron Schrab
The existing code triggers whenever GIT_SSL_CERT_PASSWORD_PROTECTED
is defined. Setting GIT_SSL_CERT_PASSWORD_PROTECTED to a false
value could not be used to override the http.sslCertPasswordProtected
setting once it had been turned on.
Signed-off-by: Kyle J. McKay <mackyle@gmail.com>
---
http.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/http.c b/http.c
index e65661e..9542a59 100644
--- a/http.c
+++ b/http.c
@@ -403,11 +403,10 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
curl_ftp_no_epsv = 1;
if (url) {
+ int pwdreq = git_env_bool("GIT_SSL_CERT_PASSWORD_PROTECTED", -1);
credential_from_url(&http_auth, url);
- if (!ssl_cert_password_required &&
- getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") &&
- !prefixcmp(url, "https://"))
- ssl_cert_password_required = 1;
+ if (pwdreq != -1 && !prefixcmp(url, "https://"))
+ ssl_cert_password_required = pwdreq;
}
#ifndef NO_CURL_EASY_DUPHANDLE
--
1.8.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v5 3/5] config: add support for http.<url>.* settings
2013-07-15 9:50 [PATCH v5 0/5] config: add support for http.<url>.* settings Kyle J. McKay
2013-07-15 9:50 ` [PATCH v5 1/5] http.c: fix parsing of http.sslCertPasswordProtected variable Kyle J. McKay
2013-07-15 9:50 ` [PATCH v5 2/5] http.c: fix parsing of GIT_SSL_CERT_PASSWORD_PROTECTED variable Kyle J. McKay
@ 2013-07-15 9:51 ` Kyle J. McKay
2013-07-15 9:51 ` [PATCH v5 4/5] config: improve " Kyle J. McKay
2013-07-15 9:51 ` [PATCH v5 5/5] tests: add new test for the url_normalize function Kyle J. McKay
4 siblings, 0 replies; 9+ messages in thread
From: Kyle J. McKay @ 2013-07-15 9:51 UTC (permalink / raw)
To: git
Cc: David Aguilar, Petr Baudis, Junio C Hamano, Richard Hartmann,
Jeff King, Daniel Knittl-Frank, Jan Krüger, Alejandro Mery,
Aaron Schrab
The credentials configuration values already support url-specific
configuration items in the form credential.<url>.*. This patch
adds similar support for http configuration values.
The <url> value is considered a match to a url if the <url> value
is either an exact match or a prefix of the url which ends on a
path component boundary ('/'). So "https://example.com/test" will
match "https://example.com/test" and "https://example.com/test/too"
but not "https://example.com/testextra".
Longer matches take precedence over shorter matches with
environment variable settings taking precedence over all.
With this configuration:
[http]
useragent = other-agent
noEPSV = true
[http "https://example.com"]
useragent = example-agent
sslVerify = false
[http "https://example.com/path"]
useragent = path-agent
The "https://other.example.com/" url will have useragent
"other-agent" and sslVerify will be on.
The "https://example.com/" url will have useragent
"example-agent" and sslVerify will be off.
The "https://example.com/path/sub" url will have useragent
"path-agent" and sslVerify will be off.
All three of the examples will have noEPSV enabled.
Signed-off-by: Kyle J. McKay <mackyle@gmail.com>
---
Documentation/config.txt | 15 +++++
http.c | 169 ++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 167 insertions(+), 17 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 81856dd..ceced99 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1531,6 +1531,21 @@ http.useragent::
of common USER_AGENT strings (but not including those like git/1.7.1).
Can be overridden by the 'GIT_HTTP_USER_AGENT' environment variable.
+http.<url>.*::
+ Any of the http.* options above can be applied selectively to some urls.
+ For example "http.https://example.com.useragent" would set the user
+ agent only for https connections to example.com. The <url> value
+ matches a url if it is an exact match or a prefix of the url matching
+ at a "/" boundary. Longer <url> matches take precedence over shorter
+ ones with the environment variable settings taking precedence over all.
+ Note that <url> must match the url passed to git exactly (other than
+ possibly being a prefix). This means any user, password and/or port
+ setting that appears in a url as well as any %XX escapes that are
+ present must also appear in <url> to have a successful match. The urls
+ that are matched against are those given directly to git commands. In
+ other words, use exactly the same url that was passed to git (possibly
+ shortened) for the <url> value of the config setting.
+
i18n.commitEncoding::
Character encoding the commit messages are stored in; Git itself
does not care per se, but this information is necessary e.g. when
diff --git a/http.c b/http.c
index 9542a59..758e5b1 100644
--- a/http.c
+++ b/http.c
@@ -30,6 +30,34 @@ static CURL *curl_default;
char curl_errorstr[CURL_ERROR_SIZE];
+enum http_option_type {
+ OPT_POST_BUFFER,
+ OPT_MIN_SESSIONS,
+ OPT_SSL_VERIFY,
+ OPT_SSL_TRY,
+ OPT_SSL_CERT,
+ OPT_SSL_CAINFO,
+ OPT_LOW_SPEED,
+ OPT_LOW_TIME,
+ OPT_NO_EPSV,
+ OPT_HTTP_PROXY,
+ OPT_COOKIE_FILE,
+ OPT_USER_AGENT,
+ OPT_PASSWD_REQ,
+#ifdef USE_CURL_MULTI
+ OPT_MAX_REQUESTS,
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070903
+ OPT_SSL_KEY,
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+ OPT_SSL_CAPATH,
+#endif
+ OPT_MAX
+};
+
+static size_t http_option_max_matched_len[OPT_MAX];
+
static int curl_ssl_verify = -1;
static int curl_ssl_try;
static const char *ssl_cert;
@@ -141,33 +169,121 @@ static void process_curl_messages(void)
}
#endif
+static size_t http_options_url_match_prefix(const char *url,
+ const char *url_prefix,
+ size_t url_prefix_len)
+{
+ /*
+ * url_prefix matches url if url_prefix is an exact match for url or it
+ * is a prefix of url and the match ends on a path component boundary.
+ * Both url and url_prefix are considered to have an implicit '/' on the
+ * end for matching purposes if they do not already.
+ *
+ * url must be NUL terminated. url_prefix_len is the length of
+ * url_prefix which need not be NUL terminated.
+ *
+ * The return value is the length of the match in characters (excluding
+ * any final '/') or 0 for no match. Passing "/" as url_prefix will
+ * always cause 0 to be returned.
+ */
+ size_t url_len;
+ if (url_prefix_len && url_prefix[url_prefix_len - 1] == '/')
+ url_prefix_len--;
+ if (!url_prefix_len || strncmp(url, url_prefix, url_prefix_len))
+ return 0;
+ url_len = strlen(url);
+ if ((url_len == url_prefix_len) || (url[url_prefix_len] == '/'))
+ return url_prefix_len;
+ return 0;
+}
+
+static int new_match_is_shorter(size_t matchlen, enum http_option_type opt)
+{
+ /*
+ * Compare matchlen to the last matched length of option opt and
+ * return true if matchlen is shorter than the last matched length
+ * (meaning the config setting should be ignored). Upon seeing the
+ * _same_ key (i.e. new key has the same match length which is therefore
+ * not shorter) the new setting will override the previous setting.
+ * Otherwise return false and record matchlen as the current last
+ * matched length of option opt.
+ */
+ if (matchlen < http_option_max_matched_len[opt])
+ return 1;
+ http_option_max_matched_len[opt] = matchlen;
+ return 0;
+}
+
static int http_options(const char *var, const char *value, void *cb)
{
- if (!strcmp("http.sslverify", var)) {
+ const char *url = cb;
+ const char *key, *dot;
+ size_t matchlen = 0;
+
+ key = skip_prefix(var, "http.");
+ if (!key)
+ return git_default_config(var, value, cb);
+
+ /*
+ * If the part following the leading "http." contains a '.' then check
+ * to see if the part between "http." and the last '.' is a match to
+ * url. If it's not then ignore the setting. Otherwise set key to
+ * point to the option which is the part after the final '.' and
+ * use key in subsequent comparisons to determine the option type.
+ */
+ dot = strrchr(key, '.');
+ if (dot) {
+ matchlen = http_options_url_match_prefix(url, key, dot - key);
+ if (!matchlen)
+ return 0;
+ key = dot + 1;
+ }
+
+ if (!strcmp("sslverify", key)) {
+ if (new_match_is_shorter(matchlen, OPT_SSL_VERIFY))
+ return 0;
curl_ssl_verify = git_config_bool(var, value);
return 0;
}
- if (!strcmp("http.sslcert", var))
+ if (!strcmp("sslcert", key)) {
+ if (new_match_is_shorter(matchlen, OPT_SSL_CERT))
+ return 0;
return git_config_string(&ssl_cert, var, value);
+ }
#if LIBCURL_VERSION_NUM >= 0x070903
- if (!strcmp("http.sslkey", var))
+ if (!strcmp("sslkey", key)) {
+ if (new_match_is_shorter(matchlen, OPT_SSL_KEY))
+ return 0;
return git_config_string(&ssl_key, var, value);
+ }
#endif
#if LIBCURL_VERSION_NUM >= 0x070908
- if (!strcmp("http.sslcapath", var))
+ if (!strcmp("sslcapath", key)) {
+ if (new_match_is_shorter(matchlen, OPT_SSL_CAPATH))
+ return 0;
return git_config_string(&ssl_capath, var, value);
+ }
#endif
- if (!strcmp("http.sslcainfo", var))
+ if (!strcmp("sslcainfo", key)) {
+ if (new_match_is_shorter(matchlen, OPT_SSL_CAINFO))
+ return 0;
return git_config_string(&ssl_cainfo, var, value);
- if (!strcmp("http.sslcertpasswordprotected", var)) {
+ }
+ if (!strcmp("sslcertpasswordprotected", key)) {
+ if (new_match_is_shorter(matchlen, OPT_PASSWD_REQ))
+ return 0;
ssl_cert_password_required = git_config_bool(var, value);
return 0;
}
- if (!strcmp("http.ssltry", var)) {
+ if (!strcmp("ssltry", key)) {
+ if (new_match_is_shorter(matchlen, OPT_SSL_TRY))
+ return 0;
curl_ssl_try = git_config_bool(var, value);
return 0;
}
- if (!strcmp("http.minsessions", var)) {
+ if (!strcmp("minsessions", key)) {
+ if (new_match_is_shorter(matchlen, OPT_MIN_SESSIONS))
+ return 0;
min_curl_sessions = git_config_int(var, value);
#ifndef USE_CURL_MULTI
if (min_curl_sessions > 1)
@@ -176,39 +292,58 @@ static int http_options(const char *var, const char *value, void *cb)
return 0;
}
#ifdef USE_CURL_MULTI
- if (!strcmp("http.maxrequests", var)) {
+ if (!strcmp("maxrequests", key)) {
+ if (new_match_is_shorter(matchlen, OPT_MAX_REQUESTS))
+ return 0;
max_requests = git_config_int(var, value);
return 0;
}
#endif
- if (!strcmp("http.lowspeedlimit", var)) {
+ if (!strcmp("lowspeedlimit", key)) {
+ if (new_match_is_shorter(matchlen, OPT_LOW_SPEED))
+ return 0;
curl_low_speed_limit = (long)git_config_int(var, value);
return 0;
}
- if (!strcmp("http.lowspeedtime", var)) {
+ if (!strcmp("lowspeedtime", key)) {
+ if (new_match_is_shorter(matchlen, OPT_LOW_TIME))
+ return 0;
curl_low_speed_time = (long)git_config_int(var, value);
return 0;
}
- if (!strcmp("http.noepsv", var)) {
+ if (!strcmp("noepsv", key)) {
+ if (new_match_is_shorter(matchlen, OPT_NO_EPSV))
+ return 0;
curl_ftp_no_epsv = git_config_bool(var, value);
return 0;
}
- if (!strcmp("http.proxy", var))
+ if (!strcmp("proxy", key)) {
+ if (new_match_is_shorter(matchlen, OPT_HTTP_PROXY))
+ return 0;
return git_config_string(&curl_http_proxy, var, value);
+ }
- if (!strcmp("http.cookiefile", var))
+ if (!strcmp("cookiefile", key)) {
+ if (new_match_is_shorter(matchlen, OPT_COOKIE_FILE))
+ return 0;
return git_config_string(&curl_cookie_file, var, value);
+ }
- if (!strcmp("http.postbuffer", var)) {
+ if (!strcmp("postbuffer", key)) {
+ if (new_match_is_shorter(matchlen, OPT_POST_BUFFER))
+ return 0;
http_post_buffer = git_config_int(var, value);
if (http_post_buffer < LARGE_PACKET_MAX)
http_post_buffer = LARGE_PACKET_MAX;
return 0;
}
- if (!strcmp("http.useragent", var))
+ if (!strcmp("useragent", key)) {
+ if (new_match_is_shorter(matchlen, OPT_USER_AGENT))
+ return 0;
return git_config_string(&user_agent, var, value);
+ }
/* Fall back on the default ones */
return git_default_config(var, value, cb);
@@ -343,7 +478,7 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
http_is_verbose = 0;
- git_config(http_options, NULL);
+ git_config(http_options, (void *)url);
curl_global_init(CURL_GLOBAL_ALL);
--
1.8.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v5 4/5] config: improve support for http.<url>.* settings
2013-07-15 9:50 [PATCH v5 0/5] config: add support for http.<url>.* settings Kyle J. McKay
` (2 preceding siblings ...)
2013-07-15 9:51 ` [PATCH v5 3/5] config: add support for http.<url>.* settings Kyle J. McKay
@ 2013-07-15 9:51 ` Kyle J. McKay
2013-07-15 23:12 ` Eric Sunshine
2013-07-15 9:51 ` [PATCH v5 5/5] tests: add new test for the url_normalize function Kyle J. McKay
4 siblings, 1 reply; 9+ messages in thread
From: Kyle J. McKay @ 2013-07-15 9:51 UTC (permalink / raw)
To: git
Cc: David Aguilar, Petr Baudis, Junio C Hamano, Richard Hartmann,
Jeff King, Daniel Knittl-Frank, Jan Krüger, Alejandro Mery,
Aaron Schrab
Improve on the http.<url>.* url matching behavior by first
normalizing the urls before they are compared.
With this change, for example, the following configuration
section:
[http "https://example.com/path"]
useragent = example-agent
sslVerify = false
will properly match a "HTTPS://example.COM/p%61th" url which
is equivalent.
The normalization rules are based on RFC 3986 and should
result in any two equivalent urls being a match.
Signed-off-by: Kyle J. McKay <mackyle@gmail.com>
---
Documentation/config.txt | 19 ++--
http.c | 222 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 229 insertions(+), 12 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index ceced99..98eee23 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1535,16 +1535,15 @@ http.<url>.*::
Any of the http.* options above can be applied selectively to some urls.
For example "http.https://example.com.useragent" would set the user
agent only for https connections to example.com. The <url> value
- matches a url if it is an exact match or a prefix of the url matching
- at a "/" boundary. Longer <url> matches take precedence over shorter
- ones with the environment variable settings taking precedence over all.
- Note that <url> must match the url passed to git exactly (other than
- possibly being a prefix). This means any user, password and/or port
- setting that appears in a url as well as any %XX escapes that are
- present must also appear in <url> to have a successful match. The urls
- that are matched against are those given directly to git commands. In
- other words, use exactly the same url that was passed to git (possibly
- shortened) for the <url> value of the config setting.
+ matches a url if it is an exact match or if it is a prefix of the url
+ matching at a "/" boundary. Longer <url> matches take precedence over
+ shorter ones with the environment variable settings taking precedence
+ over all. The urls are normalized before testing for a match. Note,
+ however, that any user, password and/or port setting that appears in a
+ url must also match that part of <url> to have a successful match. The
+ urls that are matched against are those given directly to git commands.
+ This means any urls visited as a result of a redirection do not
+ participate in matching.
i18n.commitEncoding::
Character encoding the commit messages are stored in; Git itself
diff --git a/http.c b/http.c
index 758e5b1..d04386e 100644
--- a/http.c
+++ b/http.c
@@ -169,6 +169,210 @@ static void process_curl_messages(void)
}
#endif
+#define URL_ALPHA "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+#define URL_DIGIT "0123456789"
+#define URL_HEXDIGIT URL_DIGIT "ABCDEFabcdef"
+#define URL_ALPHADIGIT URL_ALPHA URL_DIGIT
+#define URL_SCHEME_CHARS URL_ALPHADIGIT "+.-"
+#define URL_HOST_CHARS URL_ALPHADIGIT ".-[:]" /* IPv6 literals need [:] */
+#define URL_UNSAFE_CHARS " <>\"%{}|\\^`" /* plus 0x00-0x1F,0x7F-0xFF */
+#define URL_GEN_RESERVED ":/?#[]@"
+#define URL_SUB_RESERVED "!$&'()*+,;="
+#define URL_RESERVED URL_GEN_RESERVED URL_SUB_RESERVED /* only allowed delims */
+
+static int hex_digit_value(int ch)
+{
+ /*
+ * Returns the hexadecimal digit value (0x00..0x0F) of character ch.
+ * If ch is not a hexadecimal digit, the return value is undefined.
+ */
+
+ if ('0' <= ch && ch <= '9')
+ return ch - '0';
+ return toupper(ch) - 'A' + 10;
+}
+
+static int append_normalized_escapes(struct strbuf *buf,
+ const char *from,
+ size_t from_len,
+ const char *esc_extra,
+ const char *esc_ok)
+{
+ /*
+ * Append to strbuf buf characters from string from with length from_len
+ * while unescaping characters that do not need to be escaped and
+ * escaping characters that do. The set of characters to escape (the
+ * complement of which is unescaped) starts out as the RFC 3986 unsafe
+ * characters (0x00-0x1F,0x7F-0xFF," <>\"#%{}|\\^`"). If esc_extra
+ * is not NULL, those additional characters will also always be escaped.
+ * If esc_ok is not NULL, those characters will be left escaped if found
+ * that way, but will not be unescaped otherwise (used for delimiters).
+ * If a %-escape sequence is encountered that is not followed by 2
+ * hexadecimal digits, the sequence is invalid and false (0) will be
+ * returned. Otherwise true (1) will be returned for success.
+ *
+ * Note that all %-escape sequences will be normalized to UPPERCASE
+ * as indicated in RFC 3986. Unless included in esc_extra or esc_ok
+ * alphanumerics and "-._~" will always be unescaped as per RFC 3986.
+ */
+
+ while (from_len) {
+ int ch = *from++;
+ int was_esc = 0;
+
+ from_len--;
+ if (ch == '%') {
+ if (from_len < 2 ||
+ !strchr(URL_HEXDIGIT, from[0]) ||
+ !strchr(URL_HEXDIGIT, from[1]))
+ return 0;
+ ch = hex_digit_value(*from++) << 4;
+ ch |= hex_digit_value(*from++);
+ from_len -= 2;
+ was_esc = 1;
+ }
+ if (ch <= 0x1F || ch >= 0x7F || strchr(URL_UNSAFE_CHARS, ch) ||
+ (esc_extra && strchr(esc_extra, ch)) ||
+ (was_esc && strchr(esc_ok, ch)))
+ strbuf_addf(buf, "%%%02X", ch);
+ else
+ strbuf_addch(buf, ch);
+ }
+
+ return 1;
+}
+
+static char *http_options_url_normalize(const char *url, size_t *out_len)
+{
+ /*
+ * Normalize NUL-terminated url using the following rules:
+ *
+ * 1. Case-insensitive parts of url will be converted to lower case
+ * 2. %-encoded characters that do not need to be will be unencoded
+ * 3. Characters that are not %-encoded and must be will be encoded
+ * 4. All %-encodings will be converted to upper case hexadecimal
+ * 5. If the default port for the scheme is given it will be removed
+ * 6. If the user:password part is given and empty it will be removed
+ * 7. A path part (including empty) not starting with '/' has one added
+ * 8. IPv6 host literals are allowed (but not normalized or validated)
+ *
+ * The rules are based on information in RFC 3986.
+ *
+ * Please note this function requires a full URL including a scheme
+ * and host part (except for file: URLs which may have an empty host).
+ *
+ * The return value is a newly allocated string that must be freed
+ * or NULL if the url is not valid.
+ *
+ * If a non-NULL value is returned and out_len is not NULL then the
+ * length of the result will be stored in *out_len.
+ *
+ * This is NOT a URL validation function. Full URL validation is NOT
+ * performed. Some invalid host names are passed through this function
+ * undetected. Invalid port numbers may also be passed through
+ * undetected. However, most all other problems that make a URL invalid
+ * will be detected (including a missing host for non file: URLs).
+ */
+
+ size_t url_len = strlen(url);
+ struct strbuf norm;
+ size_t spanned;
+ const char *slash_ptr, *at_ptr, *colon_ptr;
+
+ /* Copy lowercased scheme and :// suffix, %-escapes are not allowed */
+ spanned = strspn(url, URL_SCHEME_CHARS);
+ if (!spanned || spanned + 3 > url_len || url[spanned] != ':' ||
+ url[spanned+1] != '/' || url[spanned+2] != '/')
+ return NULL; /* Bad scheme and/or missing "://" part */
+ strbuf_init(&norm, url_len);
+ spanned += 3;
+ url_len -= spanned;
+ while (spanned--)
+ strbuf_addch(&norm, tolower(*url++));
+
+ /* Copy any username:password if present normalizing %-escapes */
+ at_ptr = strchr(url, '@');
+ slash_ptr = url + strcspn(url, "/?#");
+ if (at_ptr && at_ptr < slash_ptr) {
+ if (at_ptr > url) {
+ if (!append_normalized_escapes(&norm, url, at_ptr - url,
+ "", URL_RESERVED)) {
+ strbuf_release(&norm);
+ return NULL;
+ }
+ strbuf_addch(&norm, '@');
+ }
+ url_len -= (++at_ptr - url);
+ url = at_ptr;
+ }
+
+ /* Copy the host part excluding any port part, no %-escapes allowed */
+ if (!url_len || strchr(":/?#", *url)) {
+ /* Missing host invalid for all URL schemes except file */
+ if (strncmp(norm.buf, "file:", 5)) {
+ strbuf_release(&norm);
+ return NULL;
+ }
+ }
+ colon_ptr = slash_ptr - 1;
+ while (colon_ptr > url && *colon_ptr != ':' && *colon_ptr != ']')
+ colon_ptr--;
+ if (*colon_ptr != ':')
+ colon_ptr = slash_ptr;
+ spanned = strspn(url, URL_HOST_CHARS);
+ if (spanned < colon_ptr - url) {
+ /* Host name has invalid characters */
+ strbuf_release(&norm);
+ return NULL;
+ }
+ while (url < colon_ptr) {
+ strbuf_addch(&norm, tolower(*url++));
+ url_len--;
+ }
+
+ /* Check the port part and copy if not the default, no %-escapes */
+ if (colon_ptr < slash_ptr) {
+ if (colon_ptr + 1 == slash_ptr) {
+ /* Skip ":" port with no number, it's same as default */
+ } else if (slash_ptr - colon_ptr == 3 &&
+ !strncmp(norm.buf, "http:", 5) &&
+ !strncmp(url, ":80", 3)) {
+ /* Skip http :80 as it's the default */
+ } else if (slash_ptr - colon_ptr == 4 &&
+ !strncmp(norm.buf, "https:", 6) &&
+ !strncmp(url, ":443", 4)) {
+ /* Skip https :443 as it's the default */
+ } else {
+ /* Port number must be all digits */
+ spanned = strspn(url + 1, URL_DIGIT);
+ if (spanned + 1 < slash_ptr - colon_ptr) {
+ /* port number has invalid characters */
+ strbuf_release(&norm);
+ return NULL;
+ }
+ strbuf_add(&norm, url, slash_ptr - colon_ptr);
+ }
+ url_len -= slash_ptr - colon_ptr;
+ url = slash_ptr;
+ }
+
+ /*
+ * Now simply copy the rest, if any, only normalizing %-escapes and
+ * being careful to not corrupt the URL by unescaping any delimiters,
+ * but do add an initial '/' if it's missing
+ */
+ if (*url != '/')
+ strbuf_addch(&norm, '/');
+ if (*url) {
+ if (!append_normalized_escapes(&norm, url, url_len, "", URL_RESERVED)) {
+ strbuf_release(&norm);
+ return NULL;
+ }
+ }
+
+ return strbuf_detach(&norm, out_len);
+}
+
static size_t http_options_url_match_prefix(const char *url,
const char *url_prefix,
size_t url_prefix_len)
@@ -185,8 +389,13 @@ static size_t http_options_url_match_prefix(const char *url,
* The return value is the length of the match in characters (excluding
* any final '/') or 0 for no match. Passing "/" as url_prefix will
* always cause 0 to be returned.
+ *
+ * Passing NULL as url and/or url_prefix will always cause 0 to be
+ * returned without causing any faults.
*/
size_t url_len;
+ if (!url || !url_prefix)
+ return 0;
if (url_prefix_len && url_prefix[url_prefix_len - 1] == '/')
url_prefix_len--;
if (!url_prefix_len || strncmp(url, url_prefix, url_prefix_len))
@@ -233,7 +442,14 @@ static int http_options(const char *var, const char *value, void *cb)
*/
dot = strrchr(key, '.');
if (dot) {
- matchlen = http_options_url_match_prefix(url, key, dot - key);
+ char *config_url = xmemdupz(key, dot - key);
+ size_t norm_url_len;
+ char *norm_url = http_options_url_normalize(config_url, &norm_url_len);
+ free(config_url);
+ if (!norm_url)
+ return 0;
+ matchlen = http_options_url_match_prefix(url, norm_url, norm_url_len);
+ free(norm_url);
if (!matchlen)
return 0;
key = dot + 1;
@@ -475,10 +691,12 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
{
char *low_speed_limit;
char *low_speed_time;
+ char *norm_url = http_options_url_normalize(url, NULL);
http_is_verbose = 0;
- git_config(http_options, (void *)url);
+ git_config(http_options, norm_url);
+ free(norm_url);
curl_global_init(CURL_GLOBAL_ALL);
--
1.8.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v5 5/5] tests: add new test for the url_normalize function
2013-07-15 9:50 [PATCH v5 0/5] config: add support for http.<url>.* settings Kyle J. McKay
` (3 preceding siblings ...)
2013-07-15 9:51 ` [PATCH v5 4/5] config: improve " Kyle J. McKay
@ 2013-07-15 9:51 ` Kyle J. McKay
4 siblings, 0 replies; 9+ messages in thread
From: Kyle J. McKay @ 2013-07-15 9:51 UTC (permalink / raw)
To: git
Cc: David Aguilar, Petr Baudis, Junio C Hamano, Richard Hartmann,
Jeff King, Daniel Knittl-Frank, Jan Krüger, Alejandro Mery,
Aaron Schrab
In order to perform sane URL matching for http.<url>.* options,
http.c normalizes URLs before performing matches.
A new test-url-normalize test program is introduced along with
a new t5200-url-normalize.sh script to run the tests.
Since the url_normalize function currently lives in http.c this
test will be skipped if NO_CURL is defined since http.c is skipped
in that case.
Signed-off-by: Kyle J. McKay <mackyle@gmail.com>
---
.gitignore | 1 +
Makefile | 5 +++
t/t5200-url-normalize.sh | 109 +++++++++++++++++++++++++++++++++++++++++++++++
test-url-normalize.c | 62 +++++++++++++++++++++++++++
4 files changed, 177 insertions(+)
create mode 100755 t/t5200-url-normalize.sh
create mode 100644 test-url-normalize.c
diff --git a/.gitignore b/.gitignore
index efa8db0..b60a9df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -201,6 +201,7 @@
/test-string-list
/test-subprocess
/test-svn-fe
+/test-url-normalize
/test-wildmatch
/common-cmds.h
*.tar.gz
diff --git a/Makefile b/Makefile
index 0600eb4..195c62a 100644
--- a/Makefile
+++ b/Makefile
@@ -580,6 +580,7 @@ TEST_PROGRAMS_NEED_X += test-sigchain
TEST_PROGRAMS_NEED_X += test-string-list
TEST_PROGRAMS_NEED_X += test-subprocess
TEST_PROGRAMS_NEED_X += test-svn-fe
+TEST_PROGRAMS_NEED_X += test-url-normalize
TEST_PROGRAMS_NEED_X += test-wildmatch
TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
@@ -2278,6 +2279,10 @@ test-parse-options$X: parse-options.o parse-options-cb.o
test-svn-fe$X: vcs-svn/lib.a
+test-url-normalize$X: test-url-normalize.o GIT-LDFLAGS $(GITLIBS)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+
.PRECIOUS: $(TEST_OBJS)
test-%$X: test-%.o GIT-LDFLAGS $(GITLIBS)
diff --git a/t/t5200-url-normalize.sh b/t/t5200-url-normalize.sh
new file mode 100755
index 0000000..40ebcd3
--- /dev/null
+++ b/t/t5200-url-normalize.sh
@@ -0,0 +1,109 @@
+#!/bin/sh
+
+test_description='url normalization'
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+ skip_all='skipping test, git built without http support'
+ test_done
+fi
+
+# Note that only file: URLs should be allowed without a host
+
+test_expect_success 'url scheme' '
+ ! test-url-normalize "" &&
+ ! test-url-normalize "_" &&
+ ! test-url-normalize "scheme" &&
+ ! test-url-normalize "scheme:" &&
+ ! test-url-normalize "scheme:/" &&
+ ! test-url-normalize "scheme://" &&
+ ! test-url-normalize "file" &&
+ ! test-url-normalize "file:" &&
+ ! test-url-normalize "file:/" &&
+ test-url-normalize "file://" &&
+ ! test-url-normalize "://acme.co" &&
+ ! test-url-normalize "x_test://acme.co" &&
+ ! test-url-normalize "schem%6e://" &&
+ test-url-normalize "x-Test+v1.0://acme.co" &&
+ test "$(test-url-normalize -p "AbCdeF://x.Y")" = "abcdef://x.y/"
+'
+
+test_expect_success 'url authority' '
+ ! test-url-normalize "scheme://user:pass@" &&
+ ! test-url-normalize "scheme://?" &&
+ ! test-url-normalize "scheme://#" &&
+ ! test-url-normalize "scheme:///" &&
+ ! test-url-normalize "scheme://:" &&
+ ! test-url-normalize "scheme://:555" &&
+ test-url-normalize "file://user:pass@" &&
+ test-url-normalize "file://?" &&
+ test-url-normalize "file://#" &&
+ test-url-normalize "file:///" &&
+ test-url-normalize "file://:" &&
+ test-url-normalize "file://:555" &&
+ test-url-normalize "scheme://user:pass@host" &&
+ test-url-normalize "scheme://@host" &&
+ test-url-normalize "scheme://%00@host" &&
+ ! test-url-normalize "scheme://%%@host" &&
+ ! test-url-normalize "scheme://host_" &&
+ test-url-normalize "scheme://user:pass@host/" &&
+ test-url-normalize "scheme://@host/" &&
+ test-url-normalize "scheme://host/" &&
+ test-url-normalize "scheme://host?x" &&
+ test-url-normalize "scheme://host#x" &&
+ test-url-normalize "scheme://host/@" &&
+ test-url-normalize "scheme://host?@x" &&
+ test-url-normalize "scheme://host#@x" &&
+ test-url-normalize "scheme://[::1]" &&
+ test-url-normalize "scheme://[::1]/" &&
+ ! test-url-normalize "scheme://hos%41/" &&
+ test-url-normalize "scheme://[invalid....:/" &&
+ test-url-normalize "scheme://invalid....:]/" &&
+ ! test-url-normalize "scheme://invalid....:[/" &&
+ ! test-url-normalize "scheme://invalid....:["
+'
+
+test_expect_success 'url port' '
+ test-url-normalize "xyz://q@some.host:" &&
+ test-url-normalize "xyz://q@some.host:456" &&
+ test-url-normalize "xyz://q@some.host:0" &&
+ test-url-normalize "xyz://q@some.host:99999" &&
+ test-url-normalize "http://q@some.host:80" &&
+ test-url-normalize "https://q@some.host:443"
+ ! test-url-normalize "http://q@:8008" &&
+ ! test-url-normalize "http://:8080" &&
+ ! test-url-normalize "http://:" &&
+ test-url-normalize "xyz://q@some.host:456/" &&
+ test-url-normalize "xyz://[::1]:456/" &&
+ test-url-normalize "xyz://[::1]:/" &&
+ test-url-normalize "xyz://[::1]:000/" &&
+ ! test-url-normalize "xyz://[::1]:0%300/" &&
+ ! test-url-normalize "xyz://[::1]:030f/"
+'
+
+test_expect_success 'url general escapes' '
+ ! test-url-normalize "http://x.y?%fg" &&
+ test "$(test-url-normalize -p "X://W/%7e%41^%3a")" = "x://w/~A%5E%3A" &&
+ test "$(test-url-normalize -p "X://W/:/?#[]@")" = "x://w/:/?#[]@" &&
+ test "$(test-url-normalize -p "X://W/$&()*+,;=")" = "x://w/$&()*+,;=" &&
+ test "$(test-url-normalize -p "X://W/'\''")" = "x://w/'\''" &&
+ test "$(test-url-normalize -p "X://W?'\!'")" = "x://w/?'\!'"
+';#'
+
+test_expect_success 'url username/password escapes' '
+ test "$(test-url-normalize -p "x://%41%62(^):%70+d@foo")" = "x://Ab(%5E):p+d@foo/"
+'
+
+test_expect_success 'url normalized lengths' '
+ test "$(test-url-normalize -l "Http://%4d%65:%4d^%70@The.Host")" = 25 &&
+ test "$(test-url-normalize -l "http://%41:%42@x.y/%61/")" = 17 &&
+ test "$(test-url-normalize -l "http://@x.y/^")" = 14
+'
+
+test_expect_success 'url equivalents' '
+ test-url-normalize "httP://x" "Http://X/" &&
+ test-url-normalize "Http://%4d%65:%4d^%70@The.Host" "hTTP://Me:%4D^p@the.HOST:80/" &&
+ test-url-normalize "https://@x.y/^" "httpS://x.y:443/^"
+'
+
+test_done
diff --git a/test-url-normalize.c b/test-url-normalize.c
new file mode 100644
index 0000000..1872964
--- /dev/null
+++ b/test-url-normalize.c
@@ -0,0 +1,62 @@
+#ifdef NO_CURL
+
+int main()
+{
+ return 125;
+}
+
+#else /* !NO_CURL */
+
+#include "http.c"
+
+#define url_normalize(u,s) http_options_url_normalize(u,s)
+
+int main(int argc, char **argv)
+{
+ const char *usage = "test-url-normalize [-p | -l] <url1> | <url1> <url2>";
+ char *url1, *url2;
+ int opt_p = 0, opt_l = 0;
+
+ /*
+ * For one url, succeed if url_normalize succeeds on it, fail otherwise.
+ * For two urls, succeed only if url_normalize succeeds on both and
+ * the results compare equal with strcmp. If -p is given (one url only)
+ * and url_normalize succeeds, print the result followed by "\n". If
+ * -l is given (one url only) and url_normalize succeeds, print the
+ * returned length in decimal followed by "\n".
+ */
+
+ if (argc > 1 && !strcmp(argv[1], "-p")) {
+ opt_p = 1;
+ argc--;
+ argv++;
+ } else if (argc > 1 && !strcmp(argv[1], "-l")) {
+ opt_l = 1;
+ argc--;
+ argv++;
+ }
+
+ if (argc < 2 || argc > 3)
+ die(usage);
+
+ if (argc == 2) {
+ size_t s;
+ url1 = url_normalize(argv[1], &s);
+ if (!url1)
+ return 1;
+ if (opt_p)
+ printf("%s\n", url1);
+ if (opt_l)
+ printf("%u\n", (unsigned)s);
+ return 0;
+ }
+
+ if (opt_p || opt_l)
+ die(usage);
+
+ url1 = url_normalize(argv[1], NULL);
+ url2 = url_normalize(argv[2], NULL);
+ return (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1;
+}
+
+#endif /* !NO_CURL */
--
1.8.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v5 4/5] config: improve support for http.<url>.* settings
2013-07-15 9:51 ` [PATCH v5 4/5] config: improve " Kyle J. McKay
@ 2013-07-15 23:12 ` Eric Sunshine
2013-07-16 9:53 ` Kyle J. McKay
0 siblings, 1 reply; 9+ messages in thread
From: Eric Sunshine @ 2013-07-15 23:12 UTC (permalink / raw)
To: Kyle J. McKay
Cc: Git List, David Aguilar, Petr Baudis, Junio C Hamano,
Richard Hartmann, Jeff King, Daniel Knittl-Frank, Jan Krüger,
Alejandro Mery, Aaron Schrab
On Mon, Jul 15, 2013 at 5:51 AM, Kyle J. McKay <mackyle@gmail.com> wrote:
> Improve on the http.<url>.* url matching behavior by first
> normalizing the urls before they are compared.
>
> diff --git a/http.c b/http.c
> index 758e5b1..d04386e 100644
> --- a/http.c
> +++ b/http.c
> @@ -169,6 +169,210 @@ static void process_curl_messages(void)
> }
> #endif
>
> +static int append_normalized_escapes(struct strbuf *buf,
> + const char *from,
> + size_t from_len,
> + const char *esc_extra,
> + const char *esc_ok)
> +{
> + /*
> + * Append to strbuf buf characters from string from with length from_len
s/from string from/from string/
> + * while unescaping characters that do not need to be escaped and
> + * escaping characters that do. The set of characters to escape (the
> + * complement of which is unescaped) starts out as the RFC 3986 unsafe
> + * characters (0x00-0x1F,0x7F-0xFF," <>\"#%{}|\\^`"). If esc_extra
> + * is not NULL, those additional characters will also always be escaped.
> + * If esc_ok is not NULL, those characters will be left escaped if found
> + * that way, but will not be unescaped otherwise (used for delimiters).
> + * If a %-escape sequence is encountered that is not followed by 2
> + * hexadecimal digits, the sequence is invalid and false (0) will be
> + * returned. Otherwise true (1) will be returned for success.
> + *
> + * Note that all %-escape sequences will be normalized to UPPERCASE
> + * as indicated in RFC 3986. Unless included in esc_extra or esc_ok
> + * alphanumerics and "-._~" will always be unescaped as per RFC 3986.
> + */
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v5 4/5] config: improve support for http.<url>.* settings
2013-07-15 23:12 ` Eric Sunshine
@ 2013-07-16 9:53 ` Kyle J. McKay
2013-07-16 10:01 ` Eric Sunshine
0 siblings, 1 reply; 9+ messages in thread
From: Kyle J. McKay @ 2013-07-16 9:53 UTC (permalink / raw)
To: Eric Sunshine
Cc: Git List, David Aguilar, Petr Baudis, Junio C Hamano,
Richard Hartmann, Jeff King, Daniel Knittl-Frank, Jan Krüger,
Alejandro Mery, Aaron Schrab
On Jul 15, 2013, at 16:12, Eric Sunshine wrote:
> On Mon, Jul 15, 2013 at 5:51 AM, Kyle J. McKay <mackyle@gmail.com>
> wrote:
>> +static int append_normalized_escapes(struct strbuf *buf,
>> + const char *from,
>> + size_t from_len,
>> + const char *esc_extra,
>> + const char *esc_ok)
>> +{
>> + /*
>> + * Append to strbuf buf characters from string from with
>> length from_len
>
> s/from string from/from string/
Hmmm. Actually it's meant to say "from string <parameter with name
from>".
Do quotes make it read better:
from string `from'
Or do you think it needs to be:
from string parameter `from'
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v5 4/5] config: improve support for http.<url>.* settings
2013-07-16 9:53 ` Kyle J. McKay
@ 2013-07-16 10:01 ` Eric Sunshine
0 siblings, 0 replies; 9+ messages in thread
From: Eric Sunshine @ 2013-07-16 10:01 UTC (permalink / raw)
To: Kyle J. McKay
Cc: Git List, David Aguilar, Petr Baudis, Junio C Hamano,
Richard Hartmann, Jeff King, Daniel Knittl-Frank, Jan Krüger,
Alejandro Mery, Aaron Schrab
On Tue, Jul 16, 2013 at 5:53 AM, Kyle J. McKay <mackyle@gmail.com> wrote:
> On Jul 15, 2013, at 16:12, Eric Sunshine wrote:
>>
>> On Mon, Jul 15, 2013 at 5:51 AM, Kyle J. McKay <mackyle@gmail.com> wrote:
>>>
>>> +static int append_normalized_escapes(struct strbuf *buf,
>>> + const char *from,
>>> + size_t from_len,
>>> + const char *esc_extra,
>>> + const char *esc_ok)
>>> +{
>>> + /*
>>> + * Append to strbuf buf characters from string from with length
>>> from_len
>>
>>
>> s/from string from/from string/
>
> Hmmm. Actually it's meant to say "from string <parameter with name from>".
>
> Do quotes make it read better:
>
> from string `from'
>
> Or do you think it needs to be:
>
> from string parameter `from'
Ah, I see now. Thanks for clarifying. Quoting 'from' (and the other
arguments) probably would make it sufficiently readable.
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2013-07-16 10:01 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-07-15 9:50 [PATCH v5 0/5] config: add support for http.<url>.* settings Kyle J. McKay
2013-07-15 9:50 ` [PATCH v5 1/5] http.c: fix parsing of http.sslCertPasswordProtected variable Kyle J. McKay
2013-07-15 9:50 ` [PATCH v5 2/5] http.c: fix parsing of GIT_SSL_CERT_PASSWORD_PROTECTED variable Kyle J. McKay
2013-07-15 9:51 ` [PATCH v5 3/5] config: add support for http.<url>.* settings Kyle J. McKay
2013-07-15 9:51 ` [PATCH v5 4/5] config: improve " Kyle J. McKay
2013-07-15 23:12 ` Eric Sunshine
2013-07-16 9:53 ` Kyle J. McKay
2013-07-16 10:01 ` Eric Sunshine
2013-07-15 9:51 ` [PATCH v5 5/5] tests: add new test for the url_normalize function Kyle J. McKay
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).