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
5#pragma once
6
7#include "utility.hpp"
8#include "assert.hpp"
9#include "URI.hpp"
10#include <string>
11#include <string_view>
12#include <optional>
13#include <vector>
14#include <unordered_map>
15#include <memory>
16#include <ostream>
17#include <mutex>
18#include <filesystem>
19
20namespace hi { inline namespace v1 {
21class resource_view;
22
48class URL : public URI {
49public:
50 constexpr URL() noexcept = default;
51 constexpr URL(URL const&) noexcept = default;
52 constexpr URL(URL&&) noexcept = default;
53 constexpr URL& operator=(URL const&) noexcept = default;
54 constexpr URL& operator=(URL&&) noexcept = default;
55
56 constexpr explicit URL(URI const& other) noexcept : URI(other) {}
57 constexpr explicit URL(URI&& other) noexcept : URI(std::move(other)){};
58
59 static std::string make_file_url_string(std::filesystem::path const& path)
60 {
61 auto r = std::string{};
62
63 hilet root_name = path.root_name().generic_string();
64 if (root_name.empty()) {
65 // No root-name.
66 if (not path.root_directory().empty()) {
67 // An absolute path should start with the file: scheme.
68 r += "file:" + path.root_directory().generic_string();
69 } else {
70 // A relative path should not be prefixed with a scheme.
71 ;
72 }
73
74 } else if (hilet i = root_name.find(':'); i != std::string::npos) {
75 if (i == 1) {
76 // Root name is a drive-letter, followed by potentially a relative path.
77 r += "file:///" + root_name + path.root_directory().generic_string();
78 } else {
79 throw url_error("Paths containing a device are not allowed to be converted to a URL.");
80 }
81 } else {
82 // Root name is a server.
83 r += "file://" + root_name + path.root_directory().generic_string();
84 if (not path.root_directory().empty()) {
85 throw url_error("Invalid path contains server name without a root directory.");
86 }
87 }
88
89 return r + path.relative_path().generic_string();
90 }
91
92 explicit URL(std::filesystem::path const& path) : URI(make_file_url_string(path)) {}
93
94 constexpr void static validate_file_segment(std::string_view segment)
95 {
96 for (auto c : segment) {
97 if (c == '/' or c == '\\') {
98 throw url_error("Filename server name may not contain slash or back-slash.");
99 }
100 }
101 }
102
103 constexpr void static validate_file_server(std::string_view server)
104 {
105 for (auto c : server) {
106 if (c == '/' or c == '\\') {
107 throw url_error("Filename segments may not contain slash or back-slash.");
108 }
109 }
110 }
111
117 [[nodiscard]] std::string generic_path() const
118 {
119 if (not(not scheme() or scheme() == "file")) {
120 throw url_error("URL::generic_path() is only valid on a file: scheme URL");
121 }
122
123 auto r = std::string{};
124 hilet& p = path();
125 hilet first = p.begin();
126 hilet last = p.end();
127 auto it = first;
128
129 auto has_root_name = false;
130 if (authority()) {
131 // file://server/filename is valid.
132 auto server = to_string(*authority());
133 if (not server.empty() and server != "localhost") {
134 validate_file_server(server);
135 has_root_name = true;
136 r += '/';
137 r += '/';
138 r += server;
139 r += '/';
140 }
141 }
142
143 // If a server was found than the path must be absolute.
144 hi_axiom(has_root_name == false or p.absolute());
145
146 if (p.double_absolute()) {
147 // file:////server/filename is valid.
148 if (has_root_name) {
149 // file://server//server/filename is invalid.
150 throw url_error("file URL has two server names.");
151 }
152
153 has_root_name = true;
154 r += '/';
155 r += '/';
156 it += 2;
157 validate_file_server(*it);
158 r += *it++;
159 r += '/';
160 }
161
162 // Find the drive letter.
163 auto empty_segment = false;
164 while (it != last) {
165 validate_file_segment(*it);
166 empty_segment = it->empty();
167
168 if (it == first and empty_segment) {
169 // Skip leading '/' in front of drive letter.
170 ++it;
171
172 } else if (auto i = it->find(':'); i != std::string::npos) {
173 // Found a drive letter.
174 if (i != 1) {
175 throw url_error("file URL contains a device name which is a security issue.");
176 }
177
178 if (has_root_name or p.absolute()) {
179 r += it->front();
180 // Use $ when the drive letter is on a server.
181 r += has_root_name ? '$' : ':';
182 // Add potentially a relative segment after the driver letter.
183 r += it->substr(2);
184
185 } else {
186 // Take the drive letter and optional relative directory directly on relative paths.
187 // C:dirname valid
188 // C:/dirname valid
189 // file:C:dirname valid
190 // file:C:/dirname valid
191 r += *it;
192 }
193
194 has_root_name = true;
195 r += '/';
196 ++it;
197 break;
198
199 } else {
200 // Found a normal directory or filename.
201 break;
202 }
203 }
204
205 // The rest are directories followed by a single (optionally empty) filename
206 for (; it != last; ++it) {
207 validate_file_segment(*it);
208 empty_segment = it->empty();
209 r += *it;
210 r += '/';
211 }
212
213 if (not empty_segment) {
214 // Remove trailing backslash if the last segment was a filename.
215 r.pop_back();
216 }
217
218 return r;
219 }
220
226 [[nodiscard]] std::filesystem::path filesystem_path() const
227 {
228 if (auto scheme_ = scheme()) {
229 if (scheme_ == "resource") {
230 return URL::url_from_resource_directory() / *this;
231 } else if (scheme_ == "file") {
232 return {generic_path()};
233 } else {
234 throw url_error("URL can not be converted to a filesystem path.");
235 }
236 } else {
237 return {generic_path()};
238 }
239 }
240
241 operator std::filesystem::path() const
242 {
243 return filesystem_path();
244 }
245
250
251 [[nodiscard]] constexpr friend URL operator/(URL const& base, URI const& ref) noexcept
252 {
253 return URL{up_cast<URI const&>(base) / ref};
254 }
255
256 [[nodiscard]] constexpr friend URL operator/(URL const& base, std::string_view ref) noexcept
257 {
258 return URL{up_cast<URI const&>(base) / ref};
259 }
260
261 [[nodiscard]] static URL url_from_current_working_directory() noexcept;
262 [[nodiscard]] static URL url_from_resource_directory() noexcept;
263 [[nodiscard]] static URL url_from_executable_directory() noexcept;
264 [[nodiscard]] static URL url_from_executable_file() noexcept;
265 [[nodiscard]] static URL url_from_application_data_directory() noexcept;
266 [[nodiscard]] static URL url_from_application_log_directory() noexcept;
267 [[nodiscard]] static URL url_from_system_font_directory() noexcept;
268 [[nodiscard]] static URL url_from_application_preferences_file() noexcept;
269};
270}} // namespace hi::v1
271
272template<>
273struct std::hash<hi::URL> {
274 [[nodiscard]] size_t operator()(hi::URL const& rhs) const noexcept
275 {
276 return std::hash<hi::URI>{}(rhs);
277 }
278};
279
280template<typename CharT>
281struct std::formatter<hi::URL, CharT> : std::formatter<hi::URI, CharT> {
282 auto format(hi::URL const& t, auto& fc)
283 {
284 return std::formatter<hi::URI, CharT>::format(t, fc);
285 }
286};
Utilities used by the HikoGUI library itself.
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
@ other
The gui_event does not have associated data.
STL namespace.
DOXYGEN BUG.
Definition algorithm.hpp:15
The HikoGUI namespace.
Definition ascii.hpp:19
Definition exception.hpp:108
Definition URI.hpp:25
constexpr std::optional< authority_type > const & authority() const noexcept
Get the authority-component of the URI.
Definition URI.hpp:522
constexpr std::optional< std::string > const & scheme() const noexcept
Get the scheme-component of the URI.
Definition URI.hpp:499
Universal Resource Locator.
Definition URL.hpp:48
std::string generic_path() const
Return a generic path.
Definition URL.hpp:117
std::unique_ptr< resource_view > loadView() const
Load a resource.
std::filesystem::path filesystem_path() const
Create a filesystem path from a file URL.
Definition URL.hpp:226
T move(T... args)