From cf58a84838da990e19e8b695aa0f77846681d4b0 Mon Sep 17 00:00:00 2001 From: Roman Volosovskyi Date: Tue, 6 Oct 2020 17:32:32 +0300 Subject: [PATCH] [#11192] Prevent replacing of mention with pubkey inside markdown --- src/status_im/chat/models/mentions.cljs | 176 ++++++++++++++++++- src/status_im/chat/models/mentions_test.cljs | 148 +++++++++++++++- 2 files changed, 314 insertions(+), 10 deletions(-) diff --git a/src/status_im/chat/models/mentions.cljs b/src/status_im/chat/models/mentions.cljs index 1a52ed8b7c..687d562502 100644 --- a/src/status_im/chat/models/mentions.cljs +++ b/src/status_im/chat/models/mentions.cljs @@ -12,6 +12,163 @@ (def at-sign "@") +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn re-pos [re s] + (loop [res [] s s last-idx 0] + (if-let [m (.exec re s)] + (let [new-idx (.-index m) + idx (+ last-idx new-idx) + c (get m 0)] + (recur (conj res [idx c]) (subs s (inc new-idx)) (inc idx))) + res))) + +(defn check-style-tag [text idxs idx] + (let [[pos c] (get idxs idx) + [pos2 c2] (get idxs (inc idx)) + [pos3 c3] (get idxs (+ 2 idx)) + prev-c (get text (dec pos)) + len (cond + (and (= c c2 c3) + (= pos (dec pos2) (- pos3 2))) + 3 + (and (= c c2) + (= pos (dec pos2))) + 2 + :else 1) + next-idx (inc (first (get idxs (+ idx (dec len))))) + next-c (get text next-idx) + can-be-end? (if (= 1 len) + (and prev-c + (not (string/blank? prev-c)) + (or (nil? next-c) + (string/blank? next-c))) + (and prev-c + (not (string/blank? prev-c)))) + can-be-start? (and next-c + (not (string/blank? next-c)))] + [len can-be-start? can-be-end?])) + +(defn clear-pending-at-signs + [data from] + (let [{:keys [pending]} (get data at-sign) + new-idxs (filter (partial > from) pending)] + (-> data + (update at-sign dissoc :pending) + (update-in [at-sign :checked] + (fn [checked] + ;; NOTE(rasom): there might be stack overflow without doall + (doall + ((fnil concat []) checked new-idxs))))))) + +(defn apply-style-tag [data idx pos c len start? end?] + (let [was-started? (get data c) + tripple-tilde? (and (= "~" c) (= 3 len))] + (cond + (and was-started? end?) + (let [old-len (:len was-started?) + tag-len (cond + (and tripple-tilde? + (= 3 old-len)) + 2 + + (>= old-len len) + len + + :else + old-len) + old-idx (:idx was-started?)] + {:data (-> data + (dissoc c) + (clear-pending-at-signs old-idx)) + :next-idx (+ idx tag-len)}) + + start? + {:data (-> data + (assoc c {:len len + :idx pos}) + (clear-pending-at-signs pos)) + :next-idx (+ idx len)} + + :else + {:data data + :next-idx (+ idx len)}))) + +(defn code-tag-len [idxs idx] + (let [[pos c] (get idxs idx) + [pos2 c2] (get idxs (inc idx)) + [pos3 c3] (get idxs (+ 2 idx))] + (cond + (and (= c c2 c3) + (= pos (dec pos2) (- pos3 2))) + 3 + (and (= c c2) + (= pos (dec pos2))) + 2 + :else 1))) + +(defn get-at-signs + ([text] + (let [idxs (re-pos #"[@~\\*_\n>`]{1}" text)] + (loop [data nil + idx 0] + (let [quote-started? (get data ">") + [pos c] (get idxs idx) + styling-tag? (get #{"*" "_" "~"} c) + code-tag? (= "`" c) + quote? (= ">" c) + at-sign? (= at-sign c) + newline? (= "\n" c)] + (if (nil? c) + (let [{:keys [checked pending]} (get data at-sign)] + (concat checked pending)) + (cond + newline? + (let [prev-newline (first (get data :newline))] + (recur + (cond-> (update data :newline (fnil conj '()) pos) + (and quote-started? + prev-newline + (string/blank? (subs text prev-newline (dec pos)))) + (dissoc ">")) + (inc idx))) + + quote-started? + (recur data (inc idx)) + + quote? + (let [prev-newlines (take 2 (get data :newline))] + (if (or (zero? pos) + (and (= 1 (count prev-newlines)) + (string/blank? (subs text 0 (dec pos)))) + (and (= 2 (count prev-newlines)) + (string/blank? (subs text (first prev-newlines) (dec pos))))) + (recur (-> data + (dissoc :newline "*" "_" "~" "`") + (assoc ">" {:idx pos})) (inc idx)) + (recur data (inc idx)))) + + at-sign? + (recur (update-in data [at-sign :pending] (fnil conj []) pos) + (inc idx)) + + code-tag? + (let [len (code-tag-len idxs idx) + {:keys [data next-idx]} + (apply-style-tag data idx pos c len true true)] + (recur data next-idx)) + + styling-tag? + (let [[len can-be-start? can-be-end?] + (check-style-tag text idxs idx) + {:keys [data next-idx]} + (apply-style-tag data idx pos c len can-be-start? can-be-end?)] + (recur data next-idx)) + + :else (recur data (inc idx))))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn get-mentionable-users [{{:keys [current-chat-id] :contacts/keys [contacts] :as db} :db}] @@ -144,11 +301,13 @@ (defn replace-mentions ([text users-fn] - (replace-mentions text users-fn 0)) - ([text users-fn idx] - (if (string/blank? text) + (let [idxs (get-at-signs text)] + (replace-mentions text users-fn idxs 0))) + ([text users-fn idxs diff] + (if (or (string/blank? text) + (empty? idxs)) text - (let [mention-key-idx (string/index-of text at-sign idx)] + (let [mention-key-idx (- (first idxs) diff)] (if-not mention-key-idx text (let [users (users-fn)] @@ -157,14 +316,14 @@ (let [{:keys [public-key match]} (match-mention text users mention-key-idx)] (if-not match - (recur text (fn [] users) (inc mention-key-idx)) + (recur text (fn [] users) (rest idxs) diff) (let [new-text (string/join [(subs text 0 (inc mention-key-idx)) public-key (subs text (+ (inc mention-key-idx) - (count match)))]) - mention-end (+ (inc mention-key-idx) (count public-key))] - (recur new-text (fn [] users) mention-end))))))))))) + (count match)))])] + (recur new-text (fn [] users) (rest idxs) + (+ diff (- (count text) (count new-text)))))))))))))) (defn check-mentions [cofx text] (replace-mentions text #(get-mentionable-users cofx))) @@ -486,4 +645,3 @@ (update user :searchable-phrases (fnil concat []) new-words)))) user [alias name nickname])) - diff --git a/src/status_im/chat/models/mentions_test.cljs b/src/status_im/chat/models/mentions_test.cljs index 95babceea0..430bde6eda 100644 --- a/src/status_im/chat/models/mentions_test.cljs +++ b/src/status_im/chat/models/mentions_test.cljs @@ -105,4 +105,150 @@ exprected-result (string/join " " (repeat 1000 "@0xpk1 @0xpk2"))] - (test/is (= exprected-result result)))))) + (test/is (= exprected-result result)))) + (test/testing "markdown" + (test/testing "single * case 1" + (let [text "*@user2*" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "single * case 2" + (let [text "*@user2 *" + result (mentions/replace-mentions text users)] + (test/is (= result "*@0xpk2 *") (pr-str text)))) + + (test/testing "single * case 3" + (let [text "a*@user2*" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "single * case 4" + (let [text "*@user2 foo*foo" + result (mentions/replace-mentions text users)] + (test/is (= result "*@0xpk2 foo*foo") (pr-str text)))) + + (test/testing "single * case 5" + (let [text "a *@user2*" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "single * case 6" + (let [text "*@user2 foo*" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "single * case 7" + (let [text "@user2 *@user2 foo* @user2" + result (mentions/replace-mentions text users)] + (test/is (= result "@0xpk2 *@user2 foo* @0xpk2") (pr-str text)))) + + (test/testing "single * case 8" + (let [text "*@user2 foo**@user2 foo*" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "single * case 9" + (let [text "*@user2 foo***@user2 foo* @user2" + result (mentions/replace-mentions text users)] + (test/is (= result "*@user2 foo***@user2 foo* @0xpk2") (pr-str text)))) + + (test/testing "double * case 1" + (let [text "**@user2**" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "double * case 2" + (let [text "**@user2 **" + result (mentions/replace-mentions text users)] + (test/is (= result "**@0xpk2 **") (pr-str text)))) + + (test/testing "double * case 3" + (let [text "a**@user2**" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "double * case 4" + (let [text "**@user2 foo**foo" + result (mentions/replace-mentions text users)] + (test/is (= result "**@user2 foo**foo") (pr-str text)))) + + (test/testing "double * case 5" + (let [text "a **@user2**" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "double * case 6" + (let [text "**@user2 foo**" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "double * case 7" + (let [text "@user2 **@user2 foo** @user2" + result (mentions/replace-mentions text users)] + (test/is (= result "@0xpk2 **@user2 foo** @0xpk2") (pr-str text)))) + + (test/testing "double * case 8" + (let [text "**@user2 foo****@user2 foo**" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "double * case 9" + (let [text "**@user2 foo*****@user2 foo** @user2" + result (mentions/replace-mentions text users)] + (test/is (= result "**@user2 foo*****@user2 foo** @0xpk2") (pr-str text)))) + + (test/testing "tripple * case 1" + (let [text "***@user2 foo***@user2 foo*" + result (mentions/replace-mentions text users)] + (test/is (= result "***@user2 foo***@0xpk2 foo*") (pr-str text)))) + + (test/testing "tripple ~ case 1" + (let [text "~~~@user2 foo~~~@user2 foo~" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "quote case 1" + (let [text ">@user2" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "quote case 2" + (let [text "\n>@user2" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "quote case 3" + (let [text "\n> @user2 \n \n @user2" + result (mentions/replace-mentions text users)] + (test/is (= result "\n> @user2 \n \n @0xpk2") (pr-str text)))) + + (test/testing "quote case 4" + (let [text ">@user2\n\n>@user2" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "quote case 5" + (let [text "***hey\n\n>@user2\n\n@user2 foo***" + result (mentions/replace-mentions text users)] + (test/is (= result "***hey\n\n>@user2\n\n@0xpk2 foo***") + (pr-str text)))) + + (test/testing "code case 1" + (let [text "` @user2 `" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "code case 2" + (let [text "` @user2 `" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "code case 3" + (let [text "``` @user2 ```" + result (mentions/replace-mentions text users)] + (test/is (= result text) (pr-str text)))) + + (test/testing "code case 4" + (let [text "` ` @user2 ``" + result (mentions/replace-mentions text users)] + (test/is (= result "` ` @0xpk2 ``") (pr-str text)))))))