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"
13#include "../char_maps/module.hpp"
14#include "../utility/module.hpp"
15#include <string>
16#include <string_view>
17#include <optional>
18#include <vector>
19#include <unordered_map>
20#include <memory>
21#include <ostream>
22#include <mutex>
23#include <filesystem>
24
25hi_warning_push();
26// C26434: Function '' hides a non-virtual function ''.
27// False positive reported: https://developercommunity.visualstudio.com/t/C26434-false-positive-with-conversion-op/10262199
28hi_warning_ignore_msvc(26434);
29
30namespace hi { inline namespace v1 {
31
48class URL : public URI {
49public:
52 constexpr URL() noexcept = default;
53 constexpr URL(URL const&) noexcept = default;
54 constexpr URL(URL&&) noexcept = default;
55 constexpr URL& operator=(URL const&) noexcept = default;
56 constexpr URL& operator=(URL&&) noexcept = default;
57
60 constexpr explicit URL(URI const& other) noexcept : URI(other) {}
61
64 constexpr explicit URL(URI&& other) noexcept : URI(std::move(other)){};
65
72 constexpr explicit URL(std::string_view str) : URI(str) {}
73
80 constexpr explicit URL(std::string const& str) : URL(std::string_view{str}) {}
81
88 constexpr explicit URL(const char *str) : URL(std::string_view{str}) {}
89
97 explicit URL(std::filesystem::path const& path) : URI(make_file_url_string(path)) {}
98
104 [[nodiscard]] constexpr std::u8string filesystem_path_generic_u8string(bool validate_scheme = true) const
105 {
106 if (validate_scheme and not(not scheme() or scheme() == "file")) {
107 throw url_error("URL::generic_path() is only valid on a file: scheme URL");
108 }
109
110 auto r = std::string{};
111 hilet& p = path();
112 hilet first = p.begin();
113 hilet last = p.end();
114 auto it = first;
115
116 auto has_root_name = false;
117 if (authority()) {
118 // file://server/filename is valid.
119 auto server = to_string(*authority());
120 if (not server.empty() and server != "localhost") {
121 validate_file_server(server);
122 has_root_name = true;
123 r += '/';
124 r += '/';
125 r += server;
126 r += '/';
127 }
128 }
129
130 // If a server was found than the path must be absolute.
131 hi_assert(has_root_name == false or p.absolute());
132
133 if (p.double_absolute()) {
134 // file:////server/filename is valid.
135 if (has_root_name) {
136 // file://server//server/filename is invalid.
137 throw url_error("file URL has two server names.");
138 }
139
140 has_root_name = true;
141 r += '/';
142 r += '/';
143 it += 2;
144 validate_file_server(*it);
145 r += *it++;
146 r += '/';
147 }
148
149 // Find the drive letter.
150 auto empty_segment = false;
151 while (it != last) {
152 validate_file_segment(*it);
153 empty_segment = it->empty();
154
155 if (it == first and empty_segment) {
156 // Skip leading '/' in front of drive letter.
157 ++it;
158
159 } else if (auto i = it->find(':'); i != std::string::npos) {
160 // Found a drive letter.
161 if (i != 1) {
162 throw url_error("file URL contains a device name which is a security issue.");
163 }
164
165 if (has_root_name or p.absolute()) {
166 r += it->front();
167 // Use $ when the drive letter is on a server.
168 r += has_root_name ? '$' : ':';
169 // Add potentially a relative segment after the driver letter.
170 r += it->substr(2);
171
172 } else {
173 // Take the drive letter and optional relative directory directly on relative paths.
174 // C:dirname valid
175 // C:/dirname valid
176 // file:C:dirname valid
177 // file:C:/dirname valid
178 r += *it;
179 }
180
181 has_root_name = true;
182 r += '/';
183 ++it;
184 break;
185
186 } else {
187 // Found a normal directory or filename.
188 break;
189 }
190 }
191
192 // The rest are directories followed by a single (optionally empty) filename
193 for (; it != last; ++it) {
194 validate_file_segment(*it);
195 empty_segment = it->empty();
196 r += *it;
197 r += '/';
198 }
199
200 if (not empty_segment) {
201 // Remove trailing backslash if the last segment was a filename.
202 r.pop_back();
203 }
204
205 return to_u8string(r);
206 }
207
213 [[nodiscard]] std::filesystem::path filesystem_path() const
214 {
215 if (auto scheme_ = scheme()) {
216 if (scheme_ == "resource") {
217 // Always used std::u8string with std::filesystem::path.
218 hilet ref = std::filesystem::path{filesystem_path_generic_u8string(false)};
220 return *path;
221 } else {
222 throw url_error(std::format("Resource {} not found.", *this));
223 }
224
225 } else if (scheme_ == "file") {
227 } else {
228 throw url_error("URL can not be converted to a filesystem path.");
229 }
230 } else {
231 // relative path.
233 }
234 }
235
238 operator std::filesystem::path() const
239 {
240 return filesystem_path();
241 }
242
243 [[nodiscard]] constexpr friend URL operator/(URL const& base, URI const& ref) noexcept
244 {
245 return URL{up_cast<URI const&>(base) / ref};
246 }
247
248 [[nodiscard]] constexpr friend URL operator/(URL const& base, std::string_view ref) noexcept
249 {
250 return URL{up_cast<URI const&>(base) / ref};
251 }
252
253private:
254 constexpr void static validate_file_segment(std::string_view segment)
255 {
256 for (auto c : segment) {
257 if (c == '/' or c == '\\') {
258 throw url_error("Filename server name may not contain slash or back-slash.");
259 }
260 }
261 }
262
263 constexpr void static validate_file_server(std::string_view server)
264 {
265 for (auto c : server) {
266 if (c == '/' or c == '\\') {
267 throw url_error("Filename segments may not contain slash or back-slash.");
268 }
269 }
270 }
271
272 static std::string make_file_url_string(std::filesystem::path const& path)
273 {
274 auto r = std::u8string{};
275
276 hilet root_name = path.root_name().generic_u8string();
277 if (root_name.empty()) {
278 // No root-name.
279 if (not path.root_directory().empty()) {
280 // An absolute path should start with the file: scheme.
281 r += u8"file:" + path.root_directory().generic_u8string();
282 } else {
283 // A relative path should not be prefixed with a scheme.
284 ;
285 }
286
287 } else if (hilet i = root_name.find(':'); i != std::string::npos) {
288 if (i == 1) {
289 // Root name is a drive-letter, followed by potentially a relative path.
290 r += u8"file:///" + root_name + path.root_directory().generic_u8string();
291 } else {
292 throw url_error("Paths containing a device are not allowed to be converted to a URL.");
293 }
294 } else {
295 // Root name is a server.
296 r += u8"file://" + root_name + path.root_directory().generic_u8string();
297 if (not path.root_directory().empty()) {
298 throw url_error("Invalid path contains server name without a root directory.");
299 }
300 }
301
302 return to_string(r + path.relative_path().generic_u8string());
303 }
304};
305
306}} // namespace hi::v1
307
308template<>
309struct std::hash<hi::URL> {
310 [[nodiscard]] size_t operator()(hi::URL const& rhs) const noexcept
311 {
312 return std::hash<hi::URI>{}(rhs);
313 }
314};
315
316template<typename CharT>
317struct std::formatter<hi::URL, CharT> : std::formatter<hi::URI, CharT> {
318 auto format(hi::URL const& t, auto& fc)
319 {
320 return std::formatter<hi::URI, CharT>::format(t, fc);
321 }
322};
323
324hi_warning_pop();
functions to locate files and directories.
Defines the URI class.
#define hi_assert(expression,...)
Assert if expression is true.
Definition assert.hpp:199
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
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.
STL namespace.
DOXYGEN BUG.
Definition algorithm.hpp:13
geometry/margins.hpp
Definition cache.hpp:11
A Uniform Resource Identifier.
Definition URI.hpp:37
constexpr std::optional< authority_type > const & authority() const noexcept
Get the authority-component of the URI.
Definition URI.hpp:550
constexpr std::optional< std::string > const & scheme() const noexcept
Get the scheme-component of the URI.
Definition URI.hpp:527
Universal Resource Locator.
Definition URL.hpp:48
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:97
constexpr URL(URI &&other) noexcept
Convert a URI to an URL.
Definition URL.hpp:64
operator std::filesystem::path() const
Definition URL.hpp:238
constexpr URL(const char *str)
Construct a URI from a string.
Definition URL.hpp:88
constexpr URL(std::string const &str)
Construct a URI from a string.
Definition URL.hpp:80
std::filesystem::path filesystem_path() const
Create a filesystem path from a file URL.
Definition URL.hpp:213
constexpr std::u8string filesystem_path_generic_u8string(bool validate_scheme=true) const
Return a generic path.
Definition URL.hpp:104
constexpr URL(std::string_view str)
Construct a URI from a string.
Definition URL.hpp:72
Definition exception.hpp:214
T move(T... args)
T operator()(T... args)
T to_string(T... args)