HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
URL.hpp
Go to the documentation of this file.
1// Copyright Take Vos 2019-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
9#pragma once
10
11#include "URI.hpp"
12#include "path_location.hpp"
14#include "../utility.hpp"
15#include "../assert.hpp"
16#include <string>
17#include <string_view>
18#include <optional>
19#include <vector>
20#include <unordered_map>
21#include <memory>
22#include <ostream>
23#include <mutex>
24#include <filesystem>
25
26namespace hi { inline namespace v1 {
27
44class URL : public URI {
45public:
48 constexpr URL() noexcept = default;
49 constexpr URL(URL const&) noexcept = default;
50 constexpr URL(URL&&) noexcept = default;
51 constexpr URL& operator=(URL const&) noexcept = default;
52 constexpr URL& operator=(URL&&) noexcept = default;
53
56 constexpr explicit URL(URI const& other) noexcept : URI(other) {}
57
60 constexpr explicit URL(URI&& other) noexcept : URI(std::move(other)){};
61
69 explicit URL(std::filesystem::path const& path) : URI(make_file_url_string(path)) {}
70
76 [[nodiscard]] constexpr std::u8string filesystem_path_generic_u8string(bool validate_scheme = true) const
77 {
78 if (validate_scheme and not(not scheme() or scheme() == "file")) {
79 throw url_error("URL::generic_path() is only valid on a file: scheme URL");
80 }
81
82 auto r = std::string{};
83 hilet& p = path();
84 hilet first = p.begin();
85 hilet last = p.end();
86 auto it = first;
87
88 auto has_root_name = false;
89 if (authority()) {
90 // file://server/filename is valid.
91 auto server = to_string(*authority());
92 if (not server.empty() and server != "localhost") {
93 validate_file_server(server);
94 has_root_name = true;
95 r += '/';
96 r += '/';
97 r += server;
98 r += '/';
99 }
100 }
101
102 // If a server was found than the path must be absolute.
103 hi_assert(has_root_name == false or p.absolute());
104
105 if (p.double_absolute()) {
106 // file:////server/filename is valid.
107 if (has_root_name) {
108 // file://server//server/filename is invalid.
109 throw url_error("file URL has two server names.");
110 }
111
112 has_root_name = true;
113 r += '/';
114 r += '/';
115 it += 2;
116 validate_file_server(*it);
117 r += *it++;
118 r += '/';
119 }
120
121 // Find the drive letter.
122 auto empty_segment = false;
123 while (it != last) {
124 validate_file_segment(*it);
125 empty_segment = it->empty();
126
127 if (it == first and empty_segment) {
128 // Skip leading '/' in front of drive letter.
129 ++it;
130
131 } else if (auto i = it->find(':'); i != std::string::npos) {
132 // Found a drive letter.
133 if (i != 1) {
134 throw url_error("file URL contains a device name which is a security issue.");
135 }
136
137 if (has_root_name or p.absolute()) {
138 r += it->front();
139 // Use $ when the drive letter is on a server.
140 r += has_root_name ? '$' : ':';
141 // Add potentially a relative segment after the driver letter.
142 r += it->substr(2);
143
144 } else {
145 // Take the drive letter and optional relative directory directly on relative paths.
146 // C:dirname valid
147 // C:/dirname valid
148 // file:C:dirname valid
149 // file:C:/dirname valid
150 r += *it;
151 }
152
153 has_root_name = true;
154 r += '/';
155 ++it;
156 break;
157
158 } else {
159 // Found a normal directory or filename.
160 break;
161 }
162 }
163
164 // The rest are directories followed by a single (optionally empty) filename
165 for (; it != last; ++it) {
166 validate_file_segment(*it);
167 empty_segment = it->empty();
168 r += *it;
169 r += '/';
170 }
171
172 if (not empty_segment) {
173 // Remove trailing backslash if the last segment was a filename.
174 r.pop_back();
175 }
176
177 return to_u8string(r);
178 }
179
185 [[nodiscard]] std::filesystem::path filesystem_path() const
186 {
187 if (auto scheme_ = scheme()) {
188 if (scheme_ == "resource") {
189 // Always used std::u8string with std::filesystem::path.
190 hilet ref = std::filesystem::path{filesystem_path_generic_u8string(false)};
192 return *path;
193 } else {
194 throw url_error(std::format("Resource {} not found.", *this));
195 }
196
197 } else if (scheme_ == "file") {
199 } else {
200 throw url_error("URL can not be converted to a filesystem path.");
201 }
202 } else {
203 // relative path.
205 }
206 }
207
210 operator std::filesystem::path() const
211 {
212 return filesystem_path();
213 }
214
215 [[nodiscard]] constexpr friend URL operator/(URL const& base, URI const& ref) noexcept
216 {
217 return URL{up_cast<URI const&>(base) / ref};
218 }
219
220 [[nodiscard]] constexpr friend URL operator/(URL const& base, std::string_view ref) noexcept
221 {
222 return URL{up_cast<URI const&>(base) / ref};
223 }
224
225private:
226 constexpr void static validate_file_segment(std::string_view segment)
227 {
228 for (auto c : segment) {
229 if (c == '/' or c == '\\') {
230 throw url_error("Filename server name may not contain slash or back-slash.");
231 }
232 }
233 }
234
235 constexpr void static validate_file_server(std::string_view server)
236 {
237 for (auto c : server) {
238 if (c == '/' or c == '\\') {
239 throw url_error("Filename segments may not contain slash or back-slash.");
240 }
241 }
242 }
243
244 static std::string make_file_url_string(std::filesystem::path const& path)
245 {
246 auto r = std::u8string{};
247
248 hilet root_name = path.root_name().generic_u8string();
249 if (root_name.empty()) {
250 // No root-name.
251 if (not path.root_directory().empty()) {
252 // An absolute path should start with the file: scheme.
253 r += u8"file:" + path.root_directory().generic_u8string();
254 } else {
255 // A relative path should not be prefixed with a scheme.
256 ;
257 }
258
259 } else if (hilet i = root_name.find(':'); i != std::string::npos) {
260 if (i == 1) {
261 // Root name is a drive-letter, followed by potentially a relative path.
262 r += u8"file:///" + root_name + path.root_directory().generic_u8string();
263 } else {
264 throw url_error("Paths containing a device are not allowed to be converted to a URL.");
265 }
266 } else {
267 // Root name is a server.
268 r += u8"file://" + root_name + path.root_directory().generic_u8string();
269 if (not path.root_directory().empty()) {
270 throw url_error("Invalid path contains server name without a root directory.");
271 }
272 }
273
274 return to_string(r + path.relative_path().generic_u8string());
275 }
276};
277
278}} // namespace hi::v1
279
280template<>
281struct std::hash<hi::URL> {
282 [[nodiscard]] size_t operator()(hi::URL const& rhs) const noexcept
283 {
284 return std::hash<hi::URI>{}(rhs);
285 }
286};
287
288template<typename CharT>
289struct std::formatter<hi::URL, CharT> : std::formatter<hi::URI, CharT> {
290 auto format(hi::URL const& t, auto& fc)
291 {
292 return std::formatter<hi::URI, CharT>::format(t, fc);
293 }
294};
Utilities to assert and bound check.
#define hi_assert(expression,...)
Assert if expression is true.
Definition assert.hpp:87
functions to locate files and directories.
Defines the URI class.
Utilities used by the HikoGUI library itself.
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
String conversion functions.
constexpr std::u8string to_u8string(std::u32string_view rhs) noexcept
Conversion from UTF-32 to UTF-8.
Definition to_string.hpp:111
std::optional< std::filesystem::path > find_path(path_location location, std::filesystem::path const &ref) noexcept
Find a path.
Definition path_location.hpp:83
@ resource_dirs
The location of application resources.
@ other
The gui_event does not have associated data.
DOXYGEN BUG.
Definition algorithm.hpp:15
geometry/margins.hpp
Definition assert.hpp:18
Definition exception.hpp:111
A Uniform Resource Identifier.
Definition URI.hpp:41
constexpr std::optional< authority_type > const & authority() const noexcept
Get the authority-component of the URI.
Definition URI.hpp:541
constexpr std::optional< std::string > const & scheme() const noexcept
Get the scheme-component of the URI.
Definition URI.hpp:518
Universal Resource Locator.
Definition URL.hpp:44
constexpr URL() noexcept=default
Create an empty URL.
URL(std::filesystem::path const &path)
Convert a filesystem-path to a file-scheme URL.
Definition URL.hpp:69
constexpr URL(URI &&other) noexcept
Convert a URI to an URL.
Definition URL.hpp:60
operator std::filesystem::path() const
Definition URL.hpp:210
std::filesystem::path filesystem_path() const
Create a filesystem path from a file URL.
Definition URL.hpp:185
constexpr std::u8string filesystem_path_generic_u8string(bool validate_scheme=true) const
Return a generic path.
Definition URL.hpp:76
T move(T... args)
T operator()(T... args)
T to_string(T... args)