Skip to content

Commit

Permalink
fix: universal link regex (#18020)
Browse files Browse the repository at this point in the history
Co-authored-by: Yevheniia Berdnyk <ie.berdnyk@gmail.com>
  • Loading branch information
yqrashawn and yevh-berdnyk authored Dec 20, 2023
1 parent 711e64b commit 4bc5efb
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 45 deletions.
13 changes: 6 additions & 7 deletions src/status_im/common/router.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,9 @@
"/" :group-chat}})

(def eip-extractor
{#{[:prefix "-" :address]
[:address]}
{#{[:prefix "-" :address] [:address]}
{#{["@" :chain-id] ""}
{#{["/" :function] ""}
:ethereum}}})
{#{["/" :function] ""} :ethereum}}})

(def routes
[""
Expand Down Expand Up @@ -82,6 +80,7 @@
;; fragment is the one after `#`, usually user-id, ens-name, community-id
fragment (parse-fragment uri)
ens? (utils.ens/is-valid-eth-name? fragment)
compressed-key? (validators/valid-compressed-key? fragment)

{:keys [handler route-params] :as parsed}
(assoc (bidi/match-route routes uri-without-equal-in-path)
Expand All @@ -91,7 +90,7 @@
ens?
(assoc-in [:route-params :ens-name] fragment)

(and (or (= handler :community) (= handler :community-chat)) fragment)
(and (or (= handler :community) (= handler :community-chat)) compressed-key?)
(assoc-in [:route-params :community-id] fragment)

(and equal-end-of-base64url (= handler :community) (:community-data route-params))
Expand All @@ -100,7 +99,7 @@
(and equal-end-of-base64url (= handler :community-chat) (:community-data route-params))
(update-in [:route-params :community-data] #(str % equal-end-of-base64url))

(and fragment (= handler :community-chat) (:community-data route-params))
(and compressed-key? (= handler :community-chat) (:community-data route-params))
(assoc-in [:route-params :community-id] fragment)

(and fragment
Expand All @@ -113,7 +112,7 @@
(and equal-end-of-base64url (= handler :user) (:user-data route-params))
(update-in [:route-params :user-data] #(str % equal-end-of-base64url))

(and (= handler :user) fragment)
(and (= handler :user) compressed-key?)
(assoc-in [:route-params :user-id] fragment))))

(defn match-contact-async
Expand Down
30 changes: 28 additions & 2 deletions src/status_im/constants.cljs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
(ns status-im.constants)
(ns status-im.constants
(:require
[clojure.string :as string]))

(def ^:const ms-in-bg-for-require-bioauth 5000)

Expand Down Expand Up @@ -182,12 +184,36 @@
(def ^:const method-id-approve "0x095ea7b3")
(def ^:const method-id-approve-and-call "0xcae9ca51")

(def regx-string-universal-link-encoded-data-base64
"(?:[A-Za-z0-9+/_-]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?")
(def regx-universal-link-encoded-data-base64
(str "^" regx-string-universal-link-encoded-data-base64 "$"))
(def regx-string-any-ascii "[\\x00-\\x7F]")
(def regx-string-compressed-key "zQ3[0-9A-HJ-NP-Za-km-z]{46}")
(def regx-string-public-key "0x04[0-9a-f]{128}")
(def regx-compressed-key (re-pattern (str "^" regx-string-compressed-key "$")))
(def regx-public-key (re-pattern (str "^" regx-string-public-key "$")))
(def regx-emoji
#"^((?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC69\uDC6E\uDC70-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD26\uDD30-\uDD39\uDD3D\uDD3E\uDDD1-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])?|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDEEB\uDEEC\uDEF4-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267B\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])\uFE0F|[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF])+$")
(def regx-bold #"\*[^*]+\*")
(def regx-italic #"~[^~]+~")
(def regx-backquote #"`[^`]+`")
(def regx-universal-link #"((^https?://status.app/)|(^status-app://))[\x00-\x7F]+$")
(def regx-string-universal-link
(str "((^https?://status.app/)|(^status-app://))"
"("
(string/join
"|"
[(str "u/" regx-string-universal-link-encoded-data-base64 "#" regx-string-compressed-key)
(str "u#" regx-string-compressed-key)
(str "c/" regx-string-universal-link-encoded-data-base64 "#" regx-string-compressed-key)
(str "c#" regx-string-compressed-key)
(str "cc/" regx-string-universal-link-encoded-data-base64 "#" regx-string-compressed-key)
(str "p/" regx-string-any-ascii "+")
(str "cr/" regx-string-any-ascii "+")
(str "g/" regx-string-any-ascii "+")
(str "wallet/" regx-string-any-ascii "+")])
")$"))
(def regx-universal-link (re-pattern regx-string-universal-link))
(def regx-community-universal-link #"((^https?://status.app/)|(^status-app://))c/([\x00-\x7F]+)$")
(def regx-deep-link #"((^ethereum:.*)|(^status-app://[\x00-\x7F]+$))")
(def regx-ens #"^(?=.{5,255}$)([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$")
Expand Down
79 changes: 48 additions & 31 deletions src/utils/universal_links_test.cljs
Original file line number Diff line number Diff line change
@@ -1,36 +1,53 @@
(ns utils.universal-links-test
(:require
[cljs.test :refer-macros [deftest is testing]]
[cljs.test :refer-macros [deftest testing are]]
matcher-combinators.test
[utils.universal-links :as links]))

(deftest universal-link-test
(testing "status-app://blah"
(testing "it returns true"
(is (links/universal-link? "status-app://blah"))))
(testing "status-app://blah"
(testing "it returns true"
(is (links/deep-link? "status-app://blah"))))
(testing "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
(testing "it returns true"
(is (links/deep-link? "ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"))))
(testing "http://status.app/blah"
(testing "it returns true"
(is (links/universal-link? "http://status.app/blah"))))
(testing "https://status.app/blah"
(testing "it returns true"
(is (links/universal-link? "https://status.app/blah"))))
(testing "unicode characters"
(testing "it returns false"
(is (not (links/universal-link? "https://status.app/browse/www.аррӏе.com")))))
(testing "not-status-app://blah"
(testing "it returns false"
(is (not (links/universal-link? "https://not.status.im/blah")))))
(testing "http://not.status.im/blah"
(testing "it returns false"
(is (not (links/universal-link? "https://not.status.im/blah")))))
(testing "https://not.status.im/blah"
(testing "it returns false"
(is (not (links/universal-link? "https://not.status.im/blah")))))
(testing "http://status.app/blah"
(testing "it returns false"
(is (not (links/deep-link? "http://status.app/blah"))))))
(testing "universal-link?"
(are [l rst] (match? (links/universal-link? l) rst)
"status-app://blah"
false
"http://status.app/blah"
false
"http://status.app/c#zQ3shPyZJnxZK4Bwyx9QsaksNKDYTPmpwPvGSjMYVHoXHeEgB"
true
"http://status.app/u#zQ3shPyZJnxZK4Bwyx9QsaksNKDYTPmpwPvGSjMYVHoXHeEgB"
true
"https://status.app/u/Ow==#zQ3shsKnV5HJMWJR61c6dssWzHszdbLfBoMF1gcLtSQAYdw2d"
true
"https://status.app/c/Ow==#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
true
"https://status.app/c/Ow==#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc111"
false
"https://status.app/u/G10A4B0JdgwyRww90WXtnP1oNH1ZLQNM0yX0Ja9YyAMjrqSZIYINOHCbFhrnKRAcPGStPxCMJDSZlGCKzmZrJcimHY8BbcXlORrElv_BbQEegnMDPx1g9C5VVNl0fE4y#zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"
true
"https://status.app/c/G00AAGS9TbI9mSR-ZNmFrhRjNuEeXAAbcAIUaLLJyjMOG3ACJQ12oIHD78QhzO9s_T5bUeU7rnATWJg3mGgTUemrAg==#zQ3shYf5SquxkiY3FmCW6Nz2wuFWFcM6JEdUD62ApjAvE5YPv"
true
"http://status.app/c#zQ3shPyZJnxZK4Bwyx9QsaksNKDYTPmpwPvGSjMYVHoXHeEgBhttp://status.app/c#zQ3shPyZJnxZK4Bwyx9QsaksNKDYTPmpwPvGSjMYVHoXHeEgB"
false
"http://status.app/u#zQ3shPyZJnxZK4Bwyx9QsaksNKDYTPmpwPvGSjMYVHoXHeEgBhttp://status.app/u#zQ3shPyZJnxZK4Bwyx9QsaksNKDYTPmpwPvGSjMYVHoXHeEgB"
false
"https://status.app/u/Ow==#zQ3shsKnV5HJMWJR61c6dssWzHszdbLfBoMF1gcLtSQAYdw2dhttps://status.app/u/Ow==#zQ3shsKnV5HJMWJR61c6dssWzHszdbLfBoMF1gcLtSQAYdw2d"
false
"https://status.app/c/Ow==#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11https://status.app/c/Ow==#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
false
"https://status.app/u/G10A4B0JdgwyRww90WXtnP1oNH1ZLQNM0yX0Ja9YyAMjrqSZIYINOHCbFhrnKRAcPGStPxCMJDSZlGCKzmZrJcimHY8BbcXlORrElv_BbQEegnMDPx1g9C5VVNl0fE4y#zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSjhttps://status.app/u/G10A4B0JdgwyRww90WXtnP1oNH1ZLQNM0yX0Ja9YyAMjrqSZIYINOHCbFhrnKRAcPGStPxCMJDSZlGCKzmZrJcimHY8BbcXlORrElv_BbQEegnMDPx1g9C5VVNl0fE4y#zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"
false
"https://status.app/c/Ow==#zQ3shbmfT3hvh4mKa1v6uAjjyztQEroh8Mfn6Ckegjd7LT3XKhttps://status.app/c#zQ3shbmfT3hvh4mKa1v6uAjjyztQEroh8Mfn6Ckegjd7LT3XK"
false
"https://status.app/blah"
false
"https://status.app/browse/www.аррӏе.com"
false
"https://not.status.im/blah"
false
"http://not.status.im/blah"
false))

(testing "deep-link?"
(are [l rst] (match? (links/deep-link? l) rst)
"status-app://blah" true
"http://status.app/blah" false
"ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7" true)))
9 changes: 5 additions & 4 deletions src/utils/validators.cljs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
(ns utils.validators)
(ns utils.validators
(:require
[status-im.constants :as constants]))

(defn valid-public-key?
[s]
(and (string? s)
(not-empty s)
(boolean (re-matches #"0x04[0-9a-f]{128}" s))))
(boolean (re-matches constants/regx-public-key s))))

(defn valid-compressed-key?
[s]
(and (string? s)
(not-empty s)
(boolean (re-matches #"^zQ3[0-9A-HJ-NP-Za-km-z]{46}" s))))

(boolean (re-matches constants/regx-compressed-key s))))
4 changes: 3 additions & 1 deletion test/appium/tests/critical/test_deep_and_universal_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def test_links_open_universal_links_from_chat(self):
"https://status.app/u#zQ3shVVxZMwLVEQvuu1KF6h4D2mzVyCC4F4mHLZm5dz5XU1aa"]

for url in profile_urls:
self.channel.chat_message_input.clear()
self.channel.send_message(url)
self.channel.chat_element_by_text(url).click_on_link_inside_message_body()
if not self.channel.profile_add_to_contacts_button.is_element_displayed(
Expand All @@ -42,10 +43,11 @@ def test_links_open_universal_links_from_chat(self):

closed_community_urls = [
"https://status.app/c/G8EAAGTiXKuwNbVVAu0GNLD-XzX4oz_E8oC1-7qSLikaTnCuG9Ag13ZgQKrMd8En9Qcpuaj3Qx3mfZ1atZzH8Zw-x_sFJ_MDv0P_7YfqoV-pNr3V4dsza-jVk41GaCGWasJb92Oer8qggaoNWf0tYCgSH19VonXciKPUz3ITdgke#zQ3shbmfT3hvh4mKa1v6uAjjyztQEroh8Mfn6Ckegjd7LT3XK",
"https://status.app/c/Ow==#zQ3shbmfT3hvh4mKa1v6uAjjyztQEroh8Mfn6Ckegjd7LT3XK"
"https://status.app/c/Ow==#zQ3shbmfT3hvh4mKa1v6uAjjyztQEroh8Mfn6Ckegjd7LT3XK",
"https://status.app/c#zQ3shbmfT3hvh4mKa1v6uAjjyztQEroh8Mfn6Ckegjd7LT3XK",
]
for url in closed_community_urls:
self.channel.chat_message_input.clear()
self.channel.send_message(url)
self.channel.chat_element_by_text(url).click_on_link_inside_message_body()
if not self.channel.element_by_translation_id(
Expand Down

0 comments on commit 4bc5efb

Please sign in to comment.