HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
URL.hpp
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/char_maps.hpp"
14#include "../utility/utility.hpp"
15#include "../macros.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
26hi_export_module(hikogui.path.URL);
27
28hi_warning_push();
29// C26434: Function '' hides a non-virtual function ''.
30// False positive reported: https://developercommunity.visualstudio.com/t/C26434-false-positive-with-conversion-op/10262199
31hi_warning_ignore_msvc(26434);
32
33// C4702 unreachable code: False positive, but "not a bug" / "low priority".
34// https://developercommunity.visualstudio.com/t/warning-c4702-for-range-based-for-loop/859129
35// The ranged for-loop is translated into a normal for-loop. The iterator
36// part of the normal for-loop is never executed due to the immediate return
37// inside the for-loop.
38hi_warning_ignore_msvc(4702);
39
40hi_export namespace hi { inline namespace v1 {
41
58class URL : public URI {
59public:
62 constexpr URL() noexcept = default;
63 constexpr URL(URL const&) noexcept = default;
64 constexpr URL(URL&&) noexcept = default;
65 constexpr URL& operator=(URL const&) noexcept = default;
66 constexpr URL& operator=(URL&&) noexcept = default;
67
70 constexpr explicit URL(URI const& other) noexcept : URI(other) {}
71
74 constexpr explicit URL(URI&& other) noexcept : URI(std::move(other)){};
75
82 constexpr explicit URL(std::string_view str) : URI(str) {}
83
90 constexpr explicit URL(std::string const& str) : URL(std::string_view{str}) {}
91
98 constexpr explicit URL(const char *str) : URL(std::string_view{str}) {}
99
107 explicit URL(std::filesystem::path const& path) : URI(make_file_url_string(path)) {}
108
114 [[nodiscard]] constexpr std::u8string filesystem_path_generic_u8string(bool validate_scheme = true) const
115 {
116 if (validate_scheme and not(not scheme() or scheme() == "file")) {
117 throw url_error("URL::generic_path() is only valid on a file: scheme URL");
118 }
119
120 auto r = std::string{};
121 auto const& p = path();
122 auto const first = p.begin();
123 auto const last = p.end();
124 auto it = first;
125
126 auto has_root_name = false;
127 if (authority()) {
128 // file://server/filename is valid.
129 auto server = to_string(*authority());
130 if (not server.empty() and server != "localhost") {
131 validate_file_server(server);
132 has_root_name = true;
133 r += '/';
134 r += '/';
135 r += server;
136 r += '/';
137 }
138 }
139
140 // If a server was found than the path must be absolute.
141 hi_assert(has_root_name == false or p.absolute());
142
143 if (p.double_absolute()) {
144 // file:////server/filename is valid.
145 if (has_root_name) {
146 // file://server//server/filename is invalid.
147 throw url_error("file URL has two server names.");
148 }
149
150 has_root_name = true;
151 r += '/';
152 r += '/';
153 it += 2;
154 validate_file_server(*it);
155 r += *it++;
156 r += '/';
157 }
158
159 // Find the drive letter.
160 auto empty_segment = false;
161 while (it != last) {
162 validate_file_segment(*it);
163 empty_segment = it->empty();
164
165 if (it == first and empty_segment) {
166 // Skip leading '/' in front of drive letter.
167 ++it;
168
169 } else if (auto i = it->find(':'); i != std::string::npos) {
170 // Found a drive letter.
171 if (i != 1) {
172 throw url_error("file URL contains a device name which is a security issue.");
173 }
174
175 if (has_root_name or p.absolute()) {
176 r += it->front();
177 // Use $ when the drive letter is on a server.
178 r += has_root_name ? '$' : ':';
179 // Add potentially a relative segment after the driver letter.
180 r += it->substr(2);
181
182 } else {
183 // Take the drive letter and optional relative directory directly on relative paths.
184 // C:dirname valid
185 // C:/dirname valid
186 // file:C:dirname valid
187 // file:C:/dirname valid
188 r += *it;
189 }
190
191 has_root_name = true;
192 r += '/';
193 ++it;
194 break;
195
196 } else {
197 // Found a normal directory or filename.
198 break;
199 }
200 }
201
202 // The rest are directories followed by a single (optionally empty) filename
203 for (; it != last; ++it) {
204 validate_file_segment(*it);
205 empty_segment = it->empty();
206 r += *it;
207 r += '/';
208 }
209
210 if (not empty_segment) {
211 // Remove trailing backslash if the last segment was a filename.
212 r.pop_back();
213 }
214
215 return to_u8string(r);
216 }
217
223 [[nodiscard]] std::filesystem::path filesystem_path() const
224 {
225 if (auto scheme_ = scheme()) {
226 if (scheme_ == "resource") {
227 // Always used std::u8string with std::filesystem::path.
228 auto const ref = std::filesystem::path{filesystem_path_generic_u8string(false)};
229
230 for (auto const& path : find_path(resource_dirs(), ref)) {
231 // Return the first matching path.
232 return path;
233 }
234 throw url_error(std::format("Resource '{}' not found in search-path: '{}'", to_string(*this), to_string(resource_dirs())));
235
236 } else if (scheme_ == "file") {
238 } else {
239 throw url_error("URL can not be converted to a filesystem path.");
240 }
241 } else {
242 // relative path.
244 }
245 }
246
249 operator std::filesystem::path() const
250 {
251 return filesystem_path();
252 }
253
254 [[nodiscard]] constexpr friend URL operator/(URL const& base, URI const& ref) noexcept
255 {
256 return URL{up_cast<URI const&>(base) / ref};
257 }
258
259 [[nodiscard]] constexpr friend URL operator/(URL const& base, std::string_view ref) noexcept
260 {
261 return URL{up_cast<URI const&>(base) / ref};
262 }
263
264private:
265 constexpr void static validate_file_segment(std::string_view segment)
266 {
267 for (auto c : segment) {
268 if (c == '/' or c == '\\') {
269 throw url_error("Filename server name may not contain slash or back-slash.");
270 }
271 }
272 }
273
274 constexpr void static validate_file_server(std::string_view server)
275 {
276 for (auto c : server) {
277 if (c == '/' or c == '\\') {
278 throw url_error("Filename segments may not contain slash or back-slash.");
279 }
280 }
281 }
282
283 static std::string make_file_url_string(std::filesystem::path const& path)
284 {
285 auto r = std::u8string{};
286
287 auto const root_name = path.root_name().generic_u8string();
288 if (root_name.empty()) {
289 // No root-name.
290 if (not path.root_directory().empty()) {
291 // An absolute path should start with the file: scheme.
292 r += u8"file:" + path.root_directory().generic_u8string();
293 } else {
294 // A relative path should not be prefixed with a scheme.
295 ;
296 }
297
298 } else if (auto const i = root_name.find(':'); i != std::string::npos) {
299 if (i == 1) {
300 // Root name is a drive-letter, followed by potentially a relative path.
301 r += u8"file:///" + root_name + path.root_directory().generic_u8string();
302 } else {
303 throw url_error("Paths containing a device are not allowed to be converted to a URL.");
304 }
305 } else {
306 // Root name is a server.
307 r += u8"file://" + root_name + path.root_directory().generic_u8string();
308 if (not path.root_directory().empty()) {
309 throw url_error("Invalid path contains server name without a root directory.");
310 }
311 }
312
313 return to_string(r + path.relative_path().generic_u8string());
314 }
315};
316
317}} // namespace hi::v1
318
319template<>
320struct std::hash<hi::URL> {
321 [[nodiscard]] size_t operator()(hi::URL const& rhs) const noexcept
322 {
323 return std::hash<hi::URI>{}(rhs);
324 }
325};
326
327// XXX #617 MSVC bug does not handle partial specialization in modules.
328hi_export template<>
329struct std::formatter<hi::URL, char> : std::formatter<hi::URI, char> {
330 auto format(hi::URL const& t, auto& fc) const
331 {
332 return std::formatter<hi::URI, char>::format(t, fc);
333 }
334};
335
336hi_warning_pop();
functions to locate files and directories.
constexpr std::u8string to_u8string(std::u32string_view rhs) noexcept
Conversion from UTF-32 to UTF-8.
Definition to_string.hpp:116
@ other
The gui_event does not have associated data.
generator< std::filesystem::path > find_path(Locations &&locations, std::filesystem::path const &ref) noexcept
Find a path.
Definition path_location_intf.hpp:50
generator< std::filesystem::path > resource_dirs() noexcept
The directories to search for resource files.
Definition path_location_win32_impl.hpp:63
STL namespace.
The HikoGUI namespace.
Definition array_generic.hpp:20
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
A Uniform Resource Identifier.
Definition URI.hpp:45
constexpr std::optional< authority_type > const & authority() const noexcept
Get the authority-component of the URI.
Definition URI.hpp:558
constexpr std::optional< std::string > const & scheme() const noexcept
Get the scheme-component of the URI.
Definition URI.hpp:535
Universal Resource Locator.
Definition URL.hpp:58
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:107
constexpr URL(URI &&other) noexcept
Convert a URI to an URL.
Definition URL.hpp:74
operator std::filesystem::path() const
Definition URL.hpp:249
constexpr URL(const char *str)
Construct a URI from a string.
Definition URL.hpp:98
constexpr URL(std::string const &str)
Construct a URI from a string.
Definition URL.hpp:90
std::filesystem::path filesystem_path() const
Create a filesystem path from a file URL.
Definition URL.hpp:223
constexpr std::u8string filesystem_path_generic_u8string(bool validate_scheme=true) const
Return a generic path.
Definition URL.hpp:114
constexpr URL(std::string_view str)
Construct a URI from a string.
Definition URL.hpp:82
Definition exception_intf.hpp:204
T move(T... args)
T operator()(T... args)
T to_string(T... args)