cxx_qt_lib/core/qurl.rs
1// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5use cxx::{type_id, ExternType};
6use std::fmt;
7use std::mem::MaybeUninit;
8
9use crate::{QByteArray, QString};
10
11#[cxx::bridge]
12mod ffi {
13 unsafe extern "C++" {
14 include!("cxx-qt-lib/qbytearray.h");
15 type QByteArray = crate::QByteArray;
16 include!("cxx-qt-lib/qstring.h");
17 type QString = crate::QString;
18 include!("cxx-qt-lib/qstringlist.h");
19 type QStringList = crate::QStringList;
20 include!("cxx-qt-lib/qurl.h");
21 type QUrl = super::QUrl;
22
23 /// Returns a `QUrl` representation of `local_file`, interpreted as a local file.
24 /// This function accepts paths separated by slashes as well as the native separator for this platform.
25 ///
26 /// This function also accepts paths with a doubled leading slash (or backslash) to indicate a remote file, as in `"//servername/path/to/file.txt"`.
27 #[Self = "QUrl"]
28 #[rust_name = "from_local_file"]
29 fn fromLocalFile(local_file: &QString) -> QUrl;
30
31 /// Returns a decoded copy of `input`. `input` is first decoded from percent encoding,
32 /// then converted from UTF-8 to unicode.
33 ///
34 /// **Note:** Given invalid input (such as a string containing the sequence `"%G5"`, which is not a valid hexadecimal number) the output will be invalid as well. As an example: the sequence `"%G5"` could be decoded to `"W"`.
35 #[Self = "QUrl"]
36 #[rust_name = "from_percent_encoding"]
37 fn fromPercentEncoding(input: &QByteArray) -> QString;
38
39 /// Returns the current whitelist of top-level domains that are allowed to have non-ASCII characters in their compositions.
40 #[Self = "QUrl"]
41 #[rust_name = "idn_whitelist"]
42 fn idnWhitelist() -> QStringList;
43
44 /// Sets the whitelist of Top-Level Domains (TLDs) that are allowed to have non-ASCII characters in domains to the value of `list`.
45 #[Self = "QUrl"]
46 #[rust_name = "set_idn_whitelist"]
47 fn setIdnWhitelist(list: &QStringList);
48
49 /// Returns an encoded copy of `input`. `input` is first converted to UTF-8,
50 /// and all ASCII-characters that are not in the unreserved group are percent encoded.
51 /// To prevent characters from being percent encoded pass them to `exclude`.
52 /// To force characters to be percent encoded pass them to `include`.
53 #[Self = "QUrl"]
54 #[rust_name = "to_percent_encoding"]
55 fn toPercentEncoding(
56 input: &QString,
57 exclude: &QByteArray,
58 include: &QByteArray,
59 ) -> QByteArray;
60
61 /// Resets the content of the `QUrl`. After calling this function,
62 /// the `QUrl` is equal to one that has been constructed with the default empty constructor.
63 fn clear(self: &mut QUrl);
64
65 /// Returns an error message if the last operation that modified this `QUrl` object ran into a parsing error.
66 /// If no error was detected, this function returns an empty string and [`is_valid`](Self::is_valid) returns `true`.
67 #[rust_name = "error_string"]
68 fn errorString(self: &QUrl) -> QString;
69
70 /// Returns `true` if this URL contains a fragment (i.e., if # was seen on it).
71 #[rust_name = "has_fragment"]
72 fn hasFragment(self: &QUrl) -> bool;
73
74 /// Returns `true` if this URL contains a Query (i.e., if ? was seen on it).
75 #[rust_name = "has_query"]
76 fn hasQuery(self: &QUrl) -> bool;
77
78 /// Returns `true` if the URL has no data; otherwise returns `false`.
79 #[rust_name = "is_empty"]
80 fn isEmpty(self: &QUrl) -> bool;
81
82 /// Returns `true` if this URL is pointing to a local file path. A URL is a local file path if the scheme is "file".
83 ///
84 /// Note that this function considers URLs with hostnames to be local file paths.
85 #[rust_name = "is_local_file"]
86 fn isLocalFile(self: &QUrl) -> bool;
87
88 /// Returns `true` if this URL is a parent of `child_url`.
89 /// `child_url` is a child of this URL if the two URLs share the same scheme and authority,
90 /// and this URL's path is a parent of the path of `child_url`.
91 #[rust_name = "is_parent_of"]
92 fn isParentOf(self: &QUrl, child_url: &QUrl) -> bool;
93
94 /// Returns `true` if the URL is relative; otherwise returns `false`.
95 /// A URL is relative reference if its scheme is undefined;
96 /// this function is therefore equivalent to calling `self.scheme().is_empty()`.
97 #[rust_name = "is_relative"]
98 fn isRelative(self: &QUrl) -> bool;
99
100 /// Returns `true` if the URL is non-empty and valid; otherwise returns `false`.
101 ///
102 /// The URL is run through a conformance test. Every part of the URL must conform to the standard encoding rules of the URI standard for the URL to be reported as valid.
103 #[rust_name = "is_valid"]
104 fn isValid(self: &QUrl) -> bool;
105
106 /// Returns the port of the URL, or `default_port` if the port is unspecified.
107 #[rust_name = "port_or"]
108 fn port(self: &QUrl, default_port: i32) -> i32;
109
110 /// Returns the result of the merge of this URL with `relative`. This URL is used as a base to convert `relative` to an absolute URL.
111 ///
112 /// If `relative` is not a relative URL, this function will return `relative` directly. Otherwise, the paths of the two URLs are merged, and the new URL returned has the scheme and authority of the base URL, but with the merged path.
113 ///
114 /// Calling this function with `".."` returns a `QUrl` whose directory is one level higher than the original. Similarly, calling this function with `"../.."` removes two levels from the path. If `relative` is `"/"`, the path becomes `"/"`.
115 fn resolved(self: &QUrl, relative: &QUrl) -> QUrl;
116
117 /// Returns the scheme of the URL. If an empty string is returned,
118 /// this means the scheme is undefined and the URL is then relative.
119 ///
120 /// The scheme can only contain US-ASCII letters or digits,
121 /// which means it cannot contain any character that would otherwise require encoding.
122 /// Additionally, schemes are always returned in lowercase form.
123 #[rust_name = "scheme_or_default"]
124 fn scheme(self: &QUrl) -> QString;
125
126 /// Sets the port of the URL to `port`.
127 /// The port is part of the authority of the URL, as described in [`set_authority`](Self::set_authority).
128 ///
129 /// `port` must be between 0 and 65535 inclusive. Setting the port to -1 indicates that the port is unspecified.
130 #[rust_name = "set_port"]
131 fn setPort(self: &mut QUrl, port: i32);
132
133 /// Returns the path of this URL formatted as a local file path.
134 /// The path returned will use forward slashes, even if it was originally created from one with backslashes.
135 ///
136 /// If this URL contains a non-empty hostname, it will be encoded in the returned value in the form found on SMB networks (for example, `"//servername/path/to/file.txt"`).
137 #[rust_name = "to_local_file_or_default"]
138 fn toLocalFile(self: &QUrl) -> QString;
139 }
140
141 // Bitwise enums don't work well with Rust and CXX, so lets just use the defaults for now
142 #[namespace = "rust::cxxqtlib1"]
143 unsafe extern "C++" {
144 #[rust_name = "qurl_authority"]
145 fn qurlAuthority(url: &QUrl) -> QString;
146 #[rust_name = "qurl_file_name"]
147 fn qurlFileName(url: &QUrl) -> QString;
148 #[rust_name = "qurl_fragment"]
149 fn qurlFragment(url: &QUrl) -> QString;
150 #[rust_name = "qurl_from_encoded"]
151 fn qurlFromEncoded(input: &QByteArray) -> QUrl;
152 #[rust_name = "qurl_from_user_input"]
153 fn qurlFromUserInput(user_input: &QString, working_directory: &QString) -> QUrl;
154 #[rust_name = "qurl_host"]
155 fn qurlHost(url: &QUrl) -> QString;
156 #[rust_name = "qurl_path"]
157 fn qurlPath(url: &QUrl) -> QString;
158 #[rust_name = "qurl_password"]
159 fn qurlPassword(url: &QUrl) -> QString;
160 #[rust_name = "qurl_query"]
161 fn qurlQuery(url: &QUrl) -> QString;
162 #[rust_name = "qurl_set_authority"]
163 fn qurlSetAuthority(url: &mut QUrl, authority: &QString);
164 #[rust_name = "qurl_set_fragment"]
165 fn qurlSetFragment(url: &mut QUrl, fragment: &QString);
166 #[rust_name = "qurl_set_host"]
167 fn qurlSetHost(url: &mut QUrl, host: &QString);
168 #[rust_name = "qurl_set_password"]
169 fn qurlSetPassword(url: &mut QUrl, password: &QString);
170 #[rust_name = "qurl_set_path"]
171 fn qurlSetPath(url: &mut QUrl, path: &QString);
172 #[rust_name = "qurl_set_query"]
173 fn qurlSetQuery(url: &mut QUrl, query: &QString);
174 #[rust_name = "qurl_set_scheme"]
175 fn qurlSetScheme(url: &mut QUrl, scheme: &QString);
176 #[rust_name = "qurl_set_url"]
177 fn qurlSetUrl(url: &mut QUrl, new_url: &QString);
178 #[rust_name = "qurl_set_user_info"]
179 fn qurlSetUserInfo(url: &mut QUrl, user_info: &QString);
180 #[rust_name = "qurl_set_user_name"]
181 fn qurlSetUserName(url: &mut QUrl, user_name: &QString);
182 #[rust_name = "qurl_to_display_string"]
183 fn qurlToDisplayString(url: &QUrl) -> QString;
184 #[rust_name = "qurl_to_encoded"]
185 fn qurlToEncoded(url: &QUrl) -> QByteArray;
186 #[rust_name = "qurl_to_qstring"]
187 fn qurlToQString(url: &QUrl) -> QString;
188 #[rust_name = "qurl_user_info"]
189 fn qurlUserInfo(url: &QUrl) -> QString;
190 #[rust_name = "qurl_user_name"]
191 fn qurlUserName(url: &QUrl) -> QString;
192 }
193
194 #[namespace = "rust::cxxqtlib1"]
195 unsafe extern "C++" {
196 include!("cxx-qt-lib/common.h");
197
198 #[doc(hidden)]
199 #[rust_name = "qurl_drop"]
200 fn drop(url: &mut QUrl);
201
202 #[doc(hidden)]
203 #[rust_name = "qurl_init_default"]
204 fn construct() -> QUrl;
205 #[doc(hidden)]
206 #[rust_name = "qurl_init_from_qstring"]
207 fn construct(string: &QString) -> QUrl;
208 #[doc(hidden)]
209 #[rust_name = "qurl_init_from_qurl"]
210 fn construct(url: &QUrl) -> QUrl;
211
212 #[doc(hidden)]
213 #[rust_name = "qurl_eq"]
214 fn operatorEq(a: &QUrl, b: &QUrl) -> bool;
215
216 #[doc(hidden)]
217 #[rust_name = "qurl_to_debug_qstring"]
218 fn toDebugQString(url: &QUrl) -> QString;
219 }
220}
221
222/// The `QUrl` class provides a convenient interface for working with URLs.
223///
224/// Qt Documentation: [QUrl](https://doc.qt.io/qt/qurl.html#details)
225#[repr(C)]
226pub struct QUrl {
227 _space: MaybeUninit<usize>,
228}
229
230impl QUrl {
231 /// Returns the authority of the URL if it is defined; otherwise an empty string is returned.
232 ///
233 /// This function returns an unambiguous value, which may contain that characters still percent-encoded, plus some control sequences not representable in decoded form in `QString`.
234 pub fn authority_or_default(&self) -> QString {
235 ffi::qurl_authority(self)
236 }
237
238 /// Returns the name of the file, excluding the directory path.
239 ///
240 /// Note that, if this `QUrl` object is given a path ending in a slash, the name of the file is considered empty.
241 ///
242 /// If the path doesn't contain any slash, it is fully returned as the file name.
243 pub fn file_name(&self) -> QString {
244 ffi::qurl_file_name(self)
245 }
246
247 /// Returns the fragment of the URL, or `None` if the URL does not contain a fragment.
248 pub fn fragment(&self) -> Option<QString> {
249 if self.has_fragment() {
250 Some(self.fragment_or_default())
251 } else {
252 None
253 }
254 }
255
256 /// Returns the fragment of the URL if it is defined; otherwise an empty string is returned.
257 pub fn fragment_or_default(&self) -> QString {
258 ffi::qurl_fragment(self)
259 }
260
261 /// Parses `input` and returns the corresponding `QUrl`. `input` is assumed to be in encoded form, containing only ASCII characters.
262 pub fn from_encoded(input: &QByteArray) -> Self {
263 ffi::qurl_from_encoded(input)
264 }
265
266 /// Returns a valid URL from a user supplied `user_input` string if one can be deduced.
267 /// In the case that is not possible, an invalid `QUrl` is returned.
268 ///
269 /// This allows the user to input a URL or a local file path in the form of a plain string. This string can be manually typed into a location bar, obtained from the clipboard, or passed in via command line arguments.
270 ///
271 /// When the string is not already a valid URL, a best guess is performed, making various assumptions.
272 ///
273 /// In the case the string corresponds to a valid file path on the system, a `file://` URL is constructed, using [`from_local_file`](Self::from_local_file).
274 ///
275 /// In order to be able to handle relative paths, this method takes an optional `working_directory` path. This is especially useful when handling command line arguments. If `working_directory` is empty, no handling of relative paths will be done.
276 pub fn from_user_input(user_input: &QString, working_directory: &QString) -> Self {
277 ffi::qurl_from_user_input(user_input, working_directory)
278 }
279
280 /// Returns the host of the URL if it is defined; otherwise an empty string is returned.
281 pub fn host_or_default(&self) -> QString {
282 ffi::qurl_host(self)
283 }
284
285 /// Returns the password of the URL if it is defined; otherwise an empty string is returned.
286 pub fn password_or_default(&self) -> QString {
287 ffi::qurl_password(self)
288 }
289
290 /// Returns the path of the URL.
291 pub fn path(&self) -> QString {
292 ffi::qurl_path(self)
293 }
294
295 /// Returns the query string of the URL if there's a query string, or `None` if not.
296 pub fn query(&self) -> Option<QString> {
297 if self.has_query() {
298 Some(self.query_or_default())
299 } else {
300 None
301 }
302 }
303
304 /// Returns the query string of the URL if there's a query string, or an empty result if not.
305 pub fn query_or_default(&self) -> QString {
306 ffi::qurl_query(self)
307 }
308
309 /// Returns the scheme of the URL. If `None` is returned,
310 /// this means the scheme is undefined and the URL is then relative.
311 ///
312 /// The scheme can only contain US-ASCII letters or digits,
313 /// which means it cannot contain any character that would otherwise require encoding
314 /// Additionally, schemes are always returned in lowercase form.
315 pub fn scheme(&self) -> Option<QString> {
316 let scheme = self.scheme_or_default();
317 if scheme.is_empty() {
318 None
319 } else {
320 Some(scheme)
321 }
322 }
323
324 /// Sets the authority of the URL to `authority`.
325 pub fn set_authority(&mut self, authority: &QString) {
326 ffi::qurl_set_authority(self, authority)
327 }
328
329 /// Sets the fragment of the URL to `fragment`.
330 /// The fragment is the last part of the URL, represented by a `'#'` followed by a string of characters.
331 pub fn set_fragment(&mut self, fragment: &QString) {
332 ffi::qurl_set_fragment(self, fragment)
333 }
334
335 /// Sets the host of the URL to `host`. The host is part of the authority.
336 pub fn set_host(&mut self, host: &QString) {
337 ffi::qurl_set_host(self, host)
338 }
339
340 /// Sets the URL's password to `password`.
341 pub fn set_password(&mut self, password: &QString) {
342 ffi::qurl_set_password(self, password)
343 }
344
345 /// Sets the path of the URL to `path`.
346 /// The path is the part of the URL that comes after the authority but before the query string.
347 pub fn set_path(&mut self, path: &QString) {
348 ffi::qurl_set_path(self, path)
349 }
350
351 /// Sets the query string of the URL to `query`.
352 pub fn set_query(&mut self, query: &QString) {
353 ffi::qurl_set_query(self, query)
354 }
355
356 /// Sets the scheme of the URL to `scheme`. As a scheme can only contain ASCII characters,
357 /// no conversion or decoding is done on the input. It must also start with an ASCII letter.
358 pub fn set_scheme(&mut self, scheme: &QString) {
359 ffi::qurl_set_scheme(self, scheme)
360 }
361
362 /// Parses `url` and sets this object to that value.
363 /// `QUrl` will automatically percent encode all characters that are not allowed in a URL
364 /// and decode the percent-encoded sequences that represent an unreserved character
365 /// (letters, digits, hyphens, underscores, dots and tildes).
366 /// All other characters are left in their original forms.
367 pub fn set_url(&mut self, url: &QString) {
368 ffi::qurl_set_url(self, url)
369 }
370
371 /// Sets the user info of the URL to `user_info`.
372 pub fn set_user_info(&mut self, user_info: &QString) {
373 ffi::qurl_set_user_info(self, user_info)
374 }
375
376 /// Sets the URL's user name to `user_name`.
377 pub fn set_user_name(&mut self, user_name: &QString) {
378 ffi::qurl_set_user_name(self, user_name)
379 }
380
381 /// Returns a human-displayable string representation of the URL.
382 /// The option [RemovePassword](https://doc.qt.io/qt/qurl.html#UrlFormattingOption-enum) is always enabled, since passwords should never be shown back to users.
383 pub fn to_display_string(&self) -> QString {
384 ffi::qurl_to_display_string(self)
385 }
386
387 /// Returns the encoded representation of the URL if it's valid; otherwise an empty `QByteArray` is returned.
388 ///
389 /// The user info, path and fragment are all converted to UTF-8, and all non-ASCII characters are then percent encoded. The host name is encoded using Punycode.
390 pub fn to_encoded(&self) -> QByteArray {
391 ffi::qurl_to_encoded(self)
392 }
393
394 /// Returns the path of this URL formatted as a local file path, or `None` if this URL is not pointing to a local file path.
395 /// The path returned will use forward slashes, even if it was originally created from one with backslashes.
396 ///
397 /// If this URL contains a non-empty hostname, it will be encoded in the returned value in the form found on SMB networks (for example, `"//servername/path/to/file.txt"`).
398 ///
399 /// Note: if the path component of this URL contains a non-UTF-8 binary sequence (such as %80), the behaviour of this function is undefined.
400 pub fn to_local_file(&self) -> Option<QString> {
401 if self.is_local_file() {
402 Some(self.to_local_file_or_default())
403 } else {
404 None
405 }
406 }
407
408 /// Returns a `QString` representation of the URL.
409 pub fn to_qstring(&self) -> QString {
410 ffi::qurl_to_qstring(self)
411 }
412
413 /// Returns the user info of the URL, or an empty string if the user info is undefined.
414 pub fn user_info_or_default(&self) -> QString {
415 ffi::qurl_user_info(self)
416 }
417
418 /// Returns the user name of the URL if it is defined; otherwise an empty string is returned.
419 pub fn user_name_or_default(&self) -> QString {
420 ffi::qurl_user_name(self)
421 }
422}
423
424impl Clone for QUrl {
425 /// Constructs a copy of other.
426 fn clone(&self) -> Self {
427 ffi::qurl_init_from_qurl(self)
428 }
429}
430
431impl Default for QUrl {
432 /// Constructs an empty QUrl object.
433 fn default() -> Self {
434 ffi::qurl_init_default()
435 }
436}
437
438impl std::cmp::PartialEq for QUrl {
439 fn eq(&self, other: &Self) -> bool {
440 ffi::qurl_eq(self, other)
441 }
442}
443
444impl std::cmp::Eq for QUrl {}
445
446impl fmt::Display for QUrl {
447 /// Format the `QUrl` as a Rust string.
448 ///
449 /// Note that this converts from UTF-16 to UTF-8.
450 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
451 ffi::qurl_to_display_string(self).fmt(f)
452 }
453}
454
455impl fmt::Debug for QUrl {
456 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
457 ffi::qurl_to_debug_qstring(self).fmt(f)
458 }
459}
460
461impl Drop for QUrl {
462 /// Destructor; called immediately before the object is deleted.
463 fn drop(&mut self) {
464 ffi::qurl_drop(self)
465 }
466}
467
468impl From<&QString> for QUrl {
469 /// Constructs a `QUrl` from a `QString`.
470 fn from(str: &QString) -> Self {
471 ffi::qurl_init_from_qstring(str)
472 }
473}
474
475impl From<&str> for QUrl {
476 /// Constructs a `QUrl` from a Rust string.
477 ///
478 /// Note that this converts from UTF-8 to UTF-16.
479 fn from(str: &str) -> Self {
480 Self::from(&QString::from(str))
481 }
482}
483
484impl From<&String> for QUrl {
485 /// Constructs a `QUrl` from a Rust string.
486 ///
487 /// Note that this converts from UTF-8 to UTF-16.
488 fn from(str: &String) -> Self {
489 Self::from(str.as_str())
490 }
491}
492
493#[cfg(feature = "http")]
494impl From<&http::Uri> for QUrl {
495 fn from(value: &http::Uri) -> Self {
496 QUrl::from(&value.to_string())
497 }
498}
499
500#[cfg(feature = "http")]
501impl TryFrom<&QUrl> for http::Uri {
502 type Error = http::uri::InvalidUri;
503
504 fn try_from(value: &QUrl) -> Result<Self, Self::Error> {
505 value.to_string().parse::<http::Uri>()
506 }
507}
508
509#[cfg(feature = "url")]
510impl From<&url::Url> for QUrl {
511 fn from(value: &url::Url) -> Self {
512 QUrl::from(&value.to_string())
513 }
514}
515
516#[cfg(feature = "url")]
517impl TryFrom<&QUrl> for url::Url {
518 type Error = url::ParseError;
519
520 fn try_from(value: &QUrl) -> Result<Self, Self::Error> {
521 url::Url::parse(value.to_string().as_str())
522 }
523}
524
525#[cfg(feature = "serde")]
526impl serde::Serialize for QUrl {
527 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
528 ffi::qurl_to_qstring(self).serialize(serializer)
529 }
530}
531
532#[cfg(feature = "serde")]
533impl<'de> serde::Deserialize<'de> for QUrl {
534 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
535 let string = QString::deserialize(deserializer)?;
536 Ok(Self::from(&string))
537 }
538}
539
540// Safety:
541//
542// Static checks on the C++ side to ensure the size is the same.
543unsafe impl ExternType for QUrl {
544 type Id = type_id!("QUrl");
545 type Kind = cxx::kind::Trivial;
546}
547
548#[cfg(test)]
549mod tests {
550 use super::*;
551
552 #[cfg(feature = "serde")]
553 #[test]
554 fn qurl_serde() {
555 let qurl = QUrl::from("https://github.com/kdab/cxx-qt");
556 assert_eq!(crate::serde_impl::roundtrip(&qurl), qurl);
557 }
558
559 #[cfg(feature = "http")]
560 #[test]
561 fn test_http() {
562 let uri = "https://github.com/kdab/cxx-qt"
563 .parse::<http::Uri>()
564 .unwrap();
565 let qurl = QUrl::from(&uri);
566 assert_eq!(uri.to_string(), qurl.to_string());
567
568 let http_uri = http::Uri::try_from(&qurl).unwrap();
569 assert_eq!(http_uri, uri);
570 }
571
572 #[cfg(feature = "url")]
573 #[test]
574 fn test_url() {
575 let url = url::Url::parse("https://github.com/kdab/cxx-qt").unwrap();
576 let qurl = QUrl::from(&url);
577 assert_eq!(url.to_string(), qurl.to_string());
578
579 let url_url = url::Url::try_from(&qurl).unwrap();
580 assert_eq!(url_url, url);
581 }
582}