@@ -46,6 +46,49 @@ require_relative 'lib/registration_repo'
Sentry.init
+BADWORD_LIST = [
+ "marijuana",
+ "psilocybin",
+ "cannabis",
+ "cocaine",
+ "heroin",
+ "meth",
+ "methamphetamine",
+ "methamphetamines",
+ "cigarette",
+ "tobacco",
+ "cbd",
+ "thc",
+ "morphine",
+ "incall",
+ "in-call",
+ "outcall",
+ "out-call",
+ "shrooms",
+ "lsd",
+ "kratom",
+ "mdma",
+ "addy",
+ "xanz",
+ "cialis",
+ "viagra",
+ "bbfs",
+ "fentanyl",
+ "opium",
+ "golden teacher",
+ "bbbj",
+ "canna",
+ "fuck",
+ "xanax",
+ "zarareturns",
+ "zarareturns.com",
+ "plantation",
+].freeze
+
+BADWORDS = Regexp.union(
+ BADWORD_LIST.map { |w| /\b#{Regexp.escape(w)}\b/ }
+)
+
# List of supported MIME types from Bandwidth - https://support.bandwidth.com/hc/en-us/articles/360014128994-What-MMS-file-types-are-supported-
MMS_MIME_TYPES = [
"application/json",
@@ -390,6 +433,14 @@ module SGXbwmsgsv2
)
end
+ if body.downcase.match?(BADWORDS)
+ return EMPromise.reject([
+ :wait,
+ 'recipient-unavailable',
+ 'Single message blocked by carrier content policy, see https://blog.jmp.chat/b/sms-censorship for details'
+ ])
+ end
+
segment_size = body.ascii_only? ? 160 : 70
if !murl && ENV["MMS_PATH"] && body.length > segment_size*3
file = Multibases.pack(
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require_relative "../../sgx-bwmsgsv2"
+require "rantly/minitest_extensions"
+require_relative "rantly_extensions/data_extensions"
+
+class BadwordsPropertyTest < Minitest::Test
+ BW_MESSAGES_URL =
+ "https://messaging.bandwidth.com/api/v2/users/account/messages"
+
+ def setup
+ reset_stanzas!
+ reset_redis!
+ end
+
+ def test_message_containing_badword_is_rejected
+ property_of {
+ bad_word = choose(*BADWORD_LIST)
+ cased = choose(bad_word.downcase, bad_word.upcase, bad_word.capitalize)
+
+ prefix = array(range(0, 4)) { sized(range(3, 8)) { string(:alnum) } }
+ suffix = array(range(0, 4)) { sized(range(3, 8)) { string(:alnum) } }
+ body = (prefix + [cased] + suffix).join(" ")
+
+ dest = nanpa_phone
+ [body, dest]
+ }.check { |body, dest|
+ reset_stanzas!
+ reset_redis!
+
+ stub_request(:post, BW_MESSAGES_URL).to_return(
+ status: 201,
+ body: JSON.dump(id: "bw-msg-stub")
+ )
+
+ m = Blather::Stanza::Message.new("#{dest}@component", body)
+ m.from = "test@example.com"
+ process_stanza(m)
+
+ assert_equal 1, written.length,
+ "Expected exactly one error stanza for body: #{body.inspect}"
+
+ stanza = Blather::XMPPNode.parse(written.first.to_xml)
+ assert stanza.error?,
+ "Expected error stanza for body: #{body.inspect}"
+
+ error = stanza.find_first("error")
+ assert_equal "wait", error["type"],
+ "Expected error type 'wait' for body: #{body.inspect}"
+ assert_equal "recipient-unavailable", xmpp_error_name(error),
+ "Expected 'recipient-unavailable' for body: #{body.inspect}"
+ }
+ end
+ em :test_message_containing_badword_is_rejected
+
+ def test_message_without_badwords_is_not_rejected_as_unavailable
+ property_of {
+ words = array(range(1, 6)) { sized(range(3, 10)) { string(:alnum) } }
+ guard(words.none? { |w| BADWORD_LIST.include?(w.downcase) })
+ body = words.join(" ")
+
+ dest = nanpa_phone
+ [body, dest]
+ }.check { |body, dest|
+ reset_stanzas!
+ reset_redis!
+
+ stub_request(:post, BW_MESSAGES_URL).to_return(
+ status: 201,
+ body: JSON.dump(id: "bw-msg-stub")
+ )
+
+ m = Blather::Stanza::Message.new("#{dest}@component", body)
+ m.from = "test@example.com"
+ process_stanza(m)
+
+ written.each do |response|
+ stanza = Blather::XMPPNode.parse(response.to_xml)
+ next unless stanza.error?
+
+ error = stanza.find_first("error")
+ msg = "Clean message rejected as " \
+ "recipient-unavailable: #{body.inspect}"
+ refute_equal "recipient-unavailable",
+ xmpp_error_name(error), msg
+ end
+ }
+ end
+ em :test_message_without_badwords_is_not_rejected_as_unavailable
+end