Skip to main content

servoshell/
parser.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#[cfg(not(any(target_os = "android", target_env = "ohos")))]
6use std::path::{Path, PathBuf};
7
8use servo::{ServoUrl, is_reg_domain};
9
10#[cfg(not(any(target_os = "android", target_env = "ohos")))]
11pub fn parse_url_or_filename(cwd: &Path, input: &str) -> Result<ServoUrl, ()> {
12    match ServoUrl::parse(input) {
13        Ok(url) => Ok(url),
14        Err(url::ParseError::RelativeUrlWithoutBase) => {
15            url::Url::from_file_path(&*cwd.join(input)).map(ServoUrl::from_url)
16        },
17        Err(_) => Err(()),
18    }
19}
20
21#[cfg(not(any(target_os = "android", target_env = "ohos")))]
22pub fn get_default_url(
23    url_opt: Option<&str>,
24    cwd: impl AsRef<Path>,
25    exists: impl FnOnce(&PathBuf) -> bool,
26    preferences: &crate::prefs::ServoShellPreferences,
27) -> ServoUrl {
28    // If the url is not provided, we fallback to the homepage in prefs,
29    // or a blank page in case the homepage is not set either.
30    let mut new_url = None;
31    let cmdline_url = url_opt.map(|s| s.to_string()).and_then(|url_string| {
32        parse_url_or_filename(cwd.as_ref(), &url_string)
33            .inspect_err(|&error| {
34                log::warn!("URL parsing failed ({:?}).", error);
35            })
36            .ok()
37    });
38
39    if let Some(url) = cmdline_url.clone() {
40        // Check if the URL path corresponds to a file
41        match (url.scheme(), url.host(), url.to_file_path()) {
42            ("file", None, Ok(ref path)) if exists(path) => {
43                new_url = cmdline_url;
44            },
45            (scheme, None, Err(_)) if !is_normal_scheme(scheme) => {
46                new_url = ServoUrl::parse(&format!("http://{}:{}", scheme, &url.path())).ok();
47            },
48            _ => {},
49        }
50    }
51
52    #[allow(
53        clippy::collapsible_if,
54        reason = "let chains are not available in 1.85"
55    )]
56    if new_url.is_none() {
57        if let Some(url_opt) = url_opt {
58            new_url = location_bar_input_to_url(url_opt, &preferences.searchpage);
59        }
60    }
61
62    let pref_url = parse_url_or_filename(cwd.as_ref(), &preferences.homepage).ok();
63    let blank_url = ServoUrl::parse("about:blank").ok();
64
65    new_url.or(pref_url).or(blank_url).unwrap()
66}
67
68/// Interpret an input URL.
69///
70/// If this is not a valid URL, try to "fix" it by adding a scheme or if all else fails,
71/// interpret the string as a search term.
72pub(crate) fn location_bar_input_to_url(request: &str, searchpage: &str) -> Option<ServoUrl> {
73    let request = request.trim();
74    let input_url = ServoUrl::parse(request).ok();
75    if let Some(url) = input_url {
76        match (url.scheme(), url.host(), url.to_file_path()) {
77            (scheme, None, Err(_)) if !is_normal_scheme(scheme) => {
78                ServoUrl::parse(&format!("http://{}:{}", scheme, &url.path())).ok()
79            },
80            _ => Some(url),
81        }
82    } else {
83        try_as_file(request)
84            .or_else(|| try_as_domain(request))
85            .or_else(|| try_as_search_page(request, searchpage))
86    }
87}
88
89fn try_as_file(request: &str) -> Option<ServoUrl> {
90    if request.starts_with('/') {
91        return ServoUrl::parse(&format!("file://{}", request)).ok();
92    }
93    None
94}
95
96fn try_as_domain(request: &str) -> Option<ServoUrl> {
97    fn is_domain_like(s: &str) -> bool {
98        !s.starts_with('/') && s.contains('/') ||
99            (!s.contains(' ') && !s.starts_with('.') && s.split('.').count() > 1)
100    }
101
102    if !request.contains(' ') && is_reg_domain(request) || is_domain_like(request) {
103        return ServoUrl::parse(&format!("https://{}", request)).ok();
104    }
105    None
106}
107
108fn try_as_search_page(request: &str, searchpage: &str) -> Option<ServoUrl> {
109    if request.is_empty() {
110        return None;
111    }
112    ServoUrl::parse(&searchpage.replace("%s", request)).ok()
113}
114
115fn is_normal_scheme(s: &str) -> bool {
116    s == "file" ||
117        s == "http" ||
118        s == "https" ||
119        s == "ws" ||
120        s == "wss" ||
121        s == "ftp" ||
122        s == "data"
123}