Sebastian Thielthanks clippybac57dd3 years agoHistory
223 lines · 7.0 KB · Rust
RawDownload
//! A library implementing a URL for use in git with access to its special capabilities.//! ## Feature Flags#![cfg_attr(    feature = "document-features",    cfg_attr(doc, doc = ::document_features::document_features!()))]#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]#![deny(rust_2018_idioms, missing_docs)]#![forbid(unsafe_code)] use bstr::{BStr, BString}; ///pub mod parse;#[doc(inline)]pub use parse::parse; ///pub mod expand_path;#[doc(inline)]pub use expand_path::expand_path; mod scheme;pub use scheme::Scheme; /// A URL with support for specialized git related capabilities.////// Additionally there is support for [deserialization][Url::from_bytes()] and serialization/// (_see the `Display::fmt()` implementation_).////// # Deviation////// Note that we do not support passing the password using the URL as it's likely leading to accidents.#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]pub struct Url {    /// The URL scheme.    pub scheme: Scheme,    /// The user to impersonate on the remote.    user: Option<String>,    /// The host to which to connect. Localhost is implied if `None`.    host: Option<String>,    /// When serializing, use the alternative forms as it was parsed as such.    serialize_alternative_form: bool,    /// The port to use when connecting to a host. If `None`, standard ports depending on `scheme` will be used.    pub port: Option<u16>,    /// The path portion of the URL, usually the location of the git repository.    pub path: bstr::BString,} /// Instantiationimpl Url {    /// Create a new instance from the given parts, which will be validated by parsing them back.    pub fn from_parts(        scheme: Scheme,        user: Option<String>,        host: Option<String>,        port: Option<u16>,        path: BString,    ) -> Result<Self, parse::Error> {        parse(            Url {                scheme,                user,                host,                port,                path,                serialize_alternative_form: false,            }            .to_bstring()            .as_ref(),        )    }     /// Create a new instance from the given parts, which will be validated by parsing them back from its alternative form.    pub fn from_parts_as_alternative_form(        scheme: Scheme,        user: Option<String>,        host: Option<String>,        port: Option<u16>,        path: BString,    ) -> Result<Self, parse::Error> {        parse(            Url {                scheme,                user,                host,                port,                path,                serialize_alternative_form: true,            }            .to_bstring()            .as_ref(),        )    }} /// Modificationimpl Url {    /// Set the given `user`, with `None` unsetting it. Returns the previous value.    pub fn set_user(&mut self, user: Option<String>) -> Option<String> {        let prev = self.user.take();        self.user = user;        prev    }} /// Builderimpl Url {    /// Enable alternate serialization for this url, e.g. `file:///path` becomes `/path`.    ///    /// This is automatically set correctly for parsed URLs, but can be set here for urls    /// created by constructor.    pub fn serialize_alternate_form(mut self, use_alternate_form: bool) -> Self {        self.serialize_alternative_form = use_alternate_form;        self    }     /// Turn a file url like `file://relative` into `file:///root/relative`, hence it assures the url's path component is absolute.    pub fn canonicalize(&mut self) -> Result<(), git_path::realpath::Error> {        if self.scheme == Scheme::File {            let path = git_path::from_bstr(self.path.as_ref());            let abs_path = git_path::realpath(path)?;            self.path = git_path::into_bstr(abs_path).into_owned();        }        Ok(())    }} /// Accessimpl Url {    /// Returns the user mentioned in the url, if present.    pub fn user(&self) -> Option<&str> {        self.user.as_deref()    }    /// Returns the host mentioned in the url, if present.    pub fn host(&self) -> Option<&str> {        self.host.as_deref()    }    /// Returns true if the path portion of the url is `/`.    pub fn path_is_root(&self) -> bool {        self.path == "/"    }    /// Returns the actual or default port for use according to the url scheme.    /// Note that there may be no default port either.    pub fn port_or_default(&self) -> Option<u16> {        self.port.or_else(|| {            use Scheme::*;            Some(match self.scheme {                Http => 80,                Https => 443,                Ssh => 22,                Git => 9418,                File | Ext(_) => return None,            })        })    }} /// Transformationimpl Url {    /// Turn a file url like `file://relative` into `file:///root/relative`, hence it assures the url's path component is absolute.    pub fn canonicalized(&self) -> Result<Self, git_path::realpath::Error> {        let mut res = self.clone();        res.canonicalize()?;        Ok(res)    }} /// Serializationimpl Url {    /// Write this URL losslessly to `out`, ready to be parsed again.    pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {        if !(self.serialize_alternative_form && (self.scheme == Scheme::File || self.scheme == Scheme::Ssh)) {            out.write_all(self.scheme.as_str().as_bytes())?;            out.write_all(b"://")?;        }        match (&self.user, &self.host) {            (Some(user), Some(host)) => {                out.write_all(user.as_bytes())?;                out.write_all(&[b'@'])?;                out.write_all(host.as_bytes())?;            }            (None, Some(host)) => {                out.write_all(host.as_bytes())?;            }            (None, None) => {}            (Some(_user), None) => unreachable!("BUG: should not be possible to have a user but no host"),        };        if let Some(port) = &self.port {            write!(&mut out, ":{port}")?;        }        if self.serialize_alternative_form && self.scheme == Scheme::Ssh {            out.write_all(b":")?;        }        out.write_all(&self.path)?;        Ok(())    }     /// Transform ourselves into a binary string, losslessly, or fail if the URL is malformed due to host or user parts being incorrect.    pub fn to_bstring(&self) -> bstr::BString {        let mut buf = Vec::with_capacity(            (5 + 3)                + self.user.as_ref().map(|n| n.len()).unwrap_or_default()                + 1                + self.host.as_ref().map(|h| h.len()).unwrap_or_default()                + self.port.map(|_| 5).unwrap_or_default()                + self.path.len(),        );        self.write_to(&mut buf).expect("io cannot fail in memory");        buf.into()    }} /// Deserializationimpl Url {    /// Parse a URL from `bytes`    pub fn from_bytes(bytes: &BStr) -> Result<Self, parse::Error> {        parse(bytes)    }} mod impls;