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