Browse Source

url: fix C0 control and whitespace handling

PR-URL: https://github.com/nodejs/node/pull/12846
Fixes: https://github.com/nodejs/node/issues/12825
Refs: https://github.com/w3c/web-platform-tests/pull/5792
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
v6
Timothy Gu 8 years ago
parent
commit
841bb4c61f
No known key found for this signature in database GPG Key ID: 7FE6B095B582B0D4
  1. 59
      src/node_url.cc
  2. 17
      src/node_url.h
  3. 32
      test/fixtures/url-tests.js

59
src/node_url.cc

@ -133,6 +133,9 @@ enum url_error_cb_args {
// https://infra.spec.whatwg.org/#ascii-tab-or-newline // https://infra.spec.whatwg.org/#ascii-tab-or-newline
CHAR_TEST(8, IsASCIITabOrNewline, (ch == '\t' || ch == '\n' || ch == '\r')) CHAR_TEST(8, IsASCIITabOrNewline, (ch == '\t' || ch == '\n' || ch == '\r'))
// https://infra.spec.whatwg.org/#c0-control-or-space
CHAR_TEST(8, IsC0ControlOrSpace, (ch >= '\0' && ch <= ' '))
// https://infra.spec.whatwg.org/#ascii-digit // https://infra.spec.whatwg.org/#ascii-digit
CHAR_TEST(8, IsASCIIDigit, (ch >= '0' && ch <= '9')) CHAR_TEST(8, IsASCIIDigit, (ch >= '0' && ch <= '9'))
@ -1134,15 +1137,45 @@ static inline void ShortenUrlPath(struct url_data* url) {
} }
void URL::Parse(const char* input, void URL::Parse(const char* input,
const size_t len, size_t len,
enum url_parse_state state_override, enum url_parse_state state_override,
struct url_data* url, struct url_data* url,
bool has_url,
const struct url_data* base, const struct url_data* base,
bool has_base) { bool has_base) {
const char* p = input;
const char* end = input + len;
if (!has_url) {
for (const char* ptr = p; ptr < end; ptr++) {
if (IsC0ControlOrSpace(*ptr))
p++;
else
break;
}
for (const char* ptr = end - 1; ptr >= p; ptr--) {
if (IsC0ControlOrSpace(*ptr))
end--;
else
break;
}
len = end - p;
}
std::string whitespace_stripped;
whitespace_stripped.reserve(len);
for (const char* ptr = p; ptr < end; ptr++)
if (!IsASCIITabOrNewline(*ptr))
whitespace_stripped += *ptr;
input = whitespace_stripped.c_str();
len = whitespace_stripped.size();
p = input;
end = input + len;
bool atflag = false; bool atflag = false;
bool sbflag = false; bool sbflag = false;
bool uflag = false; bool uflag = false;
int wskip = 0;
std::string buffer; std::string buffer;
url->scheme.reserve(len); url->scheme.reserve(len);
@ -1159,9 +1192,6 @@ void URL::Parse(const char* input,
enum url_parse_state state = has_state_override ? state_override : enum url_parse_state state = has_state_override ? state_override :
kSchemeStart; kSchemeStart;
const char* p = input;
const char* end = input + len;
if (state < kSchemeStart || state > kFragment) { if (state < kSchemeStart || state > kFragment) {
url->flags |= URL_FLAGS_INVALID_PARSE_STATE; url->flags |= URL_FLAGS_INVALID_PARSE_STATE;
return; return;
@ -1171,18 +1201,6 @@ void URL::Parse(const char* input,
const char ch = p < end ? p[0] : kEOL; const char ch = p < end ? p[0] : kEOL;
const size_t remaining = end == p ? 0 : (end - p - 1); const size_t remaining = end == p ? 0 : (end - p - 1);
if (IsASCIITabOrNewline(ch)) {
if (state == kAuthority) {
// It's necessary to keep track of how much whitespace
// is being ignored when in kAuthority state because of
// how the buffer is managed. TODO: See if there's a better
// way
wskip++;
}
p++;
continue;
}
bool special = (url->flags & URL_FLAGS_SPECIAL); bool special = (url->flags & URL_FLAGS_SPECIAL);
bool cannot_be_base; bool cannot_be_base;
const bool special_back_slash = (special && ch == '\\'); const bool special_back_slash = (special && ch == '\\');
@ -1500,7 +1518,7 @@ void URL::Parse(const char* input,
url->flags |= URL_FLAGS_FAILED; url->flags |= URL_FLAGS_FAILED;
return; return;
} }
p -= buffer.size() + 1 + wskip; p -= buffer.size() + 1;
buffer.clear(); buffer.clear();
state = kHost; state = kHost;
} else { } else {
@ -1892,16 +1910,17 @@ static void Parse(Environment* env,
HandleScope handle_scope(isolate); HandleScope handle_scope(isolate);
Context::Scope context_scope(context); Context::Scope context_scope(context);
const bool has_context = context_obj->IsObject();
const bool has_base = base_obj->IsObject(); const bool has_base = base_obj->IsObject();
struct url_data base; struct url_data base;
struct url_data url; struct url_data url;
if (context_obj->IsObject()) if (has_context)
HarvestContext(env, &url, context_obj.As<Object>()); HarvestContext(env, &url, context_obj.As<Object>());
if (has_base) if (has_base)
HarvestBase(env, &base, base_obj.As<Object>()); HarvestBase(env, &base, base_obj.As<Object>());
URL::Parse(input, len, state_override, &url, &base, has_base); URL::Parse(input, len, state_override, &url, has_context, &base, has_base);
if ((url.flags & URL_FLAGS_INVALID_PARSE_STATE) || if ((url.flags & URL_FLAGS_INVALID_PARSE_STATE) ||
((state_override != kUnknownState) && ((state_override != kUnknownState) &&
(url.flags & URL_FLAGS_TERMINATED))) (url.flags & URL_FLAGS_TERMINATED)))

17
src/node_url.h

@ -81,30 +81,35 @@ struct url_data {
class URL { class URL {
public: public:
static void Parse(const char* input, static void Parse(const char* input,
const size_t len, size_t len,
enum url_parse_state state_override, enum url_parse_state state_override,
struct url_data* url, struct url_data* url,
bool has_url,
const struct url_data* base, const struct url_data* base,
bool has_base); bool has_base);
URL(const char* input, const size_t len) { URL(const char* input, const size_t len) {
Parse(input, len, kUnknownState, &context_, nullptr, false); Parse(input, len, kUnknownState, &context_, false, nullptr, false);
} }
URL(const char* input, const size_t len, const URL* base) { URL(const char* input, const size_t len, const URL* base) {
if (base != nullptr) if (base != nullptr)
Parse(input, len, kUnknownState, &context_, &(base->context_), true); Parse(input, len, kUnknownState,
&context_, false,
&(base->context_), true);
else else
Parse(input, len, kUnknownState, &context_, nullptr, false); Parse(input, len, kUnknownState, &context_, false, nullptr, false);
} }
URL(const char* input, const size_t len, URL(const char* input, const size_t len,
const char* base, const size_t baselen) { const char* base, const size_t baselen) {
if (base != nullptr && baselen > 0) { if (base != nullptr && baselen > 0) {
URL _base(base, baselen); URL _base(base, baselen);
Parse(input, len, kUnknownState, &context_, &(_base.context_), true); Parse(input, len, kUnknownState,
&context_, false,
&(_base.context_), true);
} else { } else {
Parse(input, len, kUnknownState, &context_, nullptr, false); Parse(input, len, kUnknownState, &context_, false, nullptr, false);
} }
} }

32
test/fixtures/url-tests.js

@ -1,7 +1,7 @@
'use strict'; 'use strict';
/* WPT Refs: /* WPT Refs:
https://github.com/w3c/web-platform-tests/blob/28541bb/url/urltestdata.json https://github.com/w3c/web-platform-tests/blob/0f26c418a5/url/urltestdata.json
License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html
*/ */
module.exports = module.exports =
@ -3566,6 +3566,22 @@ module.exports =
"search": "", "search": "",
"hash": "" "hash": ""
}, },
"Leading and trailing C0 control or space",
{
"input": "\u0000\u001b\u0004\u0012 http://example.com/\u001f \u000d ",
"base": "about:blank",
"href": "http://example.com/",
"origin": "http://example.com",
"protocol": "http:",
"username": "",
"password": "",
"host": "example.com",
"hostname": "example.com",
"port": "",
"pathname": "/",
"search": "",
"hash": ""
},
"Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)", "Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)",
{ {
"input": "http://www.foo。bar.com", "input": "http://www.foo。bar.com",
@ -5487,6 +5503,20 @@ module.exports =
"search": "", "search": "",
"hash": "" "hash": ""
}, },
{
"input": "C|\n/",
"base": "file://host/dir/file",
"href": "file:///C:/",
"protocol": "file:",
"username": "",
"password": "",
"host": "",
"hostname": "",
"port": "",
"pathname": "/C:/",
"search": "",
"hash": ""
},
{ {
"input": "C|\\", "input": "C|\\",
"base": "file://host/dir/file", "base": "file://host/dir/file",

Loading…
Cancel
Save