radix_engine_interface/object_modules/metadata/models/
origin.rs

1use crate::internal_prelude::*;
2
3use lazy_static::lazy_static;
4use regex::Regex;
5
6lazy_static! {
7    /// This regular expressions only cover the most commonly used types of Origins.
8    ///
9    /// Based on https://en.wikipedia.org/wiki/URL#/media/File:URI_syntax_diagram.svg
10    ///
11    static ref ORIGIN_REGEX: Regex = Regex::new(
12        concat!(
13            // 1. Start
14            "^",
15            // 2. Schema, http or https only
16            "https?",
17            // 3. ://
18            ":\\/\\/",
19            // 4. Userinfo, not allowed
20            // 5. Host, ip address or host name
21            //    From https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
22            "(",
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            // 6. Port number, optional
28            //    From https://stackoverflow.com/questions/12968093/regex-to-validate-port-number
29            "(:([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            // 7. End
31            "$"
32        )
33    ).unwrap();
34}
35
36#[cfg_attr(feature = "fuzzing", derive(::arbitrary::Arbitrary))]
37#[derive(
38    Debug, Clone, Eq, PartialEq, ManifestSbor, ScryptoCategorize, ScryptoEncode, ScryptoDecode,
39)]
40#[sbor(transparent)]
41pub struct UncheckedOrigin(pub String);
42
43impl Describe<ScryptoCustomTypeKind> for UncheckedOrigin {
44    const TYPE_ID: RustTypeId = RustTypeId::WellKnown(well_known_scrypto_custom_types::ORIGIN_TYPE);
45
46    fn type_data() -> ScryptoTypeData<RustTypeId> {
47        well_known_scrypto_custom_types::origin_type_data()
48    }
49}
50
51impl UncheckedOrigin {
52    pub fn of(value: impl AsRef<str>) -> Self {
53        Self(value.as_ref().to_owned())
54    }
55
56    pub fn as_str(&self) -> &str {
57        self.0.as_str()
58    }
59}
60
61pub struct CheckedOrigin(String);
62
63impl CheckedOrigin {
64    pub fn of(value: impl AsRef<str>) -> Option<Self> {
65        let s = value.as_ref();
66        if s.len() <= MAX_ORIGIN_LENGTH && ORIGIN_REGEX.is_match(s) {
67            Some(Self(s.to_owned()))
68        } else {
69            None
70        }
71    }
72
73    pub fn as_str(&self) -> &str {
74        self.0.as_str()
75    }
76}
77
78impl TryFrom<UncheckedOrigin> for CheckedOrigin {
79    type Error = ();
80
81    fn try_from(value: UncheckedOrigin) -> Result<Self, Self::Error> {
82        CheckedOrigin::of(value.as_str()).ok_or(())
83    }
84}
85
86impl From<CheckedOrigin> for UncheckedOrigin {
87    fn from(value: CheckedOrigin) -> Self {
88        Self(value.0)
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_origin() {
98        assert!(CheckedOrigin::of("https://www.google.com").is_some());
99        assert!(CheckedOrigin::of("http://gooooooooooooooooooooooooooooooogle.com:8888").is_some());
100        assert!(CheckedOrigin::of("https://66.123.1.255:9").is_some());
101        assert!(CheckedOrigin::of("https://www.google.com/").is_none());
102    }
103}