mirror of
https://github.com/boostorg/url.git
synced 2026-07-22 13:53:35 +00:00
14fb347896
All view types in Boost.URL have iterators that store raw pointers to external data rather than references to the view object itself. This means iterators remain valid even after the view is destroyed, satisfying the borrowed_range requirements. Specializations added for: - decode_view, pct_string_view - params_view, params_ref, params_encoded_view, params_encoded_ref - segments_view, segments_ref, segments_encoded_view, segments_encoded_ref fix #927
424 lines
12 KiB
C++
424 lines
12 KiB
C++
//
|
|
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
//
|
|
// Official repository: https://github.com/boostorg/url
|
|
//
|
|
|
|
// Test that header file is self-contained.
|
|
#include <boost/url/segments_view.hpp>
|
|
|
|
#include <boost/url/parse.hpp>
|
|
#include <boost/url/parse_path.hpp>
|
|
#include <boost/url/url.hpp>
|
|
#include <boost/core/detail/static_assert.hpp>
|
|
#include <boost/core/ignore_unused.hpp>
|
|
|
|
#include "test_suite.hpp"
|
|
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
#ifdef assert
|
|
#undef assert
|
|
#endif
|
|
#define assert BOOST_TEST
|
|
|
|
namespace boost {
|
|
namespace urls {
|
|
|
|
BOOST_CORE_STATIC_ASSERT(
|
|
std::is_default_constructible<
|
|
segments_view>::value);
|
|
|
|
BOOST_CORE_STATIC_ASSERT(
|
|
std::is_copy_constructible<
|
|
segments_view>::value);
|
|
|
|
BOOST_CORE_STATIC_ASSERT(
|
|
std::is_copy_assignable<
|
|
segments_view>::value);
|
|
|
|
BOOST_CORE_STATIC_ASSERT(
|
|
std::is_default_constructible<
|
|
segments_view::iterator>::value);
|
|
|
|
struct segments_view_test
|
|
{
|
|
void
|
|
testSpecialMembers()
|
|
{
|
|
// segments_view()
|
|
{
|
|
segments_view ps;
|
|
BOOST_TEST(ps.empty());
|
|
BOOST_TEST(! ps.is_absolute());
|
|
BOOST_TEST_EQ(ps.buffer(), "");
|
|
BOOST_TEST_EQ(ps.size(), 0);
|
|
}
|
|
|
|
// segments_view(segments_view)
|
|
{
|
|
segments_view ps0 =
|
|
parse_path("/path/to/file.txt").value();
|
|
segments_view ps1(ps0);
|
|
BOOST_TEST_EQ(
|
|
ps0.buffer().data(),
|
|
ps1.buffer().data());
|
|
}
|
|
|
|
// segments_view(core::string_view)
|
|
{
|
|
try
|
|
{
|
|
core::string_view s = "/path/to/file.txt";
|
|
segments_view ps(s);
|
|
BOOST_TEST_PASS();
|
|
BOOST_TEST_EQ(
|
|
ps.buffer().data(), s.data());
|
|
BOOST_TEST_EQ(ps.buffer(), s);
|
|
}
|
|
catch(std::exception const&)
|
|
{
|
|
BOOST_TEST_FAIL();
|
|
}
|
|
|
|
// reserved character
|
|
BOOST_TEST_THROWS(segments_view("?"), system::system_error);
|
|
|
|
// invalid percent-escape
|
|
BOOST_TEST_THROWS(segments_view("%"), system::system_error);
|
|
BOOST_TEST_THROWS(segments_view("%F"), system::system_error);
|
|
BOOST_TEST_THROWS(segments_view("%FX"), system::system_error);
|
|
BOOST_TEST_THROWS(segments_view("%%"), system::system_error);
|
|
BOOST_TEST_THROWS(segments_view("FA%"), system::system_error);
|
|
}
|
|
|
|
// operator=(segments_view)
|
|
{
|
|
segments_view ps0("/path/to/file.txt");
|
|
segments_view ps1("/index.htm");
|
|
ps0 = ps1;
|
|
BOOST_TEST_EQ(
|
|
ps0.buffer().data(),
|
|
ps1.buffer().data());
|
|
}
|
|
|
|
// ostream
|
|
{
|
|
segments_view ps = parse_path(
|
|
"/path/to/file.txt").value();
|
|
std::stringstream ss;
|
|
ss << ps;
|
|
BOOST_TEST_EQ(ss.str(),
|
|
"/path/to/file.txt");
|
|
}
|
|
}
|
|
|
|
void
|
|
testRangeCtor()
|
|
{
|
|
// full slice equals original (absolute path)
|
|
{
|
|
segments_view ps = parse_path("/a/b/c").value();
|
|
segments_view sub(ps.begin(), ps.end());
|
|
|
|
BOOST_TEST_EQ(sub.size(), 3u);
|
|
BOOST_TEST(sub.is_absolute());
|
|
BOOST_TEST_EQ(sub.buffer(), "/a/b/c");
|
|
// alias same storage
|
|
BOOST_TEST_EQ(sub.buffer().data(), ps.buffer().data());
|
|
}
|
|
|
|
// full slice equals original (relative path)
|
|
{
|
|
segments_view ps = parse_path("a/b/c").value();
|
|
segments_view sub(ps.begin(), ps.end());
|
|
|
|
BOOST_TEST_EQ(sub.size(), 3u);
|
|
BOOST_TEST(!sub.is_absolute());
|
|
BOOST_TEST_EQ(sub.buffer(), "a/b/c");
|
|
// alias same storage
|
|
BOOST_TEST_EQ(sub.buffer().data(), ps.buffer().data());
|
|
}
|
|
|
|
// drop first segment: start at index 1 (retain separator)
|
|
{
|
|
segments_view ps = parse_path("/a/b/c").value();
|
|
auto first = std::next(ps.begin());
|
|
auto last = ps.end();
|
|
segments_view sub(first, last);
|
|
|
|
BOOST_TEST_EQ(sub.size(), 2u);
|
|
BOOST_TEST(sub.is_absolute());
|
|
BOOST_TEST_EQ(sub.buffer(), "/b/c");
|
|
|
|
auto it = sub.begin();
|
|
BOOST_TEST_EQ(*it++, "b");
|
|
BOOST_TEST_EQ(*it++, "c");
|
|
BOOST_TEST(it == sub.end());
|
|
}
|
|
|
|
// take prefix without the last segment:
|
|
// [begin, prev(end)) -> "/a/b"
|
|
{
|
|
segments_view ps = parse_path("/a/b/c").value();
|
|
auto first = ps.begin();
|
|
auto last = std::prev(ps.end());
|
|
segments_view sub(first, last);
|
|
|
|
BOOST_TEST_EQ(sub.size(), 2u);
|
|
BOOST_TEST(sub.is_absolute());
|
|
BOOST_TEST_EQ(sub.buffer(), "/a/b");
|
|
|
|
auto it = sub.begin();
|
|
BOOST_TEST_EQ(*it++, "a");
|
|
BOOST_TEST_EQ(*it++, "b");
|
|
BOOST_TEST(it == sub.end());
|
|
}
|
|
|
|
// single segment in the middle:
|
|
// ["b", past "b") -> "/b"
|
|
{
|
|
segments_view ps = parse_path("/a/b/c").value();
|
|
auto b = std::next(ps.begin());
|
|
auto e = std::next(b);
|
|
segments_view sub(b, e);
|
|
|
|
BOOST_TEST_EQ(sub.size(), 1u);
|
|
BOOST_TEST(sub.is_absolute());
|
|
BOOST_TEST_EQ(sub.buffer(), "/b");
|
|
BOOST_TEST_EQ(*sub.begin(), "b");
|
|
}
|
|
|
|
// relative path:
|
|
// subranges not starting at begin become absolute
|
|
{
|
|
segments_view ps = parse_path("a/b/c").value();
|
|
|
|
// full slice
|
|
segments_view sub1(ps.begin(), ps.end());
|
|
BOOST_TEST_EQ(sub1.size(), 3u);
|
|
BOOST_TEST(!sub1.is_absolute());
|
|
BOOST_TEST_EQ(sub1.buffer(), "a/b/c");
|
|
|
|
// prefix [begin, prev(end)) -> "a/b"
|
|
segments_view sub2(ps.begin(), std::prev(ps.end()));
|
|
BOOST_TEST_EQ(sub2.size(), 2u);
|
|
BOOST_TEST(!sub2.is_absolute());
|
|
BOOST_TEST_EQ(sub2.buffer(), "a/b");
|
|
|
|
// middle one ["b", past "b") -> "/b"
|
|
auto b = std::next(ps.begin());
|
|
segments_view sub3(b, std::next(b));
|
|
BOOST_TEST_EQ(sub3.size(), 1u);
|
|
BOOST_TEST(sub3.is_absolute());
|
|
BOOST_TEST_EQ(sub3.buffer(), "/b");
|
|
|
|
// suffix [next(begin), end) -> "/b/c"
|
|
segments_view sub4(std::next(ps.begin()), ps.end());
|
|
BOOST_TEST_EQ(sub4.size(), 2u);
|
|
BOOST_TEST(sub4.is_absolute());
|
|
BOOST_TEST_EQ(sub4.buffer(), "/b/c");
|
|
|
|
// concatenating adjoining subviews recreates original text
|
|
segments_view first_seg(ps.begin(), std::next(ps.begin()));
|
|
std::string reconstructed(
|
|
first_seg.buffer().data(),
|
|
first_seg.buffer().size());
|
|
reconstructed.append(
|
|
sub4.buffer().data(),
|
|
sub4.buffer().size());
|
|
BOOST_TEST_EQ(reconstructed, ps.buffer());
|
|
BOOST_TEST_EQ(
|
|
first_seg.buffer().decoded_size() +
|
|
sub4.buffer().decoded_size(),
|
|
ps.buffer().decoded_size());
|
|
}
|
|
|
|
// empty subrange [it, it):
|
|
// empty buffer, not absolute
|
|
{
|
|
segments_view ps = parse_path("/a/b").value();
|
|
auto it = ps.begin();
|
|
segments_view sub(it, it);
|
|
|
|
BOOST_TEST_EQ(sub.size(), 0u);
|
|
BOOST_TEST(!sub.is_absolute());
|
|
BOOST_TEST_EQ(sub.buffer(), "");
|
|
BOOST_TEST(sub.begin() == sub.end());
|
|
}
|
|
|
|
// empty subrange from relative source
|
|
{
|
|
segments_view ps = parse_path("a/b").value();
|
|
auto it = ps.begin();
|
|
segments_view sub(it, it);
|
|
|
|
BOOST_TEST_EQ(sub.size(), 0u);
|
|
BOOST_TEST(!sub.is_absolute());
|
|
BOOST_TEST_EQ(sub.buffer(), "");
|
|
BOOST_TEST(sub.begin() == sub.end());
|
|
}
|
|
|
|
// percent-encoding:
|
|
// slice of first encoded segment only, absolute start
|
|
{
|
|
segments_view ps = parse_path("/a%2Fb/c").value();
|
|
auto first = ps.begin();
|
|
auto last = std::next(ps.begin());
|
|
segments_view sub(first, last);
|
|
|
|
// encoded in buffer
|
|
BOOST_TEST(sub.is_absolute());
|
|
BOOST_TEST_EQ(sub.size(), 1u);
|
|
BOOST_TEST_EQ(sub.buffer(), "/a%2Fb");
|
|
|
|
// decoded on deref
|
|
auto it = sub.begin();
|
|
BOOST_TEST_EQ(*it++, "a/b");
|
|
BOOST_TEST(it == sub.end());
|
|
}
|
|
|
|
// aliasing: url_view -> segments_view -> subrange
|
|
{
|
|
url_view u("/x/y");
|
|
segments_view ps = u.segments();
|
|
segments_view sub(ps.begin(), ps.end());
|
|
|
|
BOOST_TEST_EQ(sub.buffer().data(), u.buffer().data());
|
|
BOOST_TEST_EQ(sub.buffer(), "/x/y");
|
|
BOOST_TEST(sub.is_absolute());
|
|
BOOST_TEST_EQ(sub.size(), 2u);
|
|
}
|
|
|
|
// empty prefix of absolute path:
|
|
// [begin, begin) -> empty
|
|
{
|
|
segments_view ps = parse_path("/a/b/c").value();
|
|
segments_view sub(ps.begin(), ps.begin());
|
|
BOOST_TEST_EQ(sub.size(), 0u);
|
|
BOOST_TEST_EQ(sub.buffer(), "");
|
|
BOOST_TEST(!sub.is_absolute());
|
|
}
|
|
|
|
// empty subrange in the middle (absolute):
|
|
// ["b","b") -> "", not absolute
|
|
{
|
|
segments_view ps = parse_path("/a/b/c").value();
|
|
auto b = std::next(ps.begin());
|
|
segments_view sub(b, b);
|
|
|
|
BOOST_TEST_EQ(sub.size(), 0u);
|
|
BOOST_TEST_EQ(sub.buffer(), "");
|
|
BOOST_TEST(!sub.is_absolute());
|
|
BOOST_TEST(sub.begin() == sub.end());
|
|
}
|
|
|
|
// empty subrange in the middle (relative):
|
|
// ["b","b") -> "", not absolute
|
|
{
|
|
segments_view ps = parse_path("a/b/c").value();
|
|
auto b = std::next(ps.begin()); // "b"
|
|
segments_view sub(b, b);
|
|
|
|
BOOST_TEST_EQ(sub.size(), 0u);
|
|
BOOST_TEST_EQ(sub.buffer(), "");
|
|
BOOST_TEST(!sub.is_absolute());
|
|
BOOST_TEST(sub.begin() == sub.end());
|
|
}
|
|
|
|
// empty subrange at end:
|
|
// [end,end) -> "", not absolute
|
|
{
|
|
segments_view ps = parse_path("/a/b/c").value();
|
|
auto e = ps.end();
|
|
segments_view sub(e, e);
|
|
|
|
BOOST_TEST_EQ(sub.size(), 0u);
|
|
BOOST_TEST_EQ(sub.buffer(), "");
|
|
BOOST_TEST(!sub.is_absolute());
|
|
}
|
|
|
|
// single-segment absolute path full slice: "/a"
|
|
{
|
|
segments_view ps = parse_path("/a").value();
|
|
segments_view sub(ps.begin(), ps.end());
|
|
|
|
BOOST_TEST_EQ(sub.size(), 1u);
|
|
BOOST_TEST(sub.is_absolute());
|
|
BOOST_TEST_EQ(sub.buffer(), "/a");
|
|
|
|
auto it = sub.begin();
|
|
BOOST_TEST_EQ(*it++, "a");
|
|
BOOST_TEST(it == sub.end());
|
|
}
|
|
|
|
// middle slice using last.pos path:
|
|
// ["b", prev(end)) on "/a/b/c" -> "/b"
|
|
{
|
|
segments_view ps = parse_path("/a/b/c").value();
|
|
auto b = std::next(ps.begin());
|
|
auto last = std::prev(ps.end());
|
|
segments_view sub(b, last);
|
|
|
|
BOOST_TEST_EQ(sub.size(), 1u);
|
|
BOOST_TEST(sub.is_absolute());
|
|
BOOST_TEST_EQ(sub.buffer(), "/b");
|
|
BOOST_TEST_EQ(*sub.begin(), "b");
|
|
}
|
|
}
|
|
|
|
void
|
|
testJavadocs()
|
|
{
|
|
// {class}
|
|
{
|
|
url_view u( "/path/to/file.txt" );
|
|
|
|
segments_view ps = u.segments();
|
|
|
|
assert( ps.buffer().data() == u.buffer().data() );
|
|
|
|
ignore_unused(ps);
|
|
}
|
|
}
|
|
|
|
void
|
|
testBorrowedRange()
|
|
{
|
|
#ifdef BOOST_URL_HAS_CONCEPTS
|
|
// segments_view is a borrowed range
|
|
BOOST_CORE_STATIC_ASSERT(
|
|
std::ranges::borrowed_range<segments_view>);
|
|
|
|
// iterators remain valid after the view is destroyed
|
|
segments_view::iterator it;
|
|
{
|
|
segments_view ps("/path/to/file.txt");
|
|
it = ps.begin();
|
|
}
|
|
// iterator is still valid (points to external buffer)
|
|
BOOST_TEST_EQ(*it, "path");
|
|
#endif
|
|
}
|
|
|
|
void
|
|
run()
|
|
{
|
|
testSpecialMembers();
|
|
testRangeCtor();
|
|
testJavadocs();
|
|
testBorrowedRange();
|
|
}
|
|
};
|
|
|
|
TEST_SUITE(
|
|
segments_view_test,
|
|
"boost.url.segments_view");
|
|
|
|
} // urls
|
|
} // boost
|