#![doc(html_no_source)]
extern crate proc_macro;
#[macro_use]
extern crate lazy_static;
use crate::proc_macro::TokenStream;
use proc_macro_hack::proc_macro_hack;
use quote::quote;
use regex::Regex;
use syn;
mod unescape_uri;
use unescape_uri::UnescapeUri;
lazy_static! {
pub(crate) static ref RFC3986_APPENDIX_B: Regex = Regex::new(r#"^(([^:/?#%]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$"#)
.expect("RFC3986_APPENDIX_B");
pub(crate) static ref URI_AUTHORITY_VS_REST: Regex = Regex::new(r#"^(([^:/?#]+:)(//[^/?#]*)?)?(([^#]*)(#.*)?)$"#)
.expect("URI_AUTHORITY_VS_REST");
pub(crate) static ref URI_CHECK_SCHEME: Regex = Regex::new(r#"^[A-Za-z][-+.A-Za-z0-9]*$"#)
.expect("URI_CHECK_SCHEME");
pub(crate) static ref URI_AUTHORITY: Regex = Regex::new(r#"^(([^@/?#]+)@)?([^\[\]:]+|\[[^\]]+\])(:([0-9]+))?$"#)
.expect("URI_AUTHORITY");
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum Error {
#[allow(unused)]
EncodingError,
MalformedStructure,
MalformedScheme,
Degenerate,
}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Error::EncodingError => f.write_str("Encoding Error"),
Error::MalformedStructure => f.write_str("The structure of the URI is not recognized."),
Error::MalformedScheme => f.write_str("The scheme of the URI is malformed."),
Error::Degenerate => {
f.write_str("This relative reference could be confused with a URI.")
}
}
}
}
fn assert_uri_str(uri_str: &str) -> Result<(), Error> {
let captures = RFC3986_APPENDIX_B
.captures(uri_str)
.ok_or(Error::MalformedStructure)?;
let has_scheme = captures.get(2).is_some();
let has_authority = captures.get(4).is_some();
if !has_scheme && !has_authority {
return Err(Error::MalformedStructure);
}
if let Some(scheme) = captures.get(2) {
URI_CHECK_SCHEME
.captures(scheme.as_str())
.ok_or(Error::MalformedScheme)?;
}
Ok(())
}
fn assert_rel_ref_str(uri_str: &str) -> Result<(), Error> {
assert_uri_str(uri_str)
.err()
.map(|_| ())
.ok_or(Error::Degenerate)?;
assert_uri_ref_str(uri_str)
}
fn assert_uri_ref_str(uri_str: &str) -> Result<(), Error> {
RFC3986_APPENDIX_B
.captures(uri_str)
.ok_or(Error::MalformedStructure)?;
Ok(())
}
fn string_literal_from_token_stream(input: TokenStream) -> String {
use syn::LitStr;
if let Some(nom) = syn::parse::<LitStr>(input.clone()).ok() {
return nom.value();
}
panic!("Expected string literal, got {:?}", input);
}
#[proc_macro_hack]
pub fn assert_uri_literal(input: TokenStream) -> TokenStream {
let uri_str = string_literal_from_token_stream(input);
if let Some(err_pos) = UnescapeUri::new(&uri_str).first_error() {
panic!("Malformed percent encoding at index {}", err_pos);
}
if let Err(err) = assert_uri_str(&uri_str) {
panic!("Malformed uri literal; {:?}", err);
}
let gen = quote! { () };
gen.into()
}
#[proc_macro_hack]
pub fn assert_rel_ref_literal(input: TokenStream) -> TokenStream {
let uri_str = string_literal_from_token_stream(input);
if let Some(err_pos) = UnescapeUri::new(&uri_str).first_error() {
panic!("Malformed percent encoding at index {}", err_pos);
}
if let Err(err) = assert_rel_ref_str(&uri_str) {
panic!("Malformed rel_ref literal; {:?}", err);
}
let gen = quote! { () };
gen.into()
}
#[proc_macro_hack]
pub fn assert_uri_ref_literal(input: TokenStream) -> TokenStream {
let uri_str = string_literal_from_token_stream(input);
if let Some(err_pos) = UnescapeUri::new(&uri_str).first_error() {
panic!("Malformed percent encoding at index {}", err_pos);
}
if let Err(err) = assert_uri_ref_str(&uri_str) {
panic!("Malformed uri_ref literal; {:?}", err);
}
let gen = quote! { () };
gen.into()
}
#[cfg(test)]
mod test {
use super::*;
fn check_uri_str(uri_str: &str) -> Result<(), Error> {
if let Some(_) = UnescapeUri::new(uri_str).first_error() {
return Err(Error::EncodingError);
}
assert_uri_str(uri_str)
}
fn check_rel_ref_str(uri_str: &str) -> Result<(), Error> {
if let Some(_) = UnescapeUri::new(uri_str).first_error() {
return Err(Error::EncodingError);
}
assert_rel_ref_str(uri_str)
}
fn check_uri_ref_str(uri_str: &str) -> Result<(), Error> {
if let Some(_) = UnescapeUri::new(uri_str).first_error() {
return Err(Error::EncodingError);
}
assert_uri_ref_str(uri_str)
}
#[test]
fn test_uri() {
assert_eq!(check_uri_str("g:a/b/c"), Ok(()));
assert_eq!(check_uri_str("g+z://a/b/c"), Ok(()));
assert_eq!(check_uri_str("//a/b/c"), Ok(()));
assert_eq!(check_uri_str("a/b/c"), Err(Error::MalformedStructure));
assert_eq!(check_uri_str("g$:a/b/c"), Err(Error::MalformedScheme));
assert_eq!(check_uri_str("g%:a/b/c"), Err(Error::EncodingError));
assert_eq!(check_uri_str("g:%aa/b/c"), Err(Error::EncodingError));
assert_eq!(check_uri_str("g:%00/b/c"), Err(Error::EncodingError));
}
#[test]
fn test_rel_ref() {
assert_eq!(check_rel_ref_str("/a/b/c"), Ok(()));
assert_eq!(check_rel_ref_str("a/b/c"), Ok(()));
assert_eq!(check_rel_ref_str("g:a/b/c"), Err(Error::Degenerate));
assert_eq!(check_rel_ref_str("g%3Aa/b/c"), Ok(()));
assert_eq!(check_rel_ref_str("./g:a/b/c"), Ok(()));
assert_eq!(check_rel_ref_str("//a/b/c"), Err(Error::Degenerate));
assert_eq!(check_rel_ref_str("/.//a/b/c"), Ok(()));
assert_eq!(check_rel_ref_str("g$:a/b/c"), Ok(()));
assert_eq!(check_rel_ref_str("g%:a/b/c"), Err(Error::EncodingError));
assert_eq!(check_rel_ref_str("g:%aa/b/c"), Err(Error::EncodingError));
assert_eq!(check_rel_ref_str("g:%00/b/c"), Err(Error::EncodingError));
assert_eq!(check_rel_ref_str("%a/b/c"), Err(Error::EncodingError));
assert_eq!(check_rel_ref_str("%aa/b/c"), Err(Error::EncodingError));
assert_eq!(check_rel_ref_str("%00/b/c"), Err(Error::EncodingError));
assert_eq!(check_rel_ref_str("a/ /c"), Err(Error::EncodingError));
assert_eq!(check_rel_ref_str("a/\n/c"), Err(Error::EncodingError));
}
#[test]
fn test_uri_ref() {
assert_eq!(check_uri_ref_str("/a/b/c"), Ok(()));
assert_eq!(check_uri_ref_str("a/b/c"), Ok(()));
assert_eq!(check_uri_ref_str("g:a/b/c"), Ok(()));
assert_eq!(check_uri_ref_str("g%3Aa/b/c"), Ok(()));
assert_eq!(check_uri_ref_str("./g:a/b/c"), Ok(()));
assert_eq!(check_uri_ref_str("//a/b/c"), Ok(()));
assert_eq!(check_uri_ref_str("/.//a/b/c"), Ok(()));
assert_eq!(check_uri_ref_str("g$:a/b/c"), Ok(()));
assert_eq!(check_uri_ref_str("g%:a/b/c"), Err(Error::EncodingError));
assert_eq!(check_uri_ref_str("g:%aa/b/c"), Err(Error::EncodingError));
assert_eq!(check_uri_ref_str("g:%00/b/c"), Err(Error::EncodingError));
assert_eq!(check_uri_ref_str("%a/b/c"), Err(Error::EncodingError));
assert_eq!(check_uri_ref_str("%aa/b/c"), Err(Error::EncodingError));
assert_eq!(check_uri_ref_str("%00/b/c"), Err(Error::EncodingError));
assert_eq!(check_uri_ref_str("a/ /c"), Err(Error::EncodingError));
assert_eq!(check_uri_ref_str("a/\n/c"), Err(Error::EncodingError));
}
}