radix_engine_interface/object_modules/metadata/models/
url.rs1use crate::internal_prelude::*;
2
3use lazy_static::lazy_static;
4use regex::Regex;
5
6lazy_static! {
7 static ref URL_REGEX: Regex = Regex::new(
12 concat!(
13 "^",
15 "https?",
17 ":\\/\\/",
19 "(",
23 "((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))",
24 "|",
25 "((([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9]))",
26 ")",
27 "(:([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?",
30 "(\\/[-\\+a-zA-Z0-9\\(\\)\\[\\]@:%_.~&=]*)*",
37 "(\\?[-\\+a-zA-Z0-9\\(\\)\\[\\]@:%_.~&=\\/]*)?",
45 "(#[-\\+a-zA-Z0-9\\(\\)\\[\\]@:%_.~&=\\/]*)?",
53 "$"
55 )
56 ).unwrap();
57}
58
59#[cfg_attr(feature = "fuzzing", derive(::arbitrary::Arbitrary))]
60#[derive(
61 Debug, Clone, Eq, PartialEq, ManifestSbor, ScryptoCategorize, ScryptoEncode, ScryptoDecode,
62)]
63#[sbor(transparent)]
64pub struct UncheckedUrl(pub String);
65
66impl Describe<ScryptoCustomTypeKind> for UncheckedUrl {
67 const TYPE_ID: RustTypeId = RustTypeId::WellKnown(well_known_scrypto_custom_types::URL_TYPE);
68
69 fn type_data() -> ScryptoTypeData<RustTypeId> {
70 well_known_scrypto_custom_types::url_type_data()
71 }
72}
73
74impl UncheckedUrl {
75 pub fn of(value: impl AsRef<str>) -> Self {
76 Self(value.as_ref().to_owned())
77 }
78
79 pub fn as_str(&self) -> &str {
80 self.0.as_str()
81 }
82}
83
84pub struct CheckedUrl(String);
85
86impl CheckedUrl {
87 pub fn of(value: impl AsRef<str>) -> Option<Self> {
88 let s = value.as_ref();
89 if s.len() <= MAX_URL_LENGTH && URL_REGEX.is_match(s) {
90 Some(Self(s.to_owned()))
91 } else {
92 None
93 }
94 }
95
96 pub fn as_str(&self) -> &str {
97 self.0.as_str()
98 }
99}
100
101impl TryFrom<UncheckedUrl> for CheckedUrl {
102 type Error = ();
103
104 fn try_from(value: UncheckedUrl) -> Result<Self, Self::Error> {
105 CheckedUrl::of(value.as_str()).ok_or(())
106 }
107}
108
109impl From<CheckedUrl> for UncheckedUrl {
110 fn from(value: CheckedUrl) -> Self {
111 Self(value.0)
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_url() {
121 assert!(CheckedUrl::of("https://66.123.1.255:9999").is_some());
122 assert!(CheckedUrl::of("https://66.123.1.255:9999/hi").is_some());
123 assert!(CheckedUrl::of("https://www.google.com").is_some());
124 assert!(CheckedUrl::of("https://www.google.com/").is_some());
125 assert!(CheckedUrl::of("https://www.google.com/test/_abc/path").is_some());
126 assert!(CheckedUrl::of("https://www.google.com/test/_abc/path?").is_some());
127 assert!(CheckedUrl::of("https://www.google.com/test/_abc/path?abc=%12&def=test").is_some());
128 assert!(CheckedUrl::of("https://www.google.com/q?-+a-zA-Z0-9()[]@:%_.~&=/").is_some());
129 assert!(CheckedUrl::of("https://www.google.com/ /q").is_none());
130 assert!(CheckedUrl::of("https://username:password@www.google.com").is_none()); assert!(CheckedUrl::of("https://www.google.com/path?#").is_some());
132 assert!(CheckedUrl::of("https://www.google.com/path?#-+a-zA-Z0-9()[]@:%_.~&=/").is_some());
133 }
134}