First commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
test
|
||||||
|
test.exe
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
ifeq ($(shell uname -s),Windows_NT)
|
||||||
|
EXT = .exe
|
||||||
|
else
|
||||||
|
EXT =
|
||||||
|
endif
|
||||||
|
|
||||||
|
all:
|
||||||
|
gcc -o test$(EXT) test.c url.c -Wall -Wextra
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm test test.exe
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <https://unlicense.org>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "url.h"
|
||||||
|
|
||||||
|
#define UNPACK(s) (s).len, (s).ptr
|
||||||
|
|
||||||
|
static char *host_type_to_str(URL_HostType type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case URL_HOST_EMPTY:
|
||||||
|
return "";
|
||||||
|
case URL_HOST_IPV4:
|
||||||
|
return "IPv4";
|
||||||
|
case URL_HOST_IPV6:
|
||||||
|
return "IPv6";
|
||||||
|
case URL_HOST_NAME:
|
||||||
|
return "name";
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return "???";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dump_parsed_url(URL *parsed)
|
||||||
|
{
|
||||||
|
printf(
|
||||||
|
"URL {\n"
|
||||||
|
" scheme = [%.*s]\n"
|
||||||
|
" username = [%.*s]\n"
|
||||||
|
" password = [%.*s]\n"
|
||||||
|
" host = [%.*s] (%s)\n"
|
||||||
|
" port = [%d]%s\n"
|
||||||
|
" path = [%.*s]\n"
|
||||||
|
" query = [%.*s]\n"
|
||||||
|
" fragment = [%.*s]\n"
|
||||||
|
"}\n",
|
||||||
|
UNPACK(parsed->scheme),
|
||||||
|
UNPACK(parsed->username),
|
||||||
|
UNPACK(parsed->password),
|
||||||
|
UNPACK(parsed->host_text),
|
||||||
|
host_type_to_str(parsed->host_type),
|
||||||
|
parsed->port,
|
||||||
|
parsed->no_port ? " (empty)" : "",
|
||||||
|
UNPACK(parsed->path),
|
||||||
|
UNPACK(parsed->query),
|
||||||
|
UNPACK(parsed->fragment));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
printf("Missing URL\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
char *url = argv[1];
|
||||||
|
|
||||||
|
URL parsed;
|
||||||
|
int ret = url_parse(url, strlen(url), NULL, &parsed);
|
||||||
|
|
||||||
|
printf("ret=%d\n", ret);
|
||||||
|
if (ret == 0)
|
||||||
|
dump_parsed_url(&parsed);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,581 @@
|
|||||||
|
#include "url.h"
|
||||||
|
|
||||||
|
// This is free and unencumbered software released into the public domain.
|
||||||
|
//
|
||||||
|
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
// distribute this software, either in source code form or as a compiled
|
||||||
|
// binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
// means.
|
||||||
|
//
|
||||||
|
// In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
// of this software dedicate any and all copyright interest in the
|
||||||
|
// software to the public domain. We make this dedication for the benefit
|
||||||
|
// of the public at large and to the detriment of our heirs and
|
||||||
|
// successors. We intend this dedication to be an overt act of
|
||||||
|
// relinquishment in perpetuity of all present and future rights to this
|
||||||
|
// software under copyright law.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
// OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
//
|
||||||
|
// For more information, please refer to <https://unlicense.org>
|
||||||
|
|
||||||
|
#ifdef NDEBUG
|
||||||
|
#define ASSERT(x) {}
|
||||||
|
#else
|
||||||
|
#define ASSERT(x) { if (!(x)) __builtin_trap(); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define UINT8_MAX (URL_u8) ((1 << 8)-1)
|
||||||
|
#define UINT16_MAX (URL_u16) ((1 << 16)-1)
|
||||||
|
|
||||||
|
#define EMPTY (URL_String) { (void*) 0, 0 }
|
||||||
|
#define SLICE(src, off, end) (URL_String) { src + off, end - off }
|
||||||
|
|
||||||
|
static URL_b32 is_alpha(char c)
|
||||||
|
{
|
||||||
|
return (c >= 'a' && c <= 'z')
|
||||||
|
|| (c >= 'A' && c <= 'Z');
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL_b32 is_digit(char c)
|
||||||
|
{
|
||||||
|
return c >= '0' && c <= '9';
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL_b32 is_hex_digit(char c)
|
||||||
|
{
|
||||||
|
return (c >= '0' && c <= '9')
|
||||||
|
|| (c >= 'a' && c <= 'f')
|
||||||
|
|| (c >= 'A' && c <= 'F');
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static URL_b32 is_gen_delim(char c)
|
||||||
|
{
|
||||||
|
return c == ':'
|
||||||
|
|| c == '/'
|
||||||
|
|| c == '?'
|
||||||
|
|| c == '#'
|
||||||
|
|| c == '['
|
||||||
|
|| c == ']'
|
||||||
|
|| c == '@';
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static URL_b32 is_sub_delim(char c)
|
||||||
|
{
|
||||||
|
return c == '!'
|
||||||
|
|| c == '$'
|
||||||
|
|| c == '&'
|
||||||
|
|| c == '\''
|
||||||
|
|| c == '('
|
||||||
|
|| c == ')'
|
||||||
|
|| c == '*'
|
||||||
|
|| c == '+'
|
||||||
|
|| c == ','
|
||||||
|
|| c == ';'
|
||||||
|
|| c == '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static URL_b32 is_reserved(char c)
|
||||||
|
{
|
||||||
|
return is_gen_delim(c)
|
||||||
|
|| is_sub_delim(c);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static URL_b32 is_unreserved(char c)
|
||||||
|
{
|
||||||
|
return is_alpha(c)
|
||||||
|
|| is_digit(c)
|
||||||
|
|| c == '-'
|
||||||
|
|| c == '.'
|
||||||
|
|| c == '_'
|
||||||
|
|| c == '~';
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL_b32 is_scheme_first(char c)
|
||||||
|
{
|
||||||
|
return is_alpha(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL_b32 is_scheme(char c)
|
||||||
|
{
|
||||||
|
return is_alpha(c)
|
||||||
|
|| is_digit(c)
|
||||||
|
|| c == '+'
|
||||||
|
|| c == '-'
|
||||||
|
|| c == '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL_b32 is_userinfo(char c)
|
||||||
|
{
|
||||||
|
return is_unreserved(c)
|
||||||
|
|| is_sub_delim(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL_b32 is_reg_name(char c)
|
||||||
|
{
|
||||||
|
return is_unreserved(c)
|
||||||
|
|| is_sub_delim(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL_b32 is_pchar(char c)
|
||||||
|
{
|
||||||
|
return is_unreserved(c)
|
||||||
|
|| is_sub_delim(c)
|
||||||
|
|| c == ':'
|
||||||
|
|| c == '@';
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL_b32 is_query(char c)
|
||||||
|
{
|
||||||
|
return is_pchar(c)
|
||||||
|
|| c == '/'
|
||||||
|
|| c == '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL_b32 is_fragment(char c)
|
||||||
|
{
|
||||||
|
return is_pchar(c)
|
||||||
|
|| c == '/'
|
||||||
|
|| c == '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns 1 if a percent encoded character is
|
||||||
|
// at offset "cur" and 0 otherwise. If a '%'
|
||||||
|
// character is found but the following characters
|
||||||
|
// aren't hex, -1 is returned.
|
||||||
|
static int is_percent_encoded(char *src, int len, int cur)
|
||||||
|
{
|
||||||
|
if (cur == len || src[cur] != '%')
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (len - cur <= 2
|
||||||
|
|| !is_hex_digit(src[cur+1])
|
||||||
|
|| !is_hex_digit(src[cur+2]))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int url_parse_ipv4(char *src, int len, int *pcur, URL_IPv4 *out)
|
||||||
|
{
|
||||||
|
int cur = pcur ? *pcur : 0;
|
||||||
|
|
||||||
|
out->data = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
|
||||||
|
if (cur == len || !is_digit(src[cur]))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
URL_u8 byte = src[cur] - '0';
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
while (cur < len && is_digit(src[cur])) {
|
||||||
|
|
||||||
|
int n = src[cur] - '0';
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
if (byte > (UINT8_MAX - n) / 10)
|
||||||
|
return -1;
|
||||||
|
byte = byte * 10 + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < 3) {
|
||||||
|
if (cur == len || src[cur] != '.')
|
||||||
|
return -1;
|
||||||
|
cur++;
|
||||||
|
}
|
||||||
|
|
||||||
|
out->data <<= 8;
|
||||||
|
out->data |= byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pcur) *pcur = cur;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hex_digit_to_int(char c)
|
||||||
|
{
|
||||||
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||||
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
parse_ipv6_comp(char *src, int len, int *pcur)
|
||||||
|
{
|
||||||
|
int cur = pcur ? *pcur : 0;
|
||||||
|
|
||||||
|
unsigned short buf;
|
||||||
|
|
||||||
|
if (cur == len || !is_hex_digit(src[cur]))
|
||||||
|
return -1;
|
||||||
|
buf = hex_digit_to_int(src[cur]);
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
if (cur == len || !is_hex_digit(src[cur]))
|
||||||
|
goto success;
|
||||||
|
buf <<= 4;
|
||||||
|
buf |= hex_digit_to_int(src[cur]);
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
if (cur == len || !is_hex_digit(src[cur]))
|
||||||
|
goto success;
|
||||||
|
buf <<= 4;
|
||||||
|
buf |= hex_digit_to_int(src[cur]);
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
if (cur == len || !is_hex_digit(src[cur]))
|
||||||
|
goto success;
|
||||||
|
buf <<= 4;
|
||||||
|
buf |= hex_digit_to_int(src[cur]);
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
success:
|
||||||
|
if (pcur) *pcur = cur;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
int url_parse_ipv6(char *src, int len, int *pcur, URL_IPv6 *out)
|
||||||
|
{
|
||||||
|
int cur = pcur ? *pcur : 0;
|
||||||
|
|
||||||
|
URL_u16 head[8];
|
||||||
|
URL_u16 tail[8];
|
||||||
|
int head_len = 0;
|
||||||
|
int tail_len = 0;
|
||||||
|
|
||||||
|
if (len - cur > 1
|
||||||
|
&& src[cur+0] == ':'
|
||||||
|
&& src[cur+1] == ':')
|
||||||
|
cur += 2;
|
||||||
|
else {
|
||||||
|
for (;;) {
|
||||||
|
|
||||||
|
int ret = parse_ipv6_comp(src, len, &cur);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
head[head_len++] = (URL_u16) ret;
|
||||||
|
if (head_len == 8)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (cur == len || src[cur] != ':')
|
||||||
|
return -1;
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
if (cur < len && src[cur] == ':') {
|
||||||
|
cur++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (head_len < 8) {
|
||||||
|
while (cur < len && is_hex_digit(src[cur])) {
|
||||||
|
|
||||||
|
int ret = parse_ipv6_comp(src, len, &cur);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
tail[tail_len++] = (URL_u16) ret;
|
||||||
|
if (head_len + tail_len == 8)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (cur == len || src[cur] != ':')
|
||||||
|
break;
|
||||||
|
cur++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < head_len; i++)
|
||||||
|
out->data[i] = head[i];
|
||||||
|
|
||||||
|
for (int i = 0; i < 8 - head_len - tail_len; i++)
|
||||||
|
out->data[head_len + i] = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < tail_len; i++)
|
||||||
|
out->data[8 - tail_len + i] = tail[i];
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int url_parse(char *src, int len, int *pcur, URL *out)
|
||||||
|
{
|
||||||
|
int cur = pcur ? *pcur : 0;
|
||||||
|
|
||||||
|
// Parse scheme characters until the end of the string,
|
||||||
|
// a character not allowed in the scheme, or ':'. If the
|
||||||
|
// found character wasn't ':', set the scheme to empty
|
||||||
|
// and rollback the cursor.
|
||||||
|
out->scheme = EMPTY;
|
||||||
|
if (cur < len && is_scheme_first(src[cur])) {
|
||||||
|
|
||||||
|
// Save the index of the first character of the scheme
|
||||||
|
// and advance
|
||||||
|
int off = cur;
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
// Consume characters from the body of the scheme
|
||||||
|
while (cur < len && is_scheme(src[cur]))
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
if (cur == len || src[cur] != ':') {
|
||||||
|
cur = 0; // Not a scheme after all
|
||||||
|
} else {
|
||||||
|
out->scheme = SLICE(src, off, cur);
|
||||||
|
cur++; // Consume the ':'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the following characters are // we expect an
|
||||||
|
// authority. An authority consists of some optional
|
||||||
|
// userinfo, a domain name, and a port.
|
||||||
|
//
|
||||||
|
// scheme://user:pass@domain.com:port...
|
||||||
|
//
|
||||||
|
// We won't know whether the first portion is user
|
||||||
|
// info or the domain until we hit a @ character or
|
||||||
|
// a character not allowed in the user info, se we
|
||||||
|
// need to backtrack in a similar way to the scheme
|
||||||
|
URL_b32 authority = 0;
|
||||||
|
if (len - cur > 1 && src[cur] == '/' && src[cur+1] == '/') {
|
||||||
|
cur += 2; // Consume the //
|
||||||
|
authority = 1;
|
||||||
|
|
||||||
|
out->username = EMPTY;
|
||||||
|
out->password = EMPTY;
|
||||||
|
if (cur < len && (is_userinfo(src[cur]) || src[cur] == ':')) {
|
||||||
|
|
||||||
|
int user_off = cur;
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
while (cur < len) {
|
||||||
|
if (is_userinfo(src[cur]))
|
||||||
|
cur++;
|
||||||
|
else {
|
||||||
|
int ret = is_percent_encoded(src, len, cur);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (ret == 0)
|
||||||
|
break;
|
||||||
|
cur += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cur < len && src[cur] == ':') {
|
||||||
|
|
||||||
|
cur++; // Consume the ':'
|
||||||
|
int pass_off = cur;
|
||||||
|
|
||||||
|
while (cur < len) {
|
||||||
|
if (is_userinfo(src[cur]))
|
||||||
|
cur++;
|
||||||
|
else {
|
||||||
|
int ret = is_percent_encoded(src, len, cur);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (ret == 0)
|
||||||
|
break;
|
||||||
|
cur += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cur == len || src[cur] != '@') {
|
||||||
|
cur = user_off; // Not userinfo after all
|
||||||
|
} else {
|
||||||
|
out->username = SLICE(src, user_off, pass_off-1);
|
||||||
|
out->password = SLICE(src, pass_off, cur);
|
||||||
|
cur++; // Consume the '@'
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (cur < len && src[cur] == '@') {
|
||||||
|
out->username = SLICE(src, user_off, cur);
|
||||||
|
out->password = EMPTY;
|
||||||
|
cur++; // Consume the '@'
|
||||||
|
} else {
|
||||||
|
cur = user_off; // Not userinfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The domain may be a registered name, an IPv4 address,
|
||||||
|
// or an IPv6 address.
|
||||||
|
// example.com
|
||||||
|
// 127.0.0.1
|
||||||
|
// [abcd:abcd::0001]
|
||||||
|
// Note that an IPv4 could technically be parsed as a
|
||||||
|
// registered name, so it's important that we first try
|
||||||
|
// the IPv4 rule, and then the registered name rule.
|
||||||
|
int host_off = cur;
|
||||||
|
if (cur < len && src[cur] == '[') {
|
||||||
|
cur++; // Consume the '['
|
||||||
|
|
||||||
|
// We started with an '[', so it's definitely an IPv6
|
||||||
|
out->host_type = URL_HOST_IPV6;
|
||||||
|
|
||||||
|
int ret = url_parse_ipv6(src, len, &cur, &out->host_ipv6);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (cur == len || src[cur] != ']')
|
||||||
|
return -1; // Missing ']' after IPv6
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
URL_b32 is_ipv4 = 0;
|
||||||
|
|
||||||
|
// First, try the IPv4 rule
|
||||||
|
if (is_digit(src[cur])) {
|
||||||
|
if (url_parse_ipv4(src, len, &cur, &out->host_ipv4) == 0) {
|
||||||
|
out->host_type = URL_HOST_IPV4;
|
||||||
|
is_ipv4 = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_ipv4) {
|
||||||
|
if (cur < len && (is_reg_name(src[cur]) || is_percent_encoded(src, len, cur))) {
|
||||||
|
|
||||||
|
int ret = is_percent_encoded(src, len, cur);
|
||||||
|
ASSERT(ret >= 0);
|
||||||
|
cur += (ret == 1) ? 3 : 1;
|
||||||
|
|
||||||
|
out->host_type = URL_HOST_NAME;
|
||||||
|
|
||||||
|
while (cur < len) {
|
||||||
|
if (is_reg_name(src[cur]))
|
||||||
|
cur++;
|
||||||
|
else {
|
||||||
|
int ret = is_percent_encoded(src, len, cur);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (ret == 0)
|
||||||
|
break;
|
||||||
|
cur += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
out->host_type = URL_HOST_EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out->host_text = SLICE(src, host_off, cur);
|
||||||
|
|
||||||
|
// Now we parse the port
|
||||||
|
out->no_port = 1;
|
||||||
|
out->port = 0;
|
||||||
|
if (cur < len && src[cur] == ':') {
|
||||||
|
|
||||||
|
cur++; // Consume the ':'
|
||||||
|
out->no_port = 0;
|
||||||
|
|
||||||
|
while (cur < len && is_digit(src[cur])) {
|
||||||
|
|
||||||
|
int n = src[cur] - '0';
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
if (out->port > (UINT16_MAX - n) / 10)
|
||||||
|
return -1; // Overflow
|
||||||
|
out->port = out->port * 10 + n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we parse the path. This is most difficult
|
||||||
|
// component as it changes based on what comes
|
||||||
|
// before it.
|
||||||
|
//
|
||||||
|
// If an URL contains an authority component, it
|
||||||
|
// must be absolute or empty.
|
||||||
|
if (authority && (cur == len || src[cur] != '/')) {
|
||||||
|
out->path = EMPTY;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
int off = cur;
|
||||||
|
|
||||||
|
while (cur < len) {
|
||||||
|
if (is_pchar(src[cur]) || src[cur] == '/')
|
||||||
|
cur++;
|
||||||
|
else {
|
||||||
|
int ret = is_percent_encoded(src, len, cur);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (ret == 0)
|
||||||
|
break;
|
||||||
|
cur += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out->path = SLICE(src, off, cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume the query
|
||||||
|
out->query = EMPTY;
|
||||||
|
if (cur < len && src[cur] == '?') {
|
||||||
|
|
||||||
|
cur++; // Consume '?'
|
||||||
|
int off = cur;
|
||||||
|
|
||||||
|
while (cur < len) {
|
||||||
|
if (is_query(src[cur]))
|
||||||
|
cur++;
|
||||||
|
else {
|
||||||
|
int ret = is_percent_encoded(src, len, cur);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (ret == 0)
|
||||||
|
break;
|
||||||
|
cur += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out->query = SLICE(src, off, cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume the fragment
|
||||||
|
out->fragment = EMPTY;
|
||||||
|
if (cur < len && src[cur] == '#') {
|
||||||
|
|
||||||
|
cur++; // Consume '#'
|
||||||
|
int off = cur;
|
||||||
|
|
||||||
|
while (cur < len) {
|
||||||
|
if (is_fragment(src[cur]))
|
||||||
|
cur++;
|
||||||
|
else {
|
||||||
|
int ret = is_percent_encoded(src, len, cur);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (ret == 0)
|
||||||
|
break;
|
||||||
|
cur += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out->fragment = SLICE(src, off, cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a cursor pointer was provided, it is assumed
|
||||||
|
// the caller is parsing a string that contains an
|
||||||
|
// URL and may be followed by something else, therefore
|
||||||
|
// we allow partially parsing the source. If no cursor
|
||||||
|
// pointer was provided, we expect the source to be
|
||||||
|
// an exact URL string.
|
||||||
|
if (pcur) {
|
||||||
|
*pcur = cur;
|
||||||
|
} else {
|
||||||
|
if (cur != len)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#ifndef URL_INCLUDED
|
||||||
|
#define URL_INCLUDED
|
||||||
|
// This is free and unencumbered software released into the public domain.
|
||||||
|
//
|
||||||
|
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
// distribute this software, either in source code form or as a compiled
|
||||||
|
// binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
// means.
|
||||||
|
//
|
||||||
|
// In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
// of this software dedicate any and all copyright interest in the
|
||||||
|
// software to the public domain. We make this dedication for the benefit
|
||||||
|
// of the public at large and to the detriment of our heirs and
|
||||||
|
// successors. We intend this dedication to be an overt act of
|
||||||
|
// relinquishment in perpetuity of all present and future rights to this
|
||||||
|
// software under copyright law.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
// OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
//
|
||||||
|
// For more information, please refer to <https://unlicense.org>
|
||||||
|
|
||||||
|
#define URL_STATIC_ASSERT _Static_assert
|
||||||
|
|
||||||
|
typedef unsigned char URL_u8;
|
||||||
|
typedef unsigned short URL_u16;
|
||||||
|
typedef unsigned int URL_u32;
|
||||||
|
typedef unsigned int URL_b32;
|
||||||
|
|
||||||
|
URL_STATIC_ASSERT(sizeof(URL_u8) == 1, "");
|
||||||
|
URL_STATIC_ASSERT(sizeof(URL_u16) == 2, "");
|
||||||
|
URL_STATIC_ASSERT(sizeof(URL_u32) == 4, "");
|
||||||
|
URL_STATIC_ASSERT(sizeof(URL_b32) == 4, "");
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *ptr;
|
||||||
|
int len;
|
||||||
|
} URL_String;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
URL_HOST_EMPTY,
|
||||||
|
URL_HOST_IPV4,
|
||||||
|
URL_HOST_IPV6,
|
||||||
|
URL_HOST_NAME,
|
||||||
|
} URL_HostType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
URL_u32 data;
|
||||||
|
} URL_IPv4;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
URL_u16 data[8];
|
||||||
|
} URL_IPv6;
|
||||||
|
|
||||||
|
URL_STATIC_ASSERT(sizeof(URL_IPv4) == 4, "");
|
||||||
|
URL_STATIC_ASSERT(sizeof(URL_IPv6) == 16, "");
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
// May be empty
|
||||||
|
URL_String scheme;
|
||||||
|
|
||||||
|
// Both may be empty
|
||||||
|
URL_String username;
|
||||||
|
URL_String password;
|
||||||
|
|
||||||
|
// The raw host string is stored in host_text.
|
||||||
|
// If the host is an IPv4 or IPv6, its parsed
|
||||||
|
// value is also stored in host_ipv4 or host_ipv6
|
||||||
|
// (host byte order).
|
||||||
|
// Note that the host may be empty, in which
|
||||||
|
// case host_type=URL_HOST_EMPTY.
|
||||||
|
URL_HostType host_type;
|
||||||
|
URL_String host_text;
|
||||||
|
union {
|
||||||
|
URL_IPv4 host_ipv4;
|
||||||
|
URL_IPv6 host_ipv6;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If no port was specified, no_port is set
|
||||||
|
// to 1 and port to 0.
|
||||||
|
URL_b32 no_port;
|
||||||
|
URL_u16 port;
|
||||||
|
|
||||||
|
URL_String path;
|
||||||
|
|
||||||
|
// May be empty
|
||||||
|
URL_String query;
|
||||||
|
|
||||||
|
// May be empty
|
||||||
|
URL_String fragment;
|
||||||
|
|
||||||
|
} URL;
|
||||||
|
|
||||||
|
int url_parse(char *src, int len, int *pcur, URL *out);
|
||||||
|
int url_parse_ipv4(char *src, int len, int *pcur, URL_IPv4 *out);
|
||||||
|
int url_parse_ipv6(char *src, int len, int *pcur, URL_IPv6 *out);
|
||||||
|
|
||||||
|
#endif // URL_INCLUDED
|
||||||
Reference in New Issue
Block a user