src: add native URL class
Adds a URL native class for use within the node.js c/c++ code. This is primarily intended to be used by the eventual ES6 modules implementation but can be used generally wherever URL parsing within the c/c++ may be necessary. ```c URL url1("http://example.org"); URL url2("foo", "http://example.org/bar"); URL url3("baz", &url2); ``` While we're at it, reduce reliance on macros to simplify impl. PR-URL: https://github.com/nodejs/node/pull/11801 Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
parent
fbb853f52b
commit
e26b6c6baa
558
src/node_url.cc
558
src/node_url.cc
@ -47,45 +47,6 @@ using v8::Value;
|
||||
} \
|
||||
}
|
||||
|
||||
#define CANNOT_BE_BASE() url.flags |= URL_FLAGS_CANNOT_BE_BASE;
|
||||
#define INVALID_PARSE_STATE() url.flags |= URL_FLAGS_INVALID_PARSE_STATE;
|
||||
#define SPECIAL() \
|
||||
{ \
|
||||
url.flags |= URL_FLAGS_SPECIAL; \
|
||||
special = true; \
|
||||
}
|
||||
#define TERMINATE() \
|
||||
{ \
|
||||
url.flags |= URL_FLAGS_TERMINATED; \
|
||||
goto done; \
|
||||
}
|
||||
#define URL_FAILED() \
|
||||
{ \
|
||||
url.flags |= URL_FLAGS_FAILED; \
|
||||
goto done; \
|
||||
}
|
||||
|
||||
#define CHECK_FLAG(flags, name) (flags & URL_FLAGS_##name) /* NOLINT */
|
||||
|
||||
#define IS_CANNOT_BE_BASE(flags) CHECK_FLAG(flags, CANNOT_BE_BASE)
|
||||
#define IS_FAILED(flags) CHECK_FLAG(flags, FAILED)
|
||||
|
||||
#define DOES_HAVE_SCHEME(url) CHECK_FLAG(url.flags, HAS_SCHEME)
|
||||
#define DOES_HAVE_USERNAME(url) CHECK_FLAG(url.flags, HAS_USERNAME)
|
||||
#define DOES_HAVE_PASSWORD(url) CHECK_FLAG(url.flags, HAS_PASSWORD)
|
||||
#define DOES_HAVE_HOST(url) CHECK_FLAG(url.flags, HAS_HOST)
|
||||
#define DOES_HAVE_PATH(url) CHECK_FLAG(url.flags, HAS_PATH)
|
||||
#define DOES_HAVE_QUERY(url) CHECK_FLAG(url.flags, HAS_QUERY)
|
||||
#define DOES_HAVE_FRAGMENT(url) CHECK_FLAG(url.flags, HAS_FRAGMENT)
|
||||
|
||||
#define SET_HAVE_SCHEME() url.flags |= URL_FLAGS_HAS_SCHEME;
|
||||
#define SET_HAVE_USERNAME() url.flags |= URL_FLAGS_HAS_USERNAME;
|
||||
#define SET_HAVE_PASSWORD() url.flags |= URL_FLAGS_HAS_PASSWORD;
|
||||
#define SET_HAVE_HOST() url.flags |= URL_FLAGS_HAS_HOST;
|
||||
#define SET_HAVE_PATH() url.flags |= URL_FLAGS_HAS_PATH;
|
||||
#define SET_HAVE_QUERY() url.flags |= URL_FLAGS_HAS_QUERY;
|
||||
#define SET_HAVE_FRAGMENT() url.flags |= URL_FLAGS_HAS_FRAGMENT;
|
||||
|
||||
#define UTF8STRING(isolate, str) \
|
||||
String::NewFromUtf8(isolate, str.c_str(), v8::NewStringType::kNormal) \
|
||||
.ToLocalChecked()
|
||||
@ -93,7 +54,7 @@ using v8::Value;
|
||||
namespace url {
|
||||
|
||||
#if defined(NODE_HAVE_I18N_SUPPORT)
|
||||
static bool ToUnicode(std::string* input, std::string* output) {
|
||||
static inline bool ToUnicode(std::string* input, std::string* output) {
|
||||
MaybeStackBuffer<char> buf;
|
||||
if (i18n::ToUnicode(&buf, input->c_str(), input->length()) < 0)
|
||||
return false;
|
||||
@ -101,7 +62,7 @@ namespace url {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ToASCII(std::string* input, std::string* output) {
|
||||
static inline bool ToASCII(std::string* input, std::string* output) {
|
||||
MaybeStackBuffer<char> buf;
|
||||
if (i18n::ToASCII(&buf, input->c_str(), input->length()) < 0)
|
||||
return false;
|
||||
@ -110,12 +71,12 @@ namespace url {
|
||||
}
|
||||
#else
|
||||
// Intentional non-ops if ICU is not present.
|
||||
static bool ToUnicode(std::string* input, std::string* output) {
|
||||
static inline bool ToUnicode(std::string* input, std::string* output) {
|
||||
*output = *input;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ToASCII(std::string* input, std::string* output) {
|
||||
static inline bool ToASCII(std::string* input, std::string* output) {
|
||||
*output = *input;
|
||||
return true;
|
||||
}
|
||||
@ -619,41 +580,26 @@ namespace url {
|
||||
url->path.pop_back();
|
||||
}
|
||||
|
||||
static void Parse(Environment* env,
|
||||
Local<Value> recv,
|
||||
const char* input,
|
||||
const size_t len,
|
||||
enum url_parse_state state_override,
|
||||
Local<Value> base_obj,
|
||||
Local<Value> context_obj,
|
||||
Local<Function> cb) {
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
HandleScope handle_scope(isolate);
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
const bool has_base = base_obj->IsObject();
|
||||
void URL::Parse(const char* input,
|
||||
const size_t len,
|
||||
enum url_parse_state state_override,
|
||||
struct url_data* url,
|
||||
const struct url_data* base,
|
||||
bool has_base) {
|
||||
bool atflag = false;
|
||||
bool sbflag = false;
|
||||
bool uflag = false;
|
||||
bool base_is_file = false;
|
||||
int wskip = 0;
|
||||
|
||||
struct url_data base;
|
||||
struct url_data url;
|
||||
if (context_obj->IsObject())
|
||||
HarvestContext(env, &url, context_obj.As<Object>());
|
||||
if (has_base)
|
||||
HarvestBase(env, &base, base_obj.As<Object>());
|
||||
|
||||
std::string buffer;
|
||||
url.scheme.reserve(len);
|
||||
url.username.reserve(len);
|
||||
url.password.reserve(len);
|
||||
url.host.reserve(len);
|
||||
url.path.reserve(len);
|
||||
url.query.reserve(len);
|
||||
url.fragment.reserve(len);
|
||||
url->scheme.reserve(len);
|
||||
url->username.reserve(len);
|
||||
url->password.reserve(len);
|
||||
url->host.reserve(len);
|
||||
url->path.reserve(len);
|
||||
url->query.reserve(len);
|
||||
url->fragment.reserve(len);
|
||||
buffer.reserve(len);
|
||||
|
||||
// Set the initial parse state.
|
||||
@ -665,8 +611,8 @@ namespace url {
|
||||
const char* end = input + len;
|
||||
|
||||
if (state < kSchemeStart || state > kFragment) {
|
||||
INVALID_PARSE_STATE();
|
||||
goto done;
|
||||
url->flags |= URL_FLAGS_INVALID_PARSE_STATE;
|
||||
return;
|
||||
}
|
||||
|
||||
while (p <= end) {
|
||||
@ -684,7 +630,8 @@ namespace url {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool special = url.flags & URL_FLAGS_SPECIAL;
|
||||
bool special = (url->flags & URL_FLAGS_SPECIAL);
|
||||
bool cannot_be_base;
|
||||
const bool special_back_slash = (special && ch == '\\');
|
||||
switch (state) {
|
||||
case kSchemeStart:
|
||||
@ -695,7 +642,8 @@ namespace url {
|
||||
state = kNoScheme;
|
||||
continue;
|
||||
} else {
|
||||
TERMINATE()
|
||||
url->flags |= URL_FLAGS_TERMINATED;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case kScheme:
|
||||
@ -706,23 +654,24 @@ namespace url {
|
||||
} else if (ch == ':' || (has_state_override && ch == kEOL)) {
|
||||
buffer += ':';
|
||||
if (buffer.size() > 0) {
|
||||
SET_HAVE_SCHEME()
|
||||
url.scheme = buffer;
|
||||
url->flags |= URL_FLAGS_HAS_SCHEME;
|
||||
url->scheme = buffer;
|
||||
}
|
||||
if (IsSpecial(url.scheme)) {
|
||||
SPECIAL()
|
||||
if (IsSpecial(url->scheme)) {
|
||||
url->flags |= URL_FLAGS_SPECIAL;
|
||||
special = true;
|
||||
} else {
|
||||
url.flags &= ~URL_FLAGS_SPECIAL;
|
||||
url->flags &= ~URL_FLAGS_SPECIAL;
|
||||
}
|
||||
if (has_state_override)
|
||||
goto done;
|
||||
return;
|
||||
buffer.clear();
|
||||
if (url.scheme == "file:") {
|
||||
if (url->scheme == "file:") {
|
||||
state = kFile;
|
||||
} else if (special &&
|
||||
has_base &&
|
||||
DOES_HAVE_SCHEME(base) &&
|
||||
url.scheme == base.scheme) {
|
||||
base->flags & URL_FLAGS_HAS_SCHEME &&
|
||||
url->scheme == base->scheme) {
|
||||
state = kSpecialRelativeOrAuthority;
|
||||
} else if (special) {
|
||||
state = kSpecialAuthoritySlashes;
|
||||
@ -730,9 +679,9 @@ namespace url {
|
||||
state = kPathOrAuthority;
|
||||
p++;
|
||||
} else {
|
||||
CANNOT_BE_BASE()
|
||||
SET_HAVE_PATH()
|
||||
url.path.push_back("");
|
||||
url->flags |= URL_FLAGS_CANNOT_BE_BASE;
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path.push_back("");
|
||||
state = kCannotBeBase;
|
||||
}
|
||||
} else if (!has_state_override) {
|
||||
@ -741,43 +690,48 @@ namespace url {
|
||||
p = input;
|
||||
continue;
|
||||
} else {
|
||||
TERMINATE()
|
||||
url->flags |= URL_FLAGS_TERMINATED;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case kNoScheme:
|
||||
if (!has_base || (IS_CANNOT_BE_BASE(base.flags) && ch != '#')) {
|
||||
URL_FAILED()
|
||||
} else if (IS_CANNOT_BE_BASE(base.flags) && ch == '#') {
|
||||
SET_HAVE_SCHEME()
|
||||
url.scheme = base.scheme;
|
||||
if (IsSpecial(url.scheme)) {
|
||||
SPECIAL()
|
||||
cannot_be_base = base->flags & URL_FLAGS_CANNOT_BE_BASE;
|
||||
if (!has_base || (cannot_be_base && ch != '#')) {
|
||||
url->flags |= URL_FLAGS_FAILED;
|
||||
return;
|
||||
} else if (cannot_be_base && ch == '#') {
|
||||
url->flags |= URL_FLAGS_HAS_SCHEME;
|
||||
url->scheme = base->scheme;
|
||||
if (IsSpecial(url->scheme)) {
|
||||
url->flags |= URL_FLAGS_SPECIAL;
|
||||
special = true;
|
||||
} else {
|
||||
url.flags &= ~URL_FLAGS_SPECIAL;
|
||||
url->flags &= ~URL_FLAGS_SPECIAL;
|
||||
}
|
||||
if (DOES_HAVE_PATH(base)) {
|
||||
SET_HAVE_PATH()
|
||||
url.path = base.path;
|
||||
if (base->flags & URL_FLAGS_HAS_PATH) {
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path = base->path;
|
||||
}
|
||||
if (DOES_HAVE_QUERY(base)) {
|
||||
SET_HAVE_QUERY()
|
||||
url.query = base.query;
|
||||
if (base->flags & URL_FLAGS_HAS_QUERY) {
|
||||
url->flags |= URL_FLAGS_HAS_QUERY;
|
||||
url->query = base->query;
|
||||
}
|
||||
if (DOES_HAVE_FRAGMENT(base)) {
|
||||
SET_HAVE_FRAGMENT()
|
||||
url.fragment = base.fragment;
|
||||
if (base->flags & URL_FLAGS_HAS_FRAGMENT) {
|
||||
url->flags |= URL_FLAGS_HAS_FRAGMENT;
|
||||
url->fragment = base->fragment;
|
||||
}
|
||||
CANNOT_BE_BASE()
|
||||
url->flags |= URL_FLAGS_CANNOT_BE_BASE;
|
||||
state = kFragment;
|
||||
} else if (has_base &&
|
||||
DOES_HAVE_SCHEME(base) &&
|
||||
base.scheme != "file:") {
|
||||
base->flags & URL_FLAGS_HAS_SCHEME &&
|
||||
base->scheme != "file:") {
|
||||
state = kRelative;
|
||||
continue;
|
||||
} else {
|
||||
SET_HAVE_SCHEME()
|
||||
url.scheme = "file:";
|
||||
SPECIAL()
|
||||
url->flags |= URL_FLAGS_HAS_SCHEME;
|
||||
url->scheme = "file:";
|
||||
url->flags |= URL_FLAGS_SPECIAL;
|
||||
special = true;
|
||||
state = kFile;
|
||||
continue;
|
||||
}
|
||||
@ -800,106 +754,107 @@ namespace url {
|
||||
}
|
||||
break;
|
||||
case kRelative:
|
||||
SET_HAVE_SCHEME()
|
||||
url.scheme = base.scheme;
|
||||
if (IsSpecial(url.scheme)) {
|
||||
SPECIAL()
|
||||
url->flags |= URL_FLAGS_HAS_SCHEME;
|
||||
url->scheme = base->scheme;
|
||||
if (IsSpecial(url->scheme)) {
|
||||
url->flags |= URL_FLAGS_SPECIAL;
|
||||
special = true;
|
||||
} else {
|
||||
url.flags &= ~URL_FLAGS_SPECIAL;
|
||||
url->flags &= ~URL_FLAGS_SPECIAL;
|
||||
}
|
||||
switch (ch) {
|
||||
case kEOL:
|
||||
if (DOES_HAVE_USERNAME(base)) {
|
||||
SET_HAVE_USERNAME()
|
||||
url.username = base.username;
|
||||
if (base->flags & URL_FLAGS_HAS_USERNAME) {
|
||||
url->flags |= URL_FLAGS_HAS_USERNAME;
|
||||
url->username = base->username;
|
||||
}
|
||||
if (DOES_HAVE_PASSWORD(base)) {
|
||||
SET_HAVE_PASSWORD()
|
||||
url.password = base.password;
|
||||
if (base->flags & URL_FLAGS_HAS_PASSWORD) {
|
||||
url->flags |= URL_FLAGS_HAS_PASSWORD;
|
||||
url->password = base->password;
|
||||
}
|
||||
if (DOES_HAVE_HOST(base)) {
|
||||
SET_HAVE_HOST()
|
||||
url.host = base.host;
|
||||
if (base->flags & URL_FLAGS_HAS_HOST) {
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
url->host = base->host;
|
||||
}
|
||||
if (DOES_HAVE_QUERY(base)) {
|
||||
SET_HAVE_QUERY()
|
||||
url.query = base.query;
|
||||
if (base->flags & URL_FLAGS_HAS_QUERY) {
|
||||
url->flags |= URL_FLAGS_HAS_QUERY;
|
||||
url->query = base->query;
|
||||
}
|
||||
if (DOES_HAVE_PATH(base)) {
|
||||
SET_HAVE_PATH()
|
||||
url.path = base.path;
|
||||
if (base->flags & URL_FLAGS_HAS_PATH) {
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path = base->path;
|
||||
}
|
||||
url.port = base.port;
|
||||
url->port = base->port;
|
||||
break;
|
||||
case '/':
|
||||
state = kRelativeSlash;
|
||||
break;
|
||||
case '?':
|
||||
if (DOES_HAVE_USERNAME(base)) {
|
||||
SET_HAVE_USERNAME()
|
||||
url.username = base.username;
|
||||
if (base->flags & URL_FLAGS_HAS_USERNAME) {
|
||||
url->flags |= URL_FLAGS_HAS_USERNAME;
|
||||
url->username = base->username;
|
||||
}
|
||||
if (DOES_HAVE_PASSWORD(base)) {
|
||||
SET_HAVE_PASSWORD()
|
||||
url.password = base.password;
|
||||
if (base->flags & URL_FLAGS_HAS_PASSWORD) {
|
||||
url->flags |= URL_FLAGS_HAS_PASSWORD;
|
||||
url->password = base->password;
|
||||
}
|
||||
if (DOES_HAVE_HOST(base)) {
|
||||
SET_HAVE_HOST()
|
||||
url.host = base.host;
|
||||
if (base->flags & URL_FLAGS_HAS_HOST) {
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
url->host = base->host;
|
||||
}
|
||||
if (DOES_HAVE_PATH(base)) {
|
||||
SET_HAVE_PATH()
|
||||
url.path = base.path;
|
||||
if (base->flags & URL_FLAGS_HAS_PATH) {
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path = base->path;
|
||||
}
|
||||
url.port = base.port;
|
||||
url->port = base->port;
|
||||
state = kQuery;
|
||||
break;
|
||||
case '#':
|
||||
if (DOES_HAVE_USERNAME(base)) {
|
||||
SET_HAVE_USERNAME()
|
||||
url.username = base.username;
|
||||
if (base->flags & URL_FLAGS_HAS_USERNAME) {
|
||||
url->flags |= URL_FLAGS_HAS_USERNAME;
|
||||
url->username = base->username;
|
||||
}
|
||||
if (DOES_HAVE_PASSWORD(base)) {
|
||||
SET_HAVE_PASSWORD()
|
||||
url.password = base.password;
|
||||
if (base->flags & URL_FLAGS_HAS_PASSWORD) {
|
||||
url->flags |= URL_FLAGS_HAS_PASSWORD;
|
||||
url->password = base->password;
|
||||
}
|
||||
if (DOES_HAVE_HOST(base)) {
|
||||
SET_HAVE_HOST()
|
||||
url.host = base.host;
|
||||
if (base->flags & URL_FLAGS_HAS_HOST) {
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
url->host = base->host;
|
||||
}
|
||||
if (DOES_HAVE_QUERY(base)) {
|
||||
SET_HAVE_QUERY()
|
||||
url.query = base.query;
|
||||
if (base->flags & URL_FLAGS_HAS_QUERY) {
|
||||
url->flags |= URL_FLAGS_HAS_QUERY;
|
||||
url->query = base->query;
|
||||
}
|
||||
if (DOES_HAVE_PATH(base)) {
|
||||
SET_HAVE_PATH()
|
||||
url.path = base.path;
|
||||
if (base->flags & URL_FLAGS_HAS_PATH) {
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path = base->path;
|
||||
}
|
||||
url.port = base.port;
|
||||
url->port = base->port;
|
||||
state = kFragment;
|
||||
break;
|
||||
default:
|
||||
if (special_back_slash) {
|
||||
state = kRelativeSlash;
|
||||
} else {
|
||||
if (DOES_HAVE_USERNAME(base)) {
|
||||
SET_HAVE_USERNAME()
|
||||
url.username = base.username;
|
||||
if (base->flags & URL_FLAGS_HAS_USERNAME) {
|
||||
url->flags |= URL_FLAGS_HAS_USERNAME;
|
||||
url->username = base->username;
|
||||
}
|
||||
if (DOES_HAVE_PASSWORD(base)) {
|
||||
SET_HAVE_PASSWORD()
|
||||
url.password = base.password;
|
||||
if (base->flags & URL_FLAGS_HAS_PASSWORD) {
|
||||
url->flags |= URL_FLAGS_HAS_PASSWORD;
|
||||
url->password = base->password;
|
||||
}
|
||||
if (DOES_HAVE_HOST(base)) {
|
||||
SET_HAVE_HOST()
|
||||
url.host = base.host;
|
||||
if (base->flags & URL_FLAGS_HAS_HOST) {
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
url->host = base->host;
|
||||
}
|
||||
if (DOES_HAVE_PATH(base)) {
|
||||
SET_HAVE_PATH()
|
||||
url.path = base.path;
|
||||
ShortenUrlPath(&url);
|
||||
if (base->flags & URL_FLAGS_HAS_PATH) {
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path = base->path;
|
||||
ShortenUrlPath(url);
|
||||
}
|
||||
url.port = base.port;
|
||||
url->port = base->port;
|
||||
state = kPath;
|
||||
continue;
|
||||
}
|
||||
@ -909,19 +864,19 @@ namespace url {
|
||||
if (ch == '/' || special_back_slash) {
|
||||
state = kSpecialAuthorityIgnoreSlashes;
|
||||
} else {
|
||||
if (DOES_HAVE_USERNAME(base)) {
|
||||
SET_HAVE_USERNAME()
|
||||
url.username = base.username;
|
||||
if (base->flags & URL_FLAGS_HAS_USERNAME) {
|
||||
url->flags |= URL_FLAGS_HAS_USERNAME;
|
||||
url->username = base->username;
|
||||
}
|
||||
if (DOES_HAVE_PASSWORD(base)) {
|
||||
SET_HAVE_PASSWORD()
|
||||
url.password = base.password;
|
||||
if (base->flags & URL_FLAGS_HAS_PASSWORD) {
|
||||
url->flags |= URL_FLAGS_HAS_PASSWORD;
|
||||
url->password = base->password;
|
||||
}
|
||||
if (DOES_HAVE_HOST(base)) {
|
||||
SET_HAVE_HOST()
|
||||
url.host = base.host;
|
||||
if (base->flags & URL_FLAGS_HAS_HOST) {
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
url->host = base->host;
|
||||
}
|
||||
url.port = base.port;
|
||||
url->port = base->port;
|
||||
state = kPath;
|
||||
continue;
|
||||
}
|
||||
@ -949,21 +904,21 @@ namespace url {
|
||||
atflag = true;
|
||||
const size_t blen = buffer.size();
|
||||
if (blen > 0 && buffer[0] != ':') {
|
||||
SET_HAVE_USERNAME()
|
||||
url->flags |= URL_FLAGS_HAS_USERNAME;
|
||||
}
|
||||
for (size_t n = 0; n < blen; n++) {
|
||||
const char bch = buffer[n];
|
||||
if (bch == ':') {
|
||||
SET_HAVE_PASSWORD()
|
||||
url->flags |= URL_FLAGS_HAS_PASSWORD;
|
||||
if (!uflag) {
|
||||
uflag = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (uflag) {
|
||||
AppendOrEscape(&url.password, bch, UserinfoEncodeSet);
|
||||
AppendOrEscape(&url->password, bch, UserinfoEncodeSet);
|
||||
} else {
|
||||
AppendOrEscape(&url.username, bch, UserinfoEncodeSet);
|
||||
AppendOrEscape(&url->username, bch, UserinfoEncodeSet);
|
||||
}
|
||||
}
|
||||
buffer.clear();
|
||||
@ -982,30 +937,42 @@ namespace url {
|
||||
case kHost:
|
||||
case kHostname:
|
||||
if (ch == ':' && !sbflag) {
|
||||
if (special && buffer.size() == 0)
|
||||
URL_FAILED()
|
||||
SET_HAVE_HOST()
|
||||
if (!ParseHost(&buffer, &url.host))
|
||||
URL_FAILED()
|
||||
if (special && buffer.size() == 0) {
|
||||
url->flags |= URL_FLAGS_FAILED;
|
||||
return;
|
||||
}
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
if (!ParseHost(&buffer, &url->host)) {
|
||||
url->flags |= URL_FLAGS_FAILED;
|
||||
return;
|
||||
}
|
||||
buffer.clear();
|
||||
state = kPort;
|
||||
if (state_override == kHostname)
|
||||
TERMINATE()
|
||||
if (state_override == kHostname) {
|
||||
url->flags |= URL_FLAGS_TERMINATED;
|
||||
return;
|
||||
}
|
||||
} else if (ch == kEOL ||
|
||||
ch == '/' ||
|
||||
ch == '?' ||
|
||||
ch == '#' ||
|
||||
special_back_slash) {
|
||||
p--;
|
||||
if (special && buffer.size() == 0)
|
||||
URL_FAILED()
|
||||
SET_HAVE_HOST()
|
||||
if (!ParseHost(&buffer, &url.host))
|
||||
URL_FAILED()
|
||||
if (special && buffer.size() == 0) {
|
||||
url->flags |= URL_FLAGS_FAILED;
|
||||
return;
|
||||
}
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
if (!ParseHost(&buffer, &url->host)) {
|
||||
url->flags |= URL_FLAGS_FAILED;
|
||||
return;
|
||||
}
|
||||
buffer.clear();
|
||||
state = kPathStart;
|
||||
if (has_state_override)
|
||||
TERMINATE()
|
||||
if (has_state_override) {
|
||||
url->flags |= URL_FLAGS_TERMINATED;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (ch == '[')
|
||||
sbflag = true;
|
||||
@ -1028,37 +995,39 @@ namespace url {
|
||||
for (size_t i = 0; i < buffer.size(); i++)
|
||||
port = port * 10 + buffer[i] - '0';
|
||||
if (port >= 0 && port <= 0xffff) {
|
||||
url.port = NormalizePort(url.scheme, port);
|
||||
url->port = NormalizePort(url->scheme, port);
|
||||
} else if (!has_state_override) {
|
||||
URL_FAILED()
|
||||
url->flags |= URL_FLAGS_FAILED;
|
||||
return;
|
||||
}
|
||||
buffer.clear();
|
||||
}
|
||||
state = kPathStart;
|
||||
continue;
|
||||
} else {
|
||||
URL_FAILED();
|
||||
url->flags |= URL_FLAGS_FAILED;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case kFile:
|
||||
base_is_file = (
|
||||
has_base &&
|
||||
DOES_HAVE_SCHEME(base) &&
|
||||
base.scheme == "file:");
|
||||
base->flags & URL_FLAGS_HAS_SCHEME &&
|
||||
base->scheme == "file:");
|
||||
switch (ch) {
|
||||
case kEOL:
|
||||
if (base_is_file) {
|
||||
if (DOES_HAVE_HOST(base)) {
|
||||
SET_HAVE_HOST()
|
||||
url.host = base.host;
|
||||
if (base->flags & URL_FLAGS_HAS_HOST) {
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
url->host = base->host;
|
||||
}
|
||||
if (DOES_HAVE_PATH(base)) {
|
||||
SET_HAVE_PATH()
|
||||
url.path = base.path;
|
||||
if (base->flags & URL_FLAGS_HAS_PATH) {
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path = base->path;
|
||||
}
|
||||
if (DOES_HAVE_QUERY(base)) {
|
||||
SET_HAVE_QUERY()
|
||||
url.query = base.query;
|
||||
if (base->flags & URL_FLAGS_HAS_QUERY) {
|
||||
url->flags |= URL_FLAGS_HAS_QUERY;
|
||||
url->query = base->query;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1070,31 +1039,31 @@ namespace url {
|
||||
break;
|
||||
case '?':
|
||||
if (base_is_file) {
|
||||
if (DOES_HAVE_HOST(base)) {
|
||||
SET_HAVE_HOST()
|
||||
url.host = base.host;
|
||||
if (base->flags & URL_FLAGS_HAS_HOST) {
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
url->host = base->host;
|
||||
}
|
||||
if (DOES_HAVE_PATH(base)) {
|
||||
SET_HAVE_PATH()
|
||||
url.path = base.path;
|
||||
if (base->flags & URL_FLAGS_HAS_PATH) {
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path = base->path;
|
||||
}
|
||||
SET_HAVE_QUERY()
|
||||
url->flags |= URL_FLAGS_HAS_QUERY;
|
||||
state = kQuery;
|
||||
break;
|
||||
}
|
||||
case '#':
|
||||
if (base_is_file) {
|
||||
if (DOES_HAVE_HOST(base)) {
|
||||
SET_HAVE_HOST()
|
||||
url.host = base.host;
|
||||
if (base->flags & URL_FLAGS_HAS_HOST) {
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
url->host = base->host;
|
||||
}
|
||||
if (DOES_HAVE_PATH(base)) {
|
||||
SET_HAVE_PATH()
|
||||
url.path = base.path;
|
||||
if (base->flags & URL_FLAGS_HAS_PATH) {
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path = base->path;
|
||||
}
|
||||
if (DOES_HAVE_QUERY(base)) {
|
||||
SET_HAVE_QUERY()
|
||||
url.query = base.query;
|
||||
if (base->flags & URL_FLAGS_HAS_QUERY) {
|
||||
url->flags |= URL_FLAGS_HAS_QUERY;
|
||||
url->query = base->query;
|
||||
}
|
||||
state = kFragment;
|
||||
break;
|
||||
@ -1107,15 +1076,15 @@ namespace url {
|
||||
p[2] != '\\' &&
|
||||
p[2] != '?' &&
|
||||
p[2] != '#'))) {
|
||||
if (DOES_HAVE_HOST(base)) {
|
||||
SET_HAVE_HOST()
|
||||
url.host = base.host;
|
||||
if (base->flags & URL_FLAGS_HAS_HOST) {
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
url->host = base->host;
|
||||
}
|
||||
if (DOES_HAVE_PATH(base)) {
|
||||
SET_HAVE_PATH()
|
||||
url.path = base.path;
|
||||
if (base->flags & URL_FLAGS_HAS_PATH) {
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path = base->path;
|
||||
}
|
||||
ShortenUrlPath(&url);
|
||||
ShortenUrlPath(url);
|
||||
}
|
||||
state = kPath;
|
||||
continue;
|
||||
@ -1126,13 +1095,13 @@ namespace url {
|
||||
state = kFileHost;
|
||||
} else {
|
||||
if (has_base &&
|
||||
DOES_HAVE_SCHEME(base) &&
|
||||
base.scheme == "file:" &&
|
||||
DOES_HAVE_PATH(base) &&
|
||||
base.path.size() > 0 &&
|
||||
NORMALIZED_WINDOWS_DRIVE_LETTER(base.path[0])) {
|
||||
SET_HAVE_PATH()
|
||||
url.path.push_back(base.path[0]);
|
||||
base->flags & URL_FLAGS_HAS_SCHEME &&
|
||||
base->scheme == "file:" &&
|
||||
base->flags & URL_FLAGS_HAS_PATH &&
|
||||
base->path.size() > 0 &&
|
||||
NORMALIZED_WINDOWS_DRIVE_LETTER(base->path[0])) {
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path.push_back(base->path[0]);
|
||||
}
|
||||
state = kPath;
|
||||
continue;
|
||||
@ -1151,9 +1120,11 @@ namespace url {
|
||||
state = kPathStart;
|
||||
} else {
|
||||
if (buffer != "localhost") {
|
||||
SET_HAVE_HOST()
|
||||
if (!ParseHost(&buffer, &url.host))
|
||||
URL_FAILED()
|
||||
url->flags |= URL_FLAGS_HAS_HOST;
|
||||
if (!ParseHost(&buffer, &url->host)) {
|
||||
url->flags |= URL_FLAGS_FAILED;
|
||||
return;
|
||||
}
|
||||
}
|
||||
buffer.clear();
|
||||
state = kPathStart;
|
||||
@ -1174,32 +1145,32 @@ namespace url {
|
||||
special_back_slash ||
|
||||
(!has_state_override && (ch == '?' || ch == '#'))) {
|
||||
if (IsDoubleDotSegment(buffer)) {
|
||||
ShortenUrlPath(&url);
|
||||
ShortenUrlPath(url);
|
||||
if (ch != '/' && !special_back_slash) {
|
||||
SET_HAVE_PATH()
|
||||
url.path.push_back("");
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path.push_back("");
|
||||
}
|
||||
} else if (IsSingleDotSegment(buffer)) {
|
||||
if (ch != '/' && !special_back_slash) {
|
||||
SET_HAVE_PATH();
|
||||
url.path.push_back("");
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
url->path.push_back("");
|
||||
}
|
||||
} else {
|
||||
if (DOES_HAVE_SCHEME(url) &&
|
||||
url.scheme == "file:" &&
|
||||
url.path.empty() &&
|
||||
if (url->flags & URL_FLAGS_HAS_SCHEME &&
|
||||
url->scheme == "file:" &&
|
||||
url->path.empty() &&
|
||||
buffer.size() == 2 &&
|
||||
WINDOWS_DRIVE_LETTER(buffer[0], buffer[1])) {
|
||||
url.flags &= ~URL_FLAGS_HAS_HOST;
|
||||
url->flags &= ~URL_FLAGS_HAS_HOST;
|
||||
buffer[1] = ':';
|
||||
}
|
||||
SET_HAVE_PATH()
|
||||
url->flags |= URL_FLAGS_HAS_PATH;
|
||||
std::string segment(buffer.c_str(), buffer.size());
|
||||
url.path.push_back(segment);
|
||||
url->path.push_back(segment);
|
||||
}
|
||||
buffer.clear();
|
||||
if (ch == '?') {
|
||||
SET_HAVE_QUERY()
|
||||
url->flags |= URL_FLAGS_HAS_QUERY;
|
||||
state = kQuery;
|
||||
} else if (ch == '#') {
|
||||
state = kFragment;
|
||||
@ -1217,16 +1188,16 @@ namespace url {
|
||||
state = kFragment;
|
||||
break;
|
||||
default:
|
||||
if (url.path.size() == 0)
|
||||
url.path.push_back("");
|
||||
if (url.path.size() > 0 && ch != kEOL)
|
||||
AppendOrEscape(&url.path[0], ch, SimpleEncodeSet);
|
||||
if (url->path.size() == 0)
|
||||
url->path.push_back("");
|
||||
if (url->path.size() > 0 && ch != kEOL)
|
||||
AppendOrEscape(&url->path[0], ch, SimpleEncodeSet);
|
||||
}
|
||||
break;
|
||||
case kQuery:
|
||||
if (ch == kEOL || (!has_state_override && ch == '#')) {
|
||||
SET_HAVE_QUERY()
|
||||
url.query = buffer;
|
||||
url->flags |= URL_FLAGS_HAS_QUERY;
|
||||
url->query = buffer;
|
||||
buffer.clear();
|
||||
if (ch == '#')
|
||||
state = kFragment;
|
||||
@ -1237,8 +1208,8 @@ namespace url {
|
||||
case kFragment:
|
||||
switch (ch) {
|
||||
case kEOL:
|
||||
SET_HAVE_FRAGMENT()
|
||||
url.fragment = buffer;
|
||||
url->flags |= URL_FLAGS_HAS_FRAGMENT;
|
||||
url->fragment = buffer;
|
||||
break;
|
||||
case 0:
|
||||
break;
|
||||
@ -1247,14 +1218,39 @@ namespace url {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
INVALID_PARSE_STATE()
|
||||
goto done;
|
||||
url->flags |= URL_FLAGS_INVALID_PARSE_STATE;
|
||||
return;
|
||||
}
|
||||
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
static void Parse(Environment* env,
|
||||
Local<Value> recv,
|
||||
const char* input,
|
||||
const size_t len,
|
||||
enum url_parse_state state_override,
|
||||
Local<Value> base_obj,
|
||||
Local<Value> context_obj,
|
||||
Local<Function> cb) {
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
HandleScope handle_scope(isolate);
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
const bool has_base = base_obj->IsObject();
|
||||
|
||||
struct url_data base;
|
||||
struct url_data url;
|
||||
if (context_obj->IsObject())
|
||||
HarvestContext(env, &url, context_obj.As<Object>());
|
||||
if (has_base)
|
||||
HarvestBase(env, &base, base_obj.As<Object>());
|
||||
|
||||
URL::Parse(input, len, state_override, &url, &base, has_base);
|
||||
if (url.flags & URL_FLAGS_INVALID_PARSE_STATE)
|
||||
return;
|
||||
|
||||
// Define the return value placeholders
|
||||
const Local<Value> undef = Undefined(isolate);
|
||||
@ -1271,22 +1267,22 @@ namespace url {
|
||||
};
|
||||
|
||||
argv[ARG_FLAGS] = Integer::NewFromUnsigned(isolate, url.flags);
|
||||
if (!IS_FAILED(url.flags)) {
|
||||
if (DOES_HAVE_SCHEME(url))
|
||||
if (!(url.flags & URL_FLAGS_FAILED)) {
|
||||
if (url.flags & URL_FLAGS_HAS_SCHEME)
|
||||
argv[ARG_PROTOCOL] = OneByteString(isolate, url.scheme.c_str());
|
||||
if (DOES_HAVE_USERNAME(url))
|
||||
if (url.flags & URL_FLAGS_HAS_USERNAME)
|
||||
argv[ARG_USERNAME] = UTF8STRING(isolate, url.username);
|
||||
if (DOES_HAVE_PASSWORD(url))
|
||||
if (url.flags & URL_FLAGS_HAS_PASSWORD)
|
||||
argv[ARG_PASSWORD] = UTF8STRING(isolate, url.password);
|
||||
if (DOES_HAVE_HOST(url))
|
||||
if (url.flags & URL_FLAGS_HAS_HOST)
|
||||
argv[ARG_HOST] = UTF8STRING(isolate, url.host);
|
||||
if (DOES_HAVE_QUERY(url))
|
||||
if (url.flags & URL_FLAGS_HAS_QUERY)
|
||||
argv[ARG_QUERY] = UTF8STRING(isolate, url.query);
|
||||
if (DOES_HAVE_FRAGMENT(url))
|
||||
if (url.flags & URL_FLAGS_HAS_FRAGMENT)
|
||||
argv[ARG_FRAGMENT] = UTF8STRING(isolate, url.fragment);
|
||||
if (url.port > -1)
|
||||
argv[ARG_PORT] = Integer::New(isolate, url.port);
|
||||
if (DOES_HAVE_PATH(url))
|
||||
if (url.flags & URL_FLAGS_HAS_PATH)
|
||||
argv[ARG_PATH] = Copy(env, url.path);
|
||||
}
|
||||
|
||||
|
@ -470,19 +470,19 @@ enum url_parse_state {
|
||||
#define XX(name) name,
|
||||
PARSESTATES(XX)
|
||||
#undef XX
|
||||
} url_parse_state;
|
||||
};
|
||||
|
||||
enum url_flags {
|
||||
#define XX(name, val) name = val,
|
||||
FLAGS(XX)
|
||||
#undef XX
|
||||
} url_flags;
|
||||
};
|
||||
|
||||
enum url_cb_args {
|
||||
#define XX(name) name,
|
||||
ARGS(XX)
|
||||
#undef XX
|
||||
} url_cb_args;
|
||||
};
|
||||
|
||||
static inline bool IsSpecial(std::string scheme) {
|
||||
#define XX(name, _) if (scheme == name) return true;
|
||||
@ -528,6 +528,91 @@ struct url_host {
|
||||
url_host_value value;
|
||||
enum url_host_type type;
|
||||
};
|
||||
|
||||
class URL {
|
||||
public:
|
||||
static void Parse(const char* input,
|
||||
const size_t len,
|
||||
enum url_parse_state state_override,
|
||||
struct url_data* url,
|
||||
const struct url_data* base,
|
||||
bool has_base);
|
||||
|
||||
URL(const char* input, const size_t len) {
|
||||
Parse(input, len, kUnknownState, &context_, nullptr, false);
|
||||
}
|
||||
|
||||
URL(const char* input, const size_t len, const URL* base) {
|
||||
if (base != nullptr)
|
||||
Parse(input, len, kUnknownState, &context_, &(base->context_), true);
|
||||
else
|
||||
Parse(input, len, kUnknownState, &context_, nullptr, false);
|
||||
}
|
||||
|
||||
URL(const char* input, const size_t len,
|
||||
const char* base, const size_t baselen) {
|
||||
if (base != nullptr && baselen > 0) {
|
||||
URL _base(base, baselen);
|
||||
Parse(input, len, kUnknownState, &context_, &(_base.context_), true);
|
||||
} else {
|
||||
Parse(input, len, kUnknownState, &context_, nullptr, false);
|
||||
}
|
||||
}
|
||||
|
||||
explicit URL(std::string input) :
|
||||
URL(input.c_str(), input.length()) {}
|
||||
|
||||
URL(std::string input, const URL* base) :
|
||||
URL(input.c_str(), input.length(), base) {}
|
||||
|
||||
URL(std::string input, std::string base) :
|
||||
URL(input.c_str(), input.length(), base.c_str(), base.length()) {}
|
||||
|
||||
int32_t flags() {
|
||||
return context_.flags;
|
||||
}
|
||||
|
||||
int port() {
|
||||
return context_.port;
|
||||
}
|
||||
|
||||
const std::string& protocol() const {
|
||||
return context_.scheme;
|
||||
}
|
||||
|
||||
const std::string& username() const {
|
||||
return context_.username;
|
||||
}
|
||||
|
||||
const std::string& password() const {
|
||||
return context_.password;
|
||||
}
|
||||
|
||||
const std::string& host() const {
|
||||
return context_.host;
|
||||
}
|
||||
|
||||
const std::string& query() const {
|
||||
return context_.query;
|
||||
}
|
||||
|
||||
const std::string& fragment() const {
|
||||
return context_.fragment;
|
||||
}
|
||||
|
||||
std::string path() {
|
||||
std::string ret;
|
||||
for (auto i = context_.path.begin(); i != context_.path.end(); i++) {
|
||||
ret += '/';
|
||||
ret += *i;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
struct url_data context_;
|
||||
};
|
||||
|
||||
} // namespace url
|
||||
|
||||
} // namespace node
|
||||
|
Loading…
x
Reference in New Issue
Block a user