safe_uri 0.1.0-beta.4

Simple and safe URI types.
Documentation
mod error;

use self::error::ParseUriErr;
use crate::*;
use shared_bytes::SharedStr;

pub use self::error::ParseUriError;

pub(crate) fn parse_uri(input: SharedStr) -> Result<Uri, ParseUriError> {
    parse_uri_internal(input).map_err(ParseUriError)
}

pub(crate) fn parse_uri_ref(input: SharedStr) -> Result<UriRef, ParseUriError> {
    parse_uri_ref_internal(input).map_err(ParseUriError)
}

type RemainderResult<T, E> = Result<(T, SharedStr), E>;

fn parse_uri_internal(input: SharedStr) -> Result<Uri, ParseUriErr> {
    parse_uri_ref_internal(input)?
        .try_into()
        .map_err(|_| ParseUriErr::MissingScheme)
}

fn parse_uri_ref_internal(input: SharedStr) -> Result<UriRef, ParseUriErr> {
    let (scheme, remainder) = scheme(input).map_err(ParseUriErr::Scheme)?;
    let (authority, remainder) = authority(remainder)?;
    let (path, remainder) = if !remainder.is_empty()
        && (remainder.starts_with('/') || !remainder.starts_with(|c| matches!(c, '?' | '#')))
    {
        path(remainder).map_err(ParseUriErr::Path)?
    } else {
        (Path::new(), remainder)
    };
    let (query, remainder) = query(remainder).map_err(ParseUriErr::Query)?;
    let fragment = fragment(remainder).map_err(ParseUriErr::Fragment)?;
    let mut resource = Resource::new();
    resource.path = path;
    resource.query = query;
    resource.fragment = fragment;
    let mut uri = UriRef::new();
    uri.scheme = scheme;
    uri.authority = authority;
    uri.resource = resource;
    Ok(uri)
}

fn scheme(input: SharedStr) -> RemainderResult<Option<Scheme>, InvalidScheme> {
    for (index, byte) in input.bytes().enumerate() {
        match byte {
            b':' => {
                let scheme = Scheme::try_from(input.slice_cloned(..index))?;
                let remainder = input.slice_into(index + 1..);
                return Ok((Some(scheme), remainder));
            }
            b'/' | b'?' | b'#' => return Ok((None, input)),
            _ => continue,
        }
    }
    Ok((None, input))
}

fn authority(input: SharedStr) -> RemainderResult<Option<Authority>, ParseUriErr> {
    let remainder = match input.strip_prefix("//") {
        Some(r) => {
            let r = input.range_of_subset(r);
            input.slice_into(r)
        }
        None => return Ok((None, input)),
    };
    let (user_info, remainder) = user_info(remainder).map_err(ParseUriErr::UserInfo)?;
    let (host, remainder) = host(remainder)?;
    let (port, remainder) = match remainder.strip_prefix(':') {
        Some(r) => {
            let r = remainder.range_of_subset(r);
            let remainder = remainder.slice_into(r);
            let (p, r) = port(remainder)?;
            (Some(p), r)
        }
        None => (None, remainder),
    };
    let mut authority = Authority::new();
    authority.user_info = user_info;
    authority.host = host;
    authority.port = port;
    Ok((Some(authority), remainder))
}

fn user_info(input: SharedStr) -> RemainderResult<Option<UserInfo>, InvalidUserInfo> {
    for (index, byte) in input.bytes().enumerate() {
        match byte {
            b'@' => {
                let user_info = UserInfo::try_from(input.slice_cloned(..index))?;
                let remainder = input.slice_into(index + 1..);
                return Ok((Some(user_info), remainder));
            }
            b'/' | b'?' | b'#' => return Ok((None, input)),
            _ => continue,
        }
    }
    Ok((None, input))
}

fn host(input: SharedStr) -> RemainderResult<Host, ParseUriErr> {
    let (host_str, remainder) = host_str(input);
    match host_from_str(host_str) {
        Ok(host) => Ok((host, remainder)),
        Err(e) => Err(e),
    }
}

fn host_str(input: SharedStr) -> (SharedStr, SharedStr) {
    if input.starts_with('[') {
        for (index, byte) in input.bytes().enumerate() {
            match byte {
                b'/' | b'?' | b'#' => {
                    return split_at(input, index);
                }
                b']' => {
                    let next_index = index + 1;
                    return split_at(input, next_index);
                }
                _ => continue,
            }
        }
    } else {
        for (index, byte) in input.bytes().enumerate() {
            match byte {
                b':' | b'/' | b'?' | b'#' => {
                    return split_at(input, index);
                }
                _ => continue,
            }
        }
    }
    (input, SharedStr::new())
}

fn host_from_str(s: SharedStr) -> Result<Host, ParseUriErr> {
    if let Some(ipv6_str) = s.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
        return match ipv6_str.parse() {
            Ok(ipv6) => Ok(Host::Ipv6Addr(ipv6)),
            Err(e) => Err(ParseUriErr::Ipv6(e)),
        };
    }
    if let Ok(x) = s.parse() {
        return Ok(Host::Ipv4Addr(x));
    }
    Ok(Host::Name(s.try_into().map_err(ParseUriErr::HostName)?))
}

fn port(input: SharedStr) -> RemainderResult<u16, ParseUriErr> {
    let (port, remainder) = (|| {
        for (index, byte) in input.bytes().enumerate() {
            match byte {
                b'/' | b'?' | b'#' => return Ok(split_at(input, index)),
                _ if !byte.is_ascii_digit() => return Err(ParseUriErr::NonDigitPortByte(byte)),
                _ => continue,
            }
        }
        Ok((input, SharedStr::new()))
    })()?;
    Ok((port.parse().map_err(ParseUriErr::Port)?, remainder))
}

fn path(input: SharedStr) -> RemainderResult<Path, InvalidPath> {
    let split_path_from_remainder = || {
        for (index, byte) in input.bytes().enumerate() {
            match byte {
                b'?' | b'#' => return split_at(input, index),
                _ => continue,
            }
        }
        (input, SharedStr::new())
    };
    let (path, remainder) = split_path_from_remainder();
    Ok((path.try_into()?, remainder))
}

fn query(input: SharedStr) -> RemainderResult<Option<Query>, InvalidQuery> {
    let remainder = match input.strip_prefix('?') {
        None => return Ok((None, input)),
        Some(r) => {
            let r = input.range_of_subset(r);
            input.slice_into(r)
        }
    };
    let split_query_from_remainder = || {
        for (index, byte) in remainder.bytes().enumerate() {
            match byte {
                b'#' => return split_at(remainder, index),
                _ => continue,
            }
        }
        (remainder, SharedStr::new())
    };
    let (query, remainder) = split_query_from_remainder();
    Ok((Some(query.try_into()?), remainder))
}

fn fragment(input: SharedStr) -> Result<Option<Fragment>, InvalidFragment> {
    match input.strip_prefix('#') {
        None => Ok(None),
        Some(fragment) => {
            let r = input.range_of_subset(fragment);
            let fragment = input.slice_into(r).try_into()?;
            Ok(Some(fragment))
        }
    }
}

fn split_at(s: SharedStr, index: usize) -> (SharedStr, SharedStr) {
    (s.slice_cloned(..index), s.slice_into(index..))
}