Skip to content

Commit e5ba0a3

Browse files
committed
url: introduce git_net_url_matches_pattern
Provide a method to determine if a given URL matches a host:port pattern like the ones found in `NO_PROXY` environment variables.
1 parent 3680f0b commit e5ba0a3

File tree

3 files changed

+114
-0
lines changed

3 files changed

+114
-0
lines changed

src/net.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,61 @@ int git_net_url_fmt_path(git_buf *buf, git_net_url *url)
404404
return git_buf_oom(buf) ? -1 : 0;
405405
}
406406

407+
static bool matches_pattern(
408+
git_net_url *url,
409+
const char *pattern,
410+
size_t pattern_len)
411+
{
412+
const char *domain, *port = NULL, *colon;
413+
size_t host_len, domain_len, port_len = 0, wildcard = 0;
414+
415+
GIT_UNUSED(url);
416+
GIT_UNUSED(pattern);
417+
418+
if (!pattern_len)
419+
return false;
420+
else if (pattern_len == 1 && pattern[0] == '*')
421+
return true;
422+
else if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.')
423+
wildcard = 2;
424+
else if (pattern[0] == '.')
425+
wildcard = 1;
426+
427+
domain = pattern + wildcard;
428+
domain_len = pattern_len - wildcard;
429+
430+
if ((colon = memchr(domain, ':', domain_len)) != NULL) {
431+
domain_len = colon - domain;
432+
port = colon + 1;
433+
port_len = pattern_len - wildcard - domain_len - 1;
434+
}
435+
436+
/* A pattern's port *must* match if it's specified */
437+
if (port_len && git__strlcmp(url->port, port, port_len) != 0)
438+
return false;
439+
440+
/* No wildcard? Host must match exactly. */
441+
if (!wildcard)
442+
return !git__strlcmp(url->host, domain, domain_len);
443+
444+
/* Wildcard: ensure there's (at least) a suffix match */
445+
if ((host_len = strlen(url->host)) < domain_len ||
446+
memcmp(url->host + (host_len - domain_len), domain, domain_len))
447+
return false;
448+
449+
/* The pattern is *.domain and the host is simply domain */
450+
if (host_len == domain_len)
451+
return true;
452+
453+
/* The pattern is *.domain and the host is foo.domain */
454+
return (url->host[host_len - domain_len - 1] == '.');
455+
}
456+
457+
bool git_net_url_matches_pattern(git_net_url *url, const char *pattern)
458+
{
459+
return matches_pattern(url, pattern, strlen(pattern));
460+
}
461+
407462
void git_net_url_dispose(git_net_url *url)
408463
{
409464
if (url->username)

src/net.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ extern int git_net_url_fmt(git_buf *out, git_net_url *url);
5454
/** Place the path and query string into the given buffer. */
5555
extern int git_net_url_fmt_path(git_buf *buf, git_net_url *url);
5656

57+
/** Determines if the url matches given pattern or pattern list */
58+
extern bool git_net_url_matches_pattern(
59+
git_net_url *url,
60+
const char *pattern);
61+
5762
/** Disposes the contents of the structure. */
5863
extern void git_net_url_dispose(git_net_url *url);
5964

tests/network/url/pattern.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include "clar_libgit2.h"
2+
#include "net.h"
3+
4+
struct url_pattern {
5+
const char *url;
6+
const char *pattern;
7+
bool matches;
8+
};
9+
10+
void test_network_url_pattern__single(void)
11+
{
12+
git_net_url url;
13+
size_t i;
14+
15+
struct url_pattern url_patterns[] = {
16+
/* Wildcard matches */
17+
{ "https://example.com/", "", false },
18+
{ "https://example.com/", "*", true },
19+
20+
/* Literal and wildcard matches */
21+
{ "https://example.com/", "example.com", true },
22+
{ "https://example.com/", ".example.com", true },
23+
{ "https://example.com/", "*.example.com", true },
24+
{ "https://www.example.com/", "www.example.com", true },
25+
{ "https://www.example.com/", ".example.com", true },
26+
{ "https://www.example.com/", "*.example.com", true },
27+
28+
/* Literal and wildcard failures */
29+
{ "https://example.com/", "example.org", false },
30+
{ "https://example.com/", ".example.org", false },
31+
{ "https://example.com/", "*.example.org", false },
32+
{ "https://foo.example.com/", "www.example.com", false },
33+
34+
/*
35+
* A port in the pattern is optional; if no port is
36+
* present, it matches *all* ports.
37+
*/
38+
{ "https://example.com/", "example.com:443", true },
39+
{ "https://example.com/", "example.com:80", false },
40+
{ "https://example.com:1443/", "example.com", true },
41+
42+
/* Failures with similar prefix/suffix */
43+
{ "https://texample.com/", "example.com", false },
44+
{ "https://example.com/", "mexample.com", false },
45+
{ "https://example.com:44/", "example.com:443", false },
46+
{ "https://example.com:443/", "example.com:44", false },
47+
};
48+
49+
for (i = 0; i < ARRAY_SIZE(url_patterns); i++) {
50+
cl_git_pass(git_net_url_parse(&url, url_patterns[i].url));
51+
cl_assert_(git_net_url_matches_pattern(&url, url_patterns[i].pattern) == url_patterns[i].matches, url_patterns[i].pattern);
52+
git_net_url_dispose(&url);
53+
}
54+
}

0 commit comments

Comments
 (0)