Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ if(NOT TARGET UrlLib AND JSRUNTIMEHOST_POLYFILL_XMLHTTPREQUEST)
FetchContent_Declare(
UrlLib
GIT_REPOSITORY https://github.com/BabylonJS/UrlLib.git
GIT_TAG 0007501304a710f14626f34a73c05bcb371de92f)
GIT_TAG 558af491b2ec413d2eefefef1629dc652a5f9a04)

message(STATUS "Fetching UrlLib")
FetchContent_MakeAvailable(UrlLib)
Expand Down
48 changes: 10 additions & 38 deletions Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,6 @@
#include <Babylon/Polyfills/XMLHttpRequest.h>
#include <sstream>

bool IsHexChar(const char& c)
{
return ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'));
}

std::string EncodePercent(const std::string& input)
{
std::ostringstream encoded;
for (auto i = input.begin(), e = input.end(); i != e; ++i)
{
encoded << *i;
if (*i == '%')
{
if (std::distance(i, e) >= 2 && !(IsHexChar(*(i + 1)) && IsHexChar(*(i + 2))))
{
// If a percent character is not followed by two hex characters, we should encode it
encoded << "25";
}
}
}
return encoded.str();
}

namespace Babylon::Polyfills::Internal
{
namespace
Expand Down Expand Up @@ -210,38 +187,33 @@ namespace Babylon::Polyfills::Internal

void XMLHttpRequest::Open(const Napi::CallbackInfo& info)
{
const auto inputURL = info[1].As<Napi::String>();

try
{
// printfs for debugging CI, will be removed
const auto inputURL{info[1].As<Napi::String>()};
// If the input URL contains any true % characters, encode them as %25
const auto encodedPercentURL{Napi::String::New(info.Env(), EncodePercent(inputURL.Utf8Value()))};
// Decode the input URL to get a completely unencoded URL
const auto decodedURL{info.Env().Global().Get("decodeURI").As<Napi::Function>().Call({encodedPercentURL})};
// Re-encode the URL to make sure that every illegal character is encoded
const auto finalURL{info.Env().Global().Get("encodeURI").As<Napi::Function>().Call({decodedURL}).As<Napi::String>()};
m_request.Open(MethodType::StringToEnum(info[0].As<Napi::String>().Utf8Value()), finalURL.Utf8Value());
SetReadyState(ReadyState::Opened);
m_request.Open(MethodType::StringToEnum(info[0].As<Napi::String>().Utf8Value()), inputURL);
}
catch (const std::exception& e)
{
// If we have a parse error, catch and rethrow to JavaScript
throw Napi::Error::New(info.Env(), std::string{"Error parsing URL scheme: "} + e.what());
throw Napi::Error::New(info.Env(), std::string{"Error opening URL: "} + e.what());
}
catch (...)
{
throw Napi::Error::New(info.Env(), "Unknown error parsing URL scheme");
throw Napi::Error::New(info.Env(), "Unknown error opening URL");
}

SetReadyState(ReadyState::Opened);
}

void XMLHttpRequest::Send(const Napi::CallbackInfo& info)
{
if (m_readyState != ReadyState::Opened)
{
throw Napi::Error::New(info.Env(), "XMLHttpRequest must be opened before it can be sent");
return;
}
m_request.SendAsync().then(m_runtimeScheduler, arcana::cancellation::none(), [env{info.Env()}, this](arcana::expected<void, std::exception_ptr> result) {

m_request.SendAsync().then(m_runtimeScheduler, arcana::cancellation::none(), [env{info.Env()}, this](arcana::expected<void, std::exception_ptr> result)
{
if (result.has_error())
{
Napi::Error::New(env, result.error()).ThrowAsJavaScriptException();
Expand Down
23 changes: 9 additions & 14 deletions Tests/UnitTests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,37 +32,32 @@ describe("XMLHTTPRequest", function () {
this.timeout(0);

it("should have readyState=4 when load ends", async function () {
const xhr = await createRequest("GET", "https://babylonjs.com");
const xhr = await createRequest("GET", "https://httpbin.org/get");
expect(xhr.readyState).to.equal(4);
});

it("should have status=200 for a file that exists", async function () {
const xhr = await createRequest("GET", "https://babylonjs.com");
const xhr = await createRequest("GET", "https://httpbin.org/status/200");
expect(xhr.status).to.equal(200);
});

it("should load unescaped URLs", async function () {
const xhr = await createRequest("GET", "https://github.com/BabylonJS/Assets/raw/master/meshes/στρογγυλεμένος % κύβος.glb");
it("should load URLs with escaped unicode characters", async function () {
const xhr = await createRequest("GET", "https://raw.githubusercontent.com/BabylonJS/Assets/master/meshes/%CF%83%CF%84%CF%81%CE%BF%CE%B3%CE%B3%CF%85%CE%BB%CE%B5%CE%BC%CE%AD%CE%BD%CE%BF%CF%82%20%25%20%CE%BA%CF%8D%CE%B2%CE%BF%CF%82.glb");
expect(xhr.status).to.equal(200);
});

it("should load partially unescaped URLs", async function () {
const xhr = await createRequest("GET", "https://github.com/BabylonJS/Assets/raw/master/meshes/στρογγυλεμένος%20%%20κύβος.glb");
it("should load URLs with unescaped unicode characters", async function () {
const xhr = await createRequest("GET", "https://raw.githubusercontent.com/BabylonJS/Assets/master/meshes/στρογγυλεμένος%20%25%20κύβος.glb");
expect(xhr.status).to.equal(200);
});

it("should load escaped URLs", async function () {
const xhr = await createRequest("GET", "https://github.com/BabylonJS/Assets/raw/master/meshes/%CF%83%CF%84%CF%81%CE%BF%CE%B3%CE%B3%CF%85%CE%BB%CE%B5%CE%BC%CE%AD%CE%BD%CE%BF%CF%82%20%25%20%CE%BA%CF%8D%CE%B2%CE%BF%CF%82.glb");
expect(xhr.status).to.equal(200);
});

it("should load URLs with unescaped %s", async function () {
const xhr = await createRequest("GET", "https://github.com/BabylonJS/Assets/raw/master/meshes/%CF%83%CF%84%CF%81%CE%BF%CE%B3%CE%B3%CF%85%CE%BB%CE%B5%CE%BC%CE%AD%CE%BD%CE%BF%CF%82%20%%20%CE%BA%CF%8D%CE%B2%CE%BF%CF%82.glb");
it("should load URLs with unescaped unicode characters and spaces", async function () {
const xhr = await createRequest("GET", "https://raw.githubusercontent.com/BabylonJS/Assets/master/meshes/στρογγυλεμένος %25 κύβος.glb");
expect(xhr.status).to.equal(200);
});

it("should have status=404 for a file that does not exist", async function () {
const xhr = await createRequest("GET", "https://babylonjs.com/invalid");
const xhr = await createRequest("GET", "https://httpbin.org/status/404");
expect(xhr.status).to.equal(404);
});

Expand Down