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
33hi_export namespace hi { inline namespace v1 {
34
51class URL : public URI {
52public:
55 constexpr URL() noexcept = default;
60
64
67 constexpr explicit URL(URI&& other) noexcept : URI(std::move(other)){};
68
75 constexpr explicit URL(std::string_view str) : URI(str) {}
76
83 constexpr explicit URL(std::string const& str) : URL(std::string_view{str}) {}
84
91 constexpr explicit URL(const char *str) : URL(std::string_view{str}) {}
92
100 explicit URL(std::filesystem::path const& path) : URI(make_file_url_string(path)) {}
101
107 [[nodiscard]] constexpr std::u8string filesystem_path_generic_u8string(bool validate_scheme = true) const
108 {
109 if (validate_scheme and not(not scheme() or scheme() == "file")) {
110 throw url_error("URL::generic_path() is only valid on a file: scheme URL");
111 }
112
113 auto r = std::string{};
114 auto const& p = path();
115 auto const first = p.begin();
116 auto const last = p.end();
117 auto it = first;
118
119 auto has_root_name = false;
120 if (authority()) {
121 // file://server/filename is valid.
122 auto server = to_string(*authority());
123 if (not server.empty() and server != "localhost") {
124 validate_file_server(server);
125 has_root_name = true;
126 r += '/';
127 r += '/';
128 r += server;
129 r += '/';
130 }
131 }
132
133 // If a server was found than the path must be absolute.
134 hi_assert(has_root_name == false or p.absolute());
135
136 if (p.double_absolute()) {
137 // file:////server/filename is valid.
138 if (has_root_name) {
139 // file://server//server/filename is invalid.
140 throw url_error("file URL has two server names.");
141 }
142
143 has_root_name = true;
144 r += '/';
145 r += '/';
146 it += 2;
147 validate_file_server(*it);
148 r += *it++;
149 r += '/';
150 }
151
152 // Find the drive letter.
153 auto empty_segment = false;
154 while (it != last) {
155 validate_file_segment(*it);
156 empty_segment = it->empty();
157
158 if (it == first and empty_segment) {
159 // Skip leading '/' in front of drive letter.
160 ++it;
161
162 } else if (auto i = it->find(':'); i != std::string::npos) {
163 // Found a drive letter.
164 if (i != 1) {
165 throw url_error("file URL contains a device name which is a security issue.");
166 }
167
168 if (has_root_name or p.absolute()) {
169 r += it->front();
170 // Use $ when the drive letter is on a server.
171 r += has_root_name ? '$' : ':';
172 // Add potentially a relative segment after the driver letter.
173 r += it->substr(2);
174
175 } else {
176 // Take the drive letter and optional relative directory directly on relative paths.
177 // C:dirname valid
178 // C:/dirname valid
179 // file:C:dirname valid
180 // file:C:/dirname valid
181 r += *it;
182 }
183
184 has_root_name = true;
185 r += '/';
186 ++it;
187 break;
188
189 } else {
190 // Found a normal directory or filename.
191 break;
192 }
193 }
194
195 // The rest are directories followed by a single (optionally empty) filename
196 for (; it != last; ++it) {
197 validate_file_segment(*it);
198 empty_segment = it->empty();
199 r += *it;
200 r += '/';
201 }
202
203 if (not empty_segment) {
204 // Remove trailing backslash if the last segment was a filename.
205 r.pop_back();
206 }
207
208 return to_u8string(r);
209 }
210
216 [[nodiscard]] std::filesystem::path filesystem_path() const
217 {
218 if (auto scheme_ = scheme()) {
219 if (scheme_ == "resource") {
220 // Always used std::u8string with std::filesystem::path.
221 auto const ref = std::filesystem::path{filesystem_path_generic_u8string(false)};
222 if (auto path = find_path(resource_dirs(), ref)) {
223 return *path;
224 } else {
225 auto search_path = std::string{};
226 for (auto p: resource_dirs()) {
227 if (not search_path.empty()) {
228 search_path += ";";
229 }
230 search_path += p.string();
231 }
232 throw url_error(std::format("Resource '{}' not found in search-path: '{}'", to_string(*this), search_path));
233 }
234
235 } else if (scheme_ == "file") {
237 } else {
238 throw url_error("URL can not be converted to a filesystem path.");
239 }
240 } else {
241 // relative path.
243 }
244 }
245
248 operator std::filesystem::path() const
249 {
250 return filesystem_path();
251 }
252
253 [[nodiscard]] constexpr friend URL operator/(URL const& base, URI const& ref) noexcept
254 {
255 return URL{up_cast<URI const&>(base) / ref};
256 }
257
258 [[nodiscard]] constexpr friend URL operator/(URL const& base, std::string_view ref) noexcept
259 {
260 return URL{up_cast<URI const&>(base) / ref};
261 }
262
263private:
264 constexpr void static validate_file_segment(std::string_view segment)
265 {
266 for (auto c : segment) {
267 if (c == '/' or c == '\\') {
268 throw url_error("Filename server name may not contain slash or back-slash.");
269 }
270 }
271 }
272
273 constexpr void static validate_file_server(std::string_view server)
274 {
275 for (auto c : server) {
276 if (c == '/' or c == '\\') {
277 throw url_error("Filename segments may not contain slash or back-slash.");
278 }
279 }
280 }
281
282 static std::string make_file_url_string(std::filesystem::path const& path)
283 {
284 auto r = std::u8string{};
285
286 auto const root_name = path.root_name().generic_u8string();
287 if (root_name.empty()) {
288 // No root-name.
289 if (not path.root_directory().empty()) {
290 // An absolute path should start with the file: scheme.
291 r += u8"file:" + path.root_directory().generic_u8string();
292 } else {
293 // A relative path should not be prefixed with a scheme.
294 ;
295 }
296
297 } else if (auto const i = root_name.find(':'); i != std::string::npos) {
298 if (i == 1) {
299 // Root name is a drive-letter, followed by potentially a relative path.
300 r += u8"file:///" + root_name + path.root_directory().generic_u8string();
301 } else {
302 throw url_error("Paths containing a device are not allowed to be converted to a URL.");
303 }
304 } else {
305 // Root name is a server.
306 r += u8"file://" + root_name + path.root_directory().generic_u8string();
307 if (not path.root_directory().empty()) {
308 throw url_error("Invalid path contains server name without a root directory.");
309 }
310 }
311
312 return to_string(r + path.relative_path().generic_u8string());
313 }
314};
315
316}} // namespace hi::v1
317
318template<>
319struct std::hash<hi::URL> {
320 [[nodiscard]] size_t operator()(hi::URL const& rhs) const noexcept
321 {
322 return std::hash<hi::URI>{}(rhs);
323 }
324};
325
326// XXX #617 MSVC bug does not handle partial specialization in modules.
327hi_export template<>
328struct std::formatter<hi::URL, char> : std::formatter<hi::URI, char> {
329 auto format(hi::URL const& t, auto& fc) const
330 {
331 return std::formatter<hi::URI, char>::format(t, fc);
332 }
333};
334
335hi_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.
hi_inline generator< std::filesystem::path > resource_dirs() noexcept
The directories to search for resource files.
hi_inline std::optional< std::filesystem::path > find_path(Locations &&locations, std::filesystem::path const &ref) noexcept
Find a path.
Definition path_location_intf.hpp:40
STL namespace.
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
The HikoGUI namespace.
Definition recursive_iterator.hpp:15
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:378
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:51
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:100
constexpr URL(URI &&other) noexcept
Convert a URI to an URL.
Definition URL.hpp:67
operator std::filesystem::path() const
Definition URL.hpp:248
constexpr URL(const char *str)
Construct a URI from a string.
Definition URL.hpp:91
constexpr URL(std::string const &str)
Construct a URI from a string.
Definition URL.hpp:83
std::filesystem::path filesystem_path() const
Create a filesystem path from a file URL.
Definition URL.hpp:216
constexpr std::u8string filesystem_path_generic_u8string(bool validate_scheme=true) const
Return a generic path.
Definition URL.hpp:107
constexpr URL(std::string_view str)
Construct a URI from a string.
Definition URL.hpp:75
Definition exception_intf.hpp:204
T move(T... args)
T operator()(T... args)
T to_string(T... args)