Skip to main content

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}