Skip to content

Commit 2348ce9

Browse files
committed
url: reject non-IPv4 hostnames that end in numbers
1 parent 1f96b1c commit 2348ce9

File tree

3 files changed

+49
-18
lines changed

3 files changed

+49
-18
lines changed

src/node_url.cc

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "node_i18n.h"
66
#include "util-inl.h"
77

8+
#include <algorithm>
89
#include <cmath>
910
#include <cstdio>
1011
#include <numeric>
@@ -58,7 +59,7 @@ class URLHost {
5859
public:
5960
~URLHost();
6061

61-
void ParseIPv4Host(const char* input, size_t length, bool* is_ipv4);
62+
void ParseIPv4Host(const char* input, size_t length);
6263
void ParseIPv6Host(const char* input, size_t length);
6364
void ParseOpaqueHost(const char* input, size_t length);
6465
void ParseHost(const char* input,
@@ -359,18 +360,21 @@ void URLHost::ParseIPv6Host(const char* input, size_t length) {
359360
type_ = HostType::H_IPV6;
360361
}
361362

362-
int64_t ParseNumber(const char* start, const char* end) {
363+
// https://url.spec.whatwg.org/#ipv4-number-parser
364+
int64_t ParseIPv4Number(const char* start, const char* end) {
365+
if (end - start == 0) return -1;
366+
363367
unsigned R = 10;
364368
if (end - start >= 2 && start[0] == '0' && (start[1] | 0x20) == 'x') {
365369
start += 2;
366370
R = 16;
367-
}
368-
if (end - start == 0) {
369-
return 0;
370-
} else if (R == 10 && end - start > 1 && start[0] == '0') {
371+
} else if (end - start >= 2 && start[0] == '0') {
371372
start++;
372373
R = 8;
373374
}
375+
376+
if (end - start == 0) return 0;
377+
374378
const char* p = start;
375379

376380
while (p < end) {
@@ -394,9 +398,34 @@ int64_t ParseNumber(const char* start, const char* end) {
394398
return strtoll(start, nullptr, R);
395399
}
396400

397-
void URLHost::ParseIPv4Host(const char* input, size_t length, bool* is_ipv4) {
401+
// https://url.spec.whatwg.org/#ends-in-a-number-checker
402+
bool EndsInANumber(const std::string& input) {
403+
std::vector<std::string> parts = SplitString(input, '.', false);
404+
405+
if (parts.empty()) return false;
406+
407+
if (parts.back().empty()) {
408+
if (parts.size() == 1) return false;
409+
parts.pop_back();
410+
}
411+
412+
const std::string& last = parts.back();
413+
414+
// If last is non-empty and contains only ASCII digits, then return true
415+
if (!last.empty() &&
416+
std::all_of(last.begin(), last.end(), ::isdigit)) {
417+
return true;
418+
}
419+
420+
const char* last_str = last.c_str();
421+
int64_t num = ParseIPv4Number(last_str, last_str + last.size());
422+
if (num >= 0) return true;
423+
424+
return false;
425+
}
426+
427+
void URLHost::ParseIPv4Host(const char* input, size_t length) {
398428
CHECK_EQ(type_, HostType::H_FAILED);
399-
*is_ipv4 = false;
400429
const char* pointer = input;
401430
const char* mark = input;
402431
const char* end = pointer + length;
@@ -414,7 +443,7 @@ void URLHost::ParseIPv4Host(const char* input, size_t length, bool* is_ipv4) {
414443
if (++parts > static_cast<int>(arraysize(numbers))) return;
415444
if (pointer == mark)
416445
return;
417-
int64_t n = ParseNumber(mark, pointer);
446+
int64_t n = ParseIPv4Number(mark, pointer);
418447
if (n < 0)
419448
return;
420449

@@ -429,7 +458,6 @@ void URLHost::ParseIPv4Host(const char* input, size_t length, bool* is_ipv4) {
429458
pointer++;
430459
}
431460
CHECK_GT(parts, 0);
432-
*is_ipv4 = true;
433461

434462
// If any but the last item in numbers is greater than 255, return failure.
435463
// If the last item in numbers is greater than or equal to
@@ -501,11 +529,10 @@ void URLHost::ParseHost(const char* input,
501529
}
502530
}
503531

504-
// Check to see if it's an IPv4 IP address
505-
bool is_ipv4;
506-
ParseIPv4Host(decoded.c_str(), decoded.length(), &is_ipv4);
507-
if (is_ipv4)
508-
return;
532+
// If domain ends in a number, then return the result of IPv4 parsing domain
533+
if (EndsInANumber(decoded)) {
534+
return ParseIPv4Host(decoded.c_str(), decoded.length());
535+
}
509536

510537
// If the unicode flag is set, run the result through punycode ToUnicode
511538
if (unicode && !ToUnicode(decoded, &decoded))

src/util.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,17 @@ std::string GetHumanReadableProcessName() {
164164
return SPrintF("%s[%d]", GetProcessTitle("Node.js"), uv_os_getpid());
165165
}
166166

167-
std::vector<std::string> SplitString(const std::string& in, char delim) {
167+
std::vector<std::string> SplitString(const std::string& in,
168+
char delim,
169+
bool skipEmpty) {
168170
std::vector<std::string> out;
169171
if (in.empty())
170172
return out;
171173
std::istringstream in_stream(in);
172174
while (in_stream.good()) {
173175
std::string item;
174176
std::getline(in_stream, item, delim);
175-
if (item.empty()) continue;
177+
if (item.empty() && skipEmpty) continue;
176178
out.emplace_back(std::move(item));
177179
}
178180
return out;

src/util.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,9 @@ struct FunctionDeleter {
645645
template <typename T, void (*function)(T*)>
646646
using DeleteFnPtr = typename FunctionDeleter<T, function>::Pointer;
647647

648-
std::vector<std::string> SplitString(const std::string& in, char delim);
648+
std::vector<std::string> SplitString(const std::string& in,
649+
char delim,
650+
bool skipEmpty = true);
649651

650652
inline v8::MaybeLocal<v8::Value> ToV8Value(v8::Local<v8::Context> context,
651653
std::string_view str,

0 commit comments

Comments
 (0)