Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
url: reject non-IPv4 hostnames that end in numbers
  • Loading branch information
F3n67u committed Jun 29, 2022
commit 5ad2a9b83fe021e78acdf91998539568344ccd6c
56 changes: 41 additions & 15 deletions src/node_url.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "node_i18n.h"
#include "util-inl.h"

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <numeric>
Expand Down Expand Up @@ -58,7 +59,7 @@ class URLHost {
public:
~URLHost();

void ParseIPv4Host(const char* input, size_t length, bool* is_ipv4);
void ParseIPv4Host(const char* input, size_t length);
void ParseIPv6Host(const char* input, size_t length);
void ParseOpaqueHost(const char* input, size_t length);
void ParseHost(const char* input,
Expand Down Expand Up @@ -359,18 +360,21 @@ void URLHost::ParseIPv6Host(const char* input, size_t length) {
type_ = HostType::H_IPV6;
}

int64_t ParseNumber(const char* start, const char* end) {
// https://url.spec.whatwg.org/#ipv4-number-parser
int64_t ParseIPv4Number(const char* start, const char* end) {
if (end - start == 0) return -1;

unsigned R = 10;
if (end - start >= 2 && start[0] == '0' && (start[1] | 0x20) == 'x') {
start += 2;
R = 16;
}
if (end - start == 0) {
return 0;
} else if (R == 10 && end - start > 1 && start[0] == '0') {
} else if (end - start >= 2 && start[0] == '0') {
start++;
R = 8;
}

if (end - start == 0) return 0;

const char* p = start;

while (p < end) {
Expand All @@ -394,9 +398,33 @@ int64_t ParseNumber(const char* start, const char* end) {
return strtoll(start, nullptr, R);
}

void URLHost::ParseIPv4Host(const char* input, size_t length, bool* is_ipv4) {
// https://url.spec.whatwg.org/#ends-in-a-number-checker
bool EndsInANumber(const std::string& input) {
std::vector<std::string> parts = SplitString(input, '.', false);

if (parts.empty()) return false;

if (parts.back().empty()) {
if (parts.size() == 1) return false;
parts.pop_back();
}

const std::string& last = parts.back();

// If last is non-empty and contains only ASCII digits, then return true
if (!last.empty() && std::all_of(last.begin(), last.end(), ::isdigit)) {
return true;
}

const char* last_str = last.c_str();
int64_t num = ParseIPv4Number(last_str, last_str + last.size());
if (num >= 0) return true;

return false;
}

void URLHost::ParseIPv4Host(const char* input, size_t length) {
CHECK_EQ(type_, HostType::H_FAILED);
*is_ipv4 = false;
const char* pointer = input;
const char* mark = input;
const char* end = pointer + length;
Expand All @@ -414,7 +442,7 @@ void URLHost::ParseIPv4Host(const char* input, size_t length, bool* is_ipv4) {
if (++parts > static_cast<int>(arraysize(numbers))) return;
if (pointer == mark)
return;
int64_t n = ParseNumber(mark, pointer);
int64_t n = ParseIPv4Number(mark, pointer);
if (n < 0)
return;

Expand All @@ -429,7 +457,6 @@ void URLHost::ParseIPv4Host(const char* input, size_t length, bool* is_ipv4) {
pointer++;
}
CHECK_GT(parts, 0);
*is_ipv4 = true;

// If any but the last item in numbers is greater than 255, return failure.
// If the last item in numbers is greater than or equal to
Expand Down Expand Up @@ -501,11 +528,10 @@ void URLHost::ParseHost(const char* input,
}
}

// Check to see if it's an IPv4 IP address
bool is_ipv4;
ParseIPv4Host(decoded.c_str(), decoded.length(), &is_ipv4);
if (is_ipv4)
return;
// If domain ends in a number, then return the result of IPv4 parsing domain
if (EndsInANumber(decoded)) {
return ParseIPv4Host(decoded.c_str(), decoded.length());
}

// If the unicode flag is set, run the result through punycode ToUnicode
if (unicode && !ToUnicode(decoded, &decoded))
Expand Down
6 changes: 4 additions & 2 deletions src/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,17 @@ std::string GetHumanReadableProcessName() {
return SPrintF("%s[%d]", GetProcessTitle("Node.js"), uv_os_getpid());
}

std::vector<std::string> SplitString(const std::string& in, char delim) {
std::vector<std::string> SplitString(const std::string& in,
char delim,
bool skipEmpty) {
std::vector<std::string> out;
if (in.empty())
return out;
std::istringstream in_stream(in);
while (in_stream.good()) {
std::string item;
std::getline(in_stream, item, delim);
if (item.empty()) continue;
if (item.empty() && skipEmpty) continue;
out.emplace_back(std::move(item));
}
return out;
Expand Down
4 changes: 3 additions & 1 deletion src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,9 @@ struct FunctionDeleter {
template <typename T, void (*function)(T*)>
using DeleteFnPtr = typename FunctionDeleter<T, function>::Pointer;

std::vector<std::string> SplitString(const std::string& in, char delim);
std::vector<std::string> SplitString(const std::string& in,
char delim,
bool skipEmpty = true);

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