Skip to content

Commit 0b28a37

Browse files
authored
Fix relative $ref on relative path $id (#1580)
Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent 41db295 commit 0b28a37

File tree

4 files changed

+67
-3
lines changed

4 files changed

+67
-3
lines changed

src/core/uri/include/sourcemeta/core/uri.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ class SOURCEMETA_CORE_URI_EXPORT URI {
295295
/// ```
296296
auto resolve_from(const URI &base) -> URI &;
297297

298+
// TODO: Do we really need this `try_resolve_from` method? There shouldn't
299+
// be any reason why resolution cannot happen. This is probably just an
300+
// artifact of `uriparser` not supporting relative resolution
301+
298302
/// Resolve a relative URI against a base URI as established by RFC
299303
/// 3986. If the resolution cannot happen, nothing happens. For example:
300304
///
@@ -420,7 +424,7 @@ class SOURCEMETA_CORE_URI_EXPORT URI {
420424
std::optional<std::string> query_;
421425
bool is_ipv6_ = false;
422426

423-
// Use PIMPL idiom to hide `urlparser`
427+
// Use PIMPL idiom to hide `uriparser`
424428
struct Internal;
425429
std::unique_ptr<Internal> internal;
426430
#if defined(_MSC_VER)

src/core/uri/uri.cc

+17-2
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,9 @@ auto URI::try_resolve_from(const URI &base) -> URI & {
583583
return this->resolve_from(base);
584584

585585
// TODO: This only handles a very specific case. We should generalize this
586-
// function to perform proper base resolution on relative bases
586+
// function to perform proper base resolution on relative bases instead of
587+
// doing these one-off workarounds
588+
587589
} else if (this->is_fragment_only() && !base.fragment().has_value()) {
588590
this->data = base.data;
589591
this->path_ = base.path_;
@@ -593,7 +595,20 @@ auto URI::try_resolve_from(const URI &base) -> URI & {
593595
this->scheme_ = base.scheme_;
594596
this->query_ = base.query_;
595597
return *this;
596-
598+
} else if (base.path().has_value() && base.path().value().starts_with("..")) {
599+
return *this;
600+
} else if (base.is_relative() && this->is_relative() &&
601+
base.path_.has_value() && this->path_.has_value() &&
602+
this->path_.value().find('/') == std::string::npos &&
603+
!base.recompose().starts_with('/')) {
604+
assert(base.is_relative());
605+
URI absolute_base{"https://stub.local/" + base.recompose()};
606+
assert(absolute_base.is_absolute());
607+
auto copy = *this;
608+
copy.resolve_from(absolute_base);
609+
this->data = copy.recompose().substr(19);
610+
this->parse();
611+
return *this;
597612
} else {
598613
return *this;
599614
}

test/jsonschema/jsonschema_frame_2020_12_test.cc

+38
Original file line numberDiff line numberDiff line change
@@ -1728,6 +1728,44 @@ TEST(JSONSchema_frame_2020_12, relative_base_uri_with_ref) {
17281728
"foo");
17291729
}
17301730

1731+
TEST(JSONSchema_frame_2020_12, relative_base_with_relative_path_ref) {
1732+
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
1733+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1734+
"$id": "foo/bar/baz",
1735+
"$ref": "qux"
1736+
})JSON");
1737+
1738+
sourcemeta::core::SchemaFrame frame{
1739+
sourcemeta::core::SchemaFrame::Mode::Instances};
1740+
frame.analyse(document, sourcemeta::core::schema_official_walker,
1741+
sourcemeta::core::schema_official_resolver);
1742+
1743+
EXPECT_EQ(frame.locations().size(), 4);
1744+
1745+
EXPECT_FRAME_STATIC_2020_12_RESOURCE(frame, "foo/bar/baz", "foo/bar/baz", "",
1746+
"foo/bar/baz", "", {""}, std::nullopt);
1747+
1748+
// JSON Pointers
1749+
1750+
EXPECT_FRAME_STATIC_2020_12_POINTER(frame, "foo/bar/baz#/$schema",
1751+
"foo/bar/baz", "/$schema", "foo/bar/baz",
1752+
"/$schema", {}, "");
1753+
EXPECT_FRAME_STATIC_2020_12_POINTER(frame, "foo/bar/baz#/$id", "foo/bar/baz",
1754+
"/$id", "foo/bar/baz", "/$id", {}, "");
1755+
EXPECT_FRAME_STATIC_2020_12_POINTER(frame, "foo/bar/baz#/$ref", "foo/bar/baz",
1756+
"/$ref", "foo/bar/baz", "/$ref", {}, "");
1757+
1758+
// References
1759+
1760+
EXPECT_EQ(frame.references().size(), 2);
1761+
1762+
EXPECT_STATIC_REFERENCE(
1763+
frame, "/$schema", "https://json-schema.org/draft/2020-12/schema",
1764+
"https://json-schema.org/draft/2020-12/schema", std::nullopt);
1765+
EXPECT_STATIC_REFERENCE(frame, "/$ref", "foo/bar/qux", "foo/bar/qux",
1766+
std::nullopt);
1767+
}
1768+
17311769
TEST(JSONSchema_frame_2020_12, idempotent_with_refs) {
17321770
const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
17331771
"$id": "https://www.sourcemeta.com/schema",

test/uri/uri_resolve_from_test.cc

+7
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ TEST(URI_try_resolve_from, base_relative_path_leading_slash) {
6363
EXPECT_EQ(relative.recompose(), "/foo#/bar");
6464
}
6565

66+
TEST(URI_try_resolve_from, relative_path_from_relative_path) {
67+
const sourcemeta::core::URI base{"foo/bar/baz"};
68+
sourcemeta::core::URI relative{"qux"};
69+
relative.try_resolve_from(base);
70+
EXPECT_EQ(relative.recompose(), "foo/bar/qux");
71+
}
72+
6673
// RFC 3986, inspired from
6774
// https://cr.openjdk.org/~dfuchs/writeups/updating-uri/A Section "Resolutuon"
6875

0 commit comments

Comments
 (0)