From ac6daf3fb8b32f4dbc99239b6e9934391f15399e Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Tue, 17 Mar 2026 17:43:32 -0400 Subject: [PATCH] dont allow outbound mms outside of NANP --- Gemfile | 1 + sgx-bwmsgsv2.rb | 10 +- .../rantly_extensions/data_extensions.rb | 54 +++++++++++ test/property/test_non_nanp_oob.rb | 92 +++++++++++++++++++ 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 test/property/test_non_nanp_oob.rb diff --git a/Gemfile b/Gemfile index bb857786caf947d415ef7e367749994c0f859348..66fb75010cbf1924bf27f734c630cb4b6796b012 100644 --- a/Gemfile +++ b/Gemfile @@ -44,4 +44,5 @@ group(:test) do gem 'webmock' # DSL wrapper around Rantly generators gem "jennifer", github: "phdavis1027/jennifer" + gem "regexp-examples", "~> 1.6" end diff --git a/sgx-bwmsgsv2.rb b/sgx-bwmsgsv2.rb index 7005919f86d676a392fb2a4c62823346206261bc..c706d248ef86a4a89538f1843b90425c31dccaa0 100755 --- a/sgx-bwmsgsv2.rb +++ b/sgx-bwmsgsv2.rb @@ -401,7 +401,15 @@ module SGXbwmsgsv2 puts "MMSOOB: found a url node - checking if to make MMS..." body = s.respond_to?(:body) ? s.body : '' - EM::HttpRequest.new(un.text, tls: { verify_peer: true }).ahead.then { |http| + + if num_dest.is_a?(String) && num_dest !~ /^\+?1/ + unless body.include?(un.text) + s.body = body.empty? ? un.text : "#{body}\n#{un.text}" + end + return to_catapult(s, nil, num_dest, user_id, token, secret, usern) + end + + EM::HttpRequest.new(un.text, tls: {verify_peer: true}).ahead.then { |http| # If content is too large, or MIME type is not supported, place the link inside the body and do not send MMS. if http.response_header["CONTENT_LENGTH"].to_i > 3500000 || !MMS_MIME_TYPES.include?(http.response_header["CONTENT_TYPE"]) diff --git a/test/property/rantly_extensions/data_extensions.rb b/test/property/rantly_extensions/data_extensions.rb index 97ecd83e2124d644ee5cdf1918e8c9778a49ec58..0ec846a50331fe2d58c5713f837d8aa04be13b07 100644 --- a/test/property/rantly_extensions/data_extensions.rb +++ b/test/property/rantly_extensions/data_extensions.rb @@ -1,8 +1,62 @@ # frozen_string_literal: true +require "regexp-examples" + class Rantly module Data module Extensions + REGEXP_EXAMPLES_OPTS = { + max_group_results: 10, + max_repeater_variance: 10 + }.freeze + + # @see https://github.com/mnestorov/regex-patterns + # @return [String] + FRENCH_PHONE = /\+33[1-9]\d{8}/.freeze + # @return [String] + GERMAN_PHONE = /\+49[1-9]\d{3,12}/.freeze + # @return [String] + UK_PHONE = /\+44[1-9]\d{9,10}/.freeze + # @return [String] + SPANISH_PHONE = /\+34[6-9]\d{8}/.freeze + # @return [String] + ITALIAN_PHONE = /\+39[0-9]{9,10}/.freeze + + # @return [String] + def french_phone + FRENCH_PHONE.random_example(**REGEXP_EXAMPLES_OPTS) + end + + # @return [String] + def german_phone + GERMAN_PHONE.random_example(**REGEXP_EXAMPLES_OPTS) + end + + # @return [String] + def uk_phone + UK_PHONE.random_example(**REGEXP_EXAMPLES_OPTS) + end + + # @return [String] + def spanish_phone + SPANISH_PHONE.random_example(**REGEXP_EXAMPLES_OPTS) + end + + # @return [String] + def italian_phone + ITALIAN_PHONE.random_example(**REGEXP_EXAMPLES_OPTS) + end + + # @return [String] + def non_nanp_phone + send( + choose( + :french_phone, :german_phone, :uk_phone, + :spanish_phone, :italian_phone + ) + ) + end + # @note https://stackoverflow.com/questions/6478875/regular-expression-matching-e-164-formatted-phone-numbers # @return [String] def nanpa_phone diff --git a/test/property/test_non_nanp_oob.rb b/test/property/test_non_nanp_oob.rb new file mode 100644 index 0000000000000000000000000000000000000000..26ba66f7181a5a67933e187c628d74545a23afed --- /dev/null +++ b/test/property/test_non_nanp_oob.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require "test_helper" +require_relative "../../sgx-bwmsgsv2" +require "rantly/minitest_extensions" +require_relative "rantly_extensions/data_extensions" + +class NonNanpOobPropertyTest < Minitest::Test + BW_MESSAGES_URL = + "https://messaging.bandwidth.com/api/v2/users/account/messages" + + def build_oob_message(dest, body, oob_url) + m = Blather::Stanza::Message.new("#{dest}@component", body) + m.from = "test@example.com/res" + + x = Nokogiri::XML::Node.new("x", m.document) + ns = x.add_namespace(nil, "jabber:x:oob") + url_node = Nokogiri::XML::Node.new("url", m.document) + url_node.namespace = ns + url_node.content = oob_url + x.add_child(url_node) + m.add_child(x) + m + end + + def test_non_nanp_oob_sends_url_as_text_not_mms + property_of { + dest = non_nanp_phone + body = choose("", sized(range(5, 40)) { string(:alnum) }) + guard(!body.downcase.match?(BADWORDS)) + oob_path = sized(range(3, 12)) { string(:alnum) } + oob_url = "https://example.com/media/#{oob_path}.jpg" + [dest, body, oob_url] + }.check { |dest, body, oob_url| + reset_stanzas! + reset_redis! + + stub_request(:post, BW_MESSAGES_URL).to_return( + status: 201, body: JSON.dump(id: "bw-msg-fallback") + ) + bw_req = stub_request(:post, BW_MESSAGES_URL).with( + body: hash_including( + text: /#{Regexp.escape(oob_url)}/ + ) + ).to_return( + status: 201, body: JSON.dump(id: "bw-msg-non-nanp") + ) + + process_stanza(build_oob_message(dest, body, oob_url)) + + assert_not_requested :head, oob_url + assert_requested bw_req + } + end + em :test_non_nanp_oob_sends_url_as_text_not_mms + + def test_nanp_oob_attempts_mms + property_of { + dest = nanpa_phone + body = sized(range(5, 40)) { string(:alnum) } + guard(!body.downcase.match?(BADWORDS)) + oob_path = sized(range(3, 12)) { string(:alnum) } + oob_url = "https://example.com/media/#{oob_path}.jpg" + [dest, body, oob_url] + }.check { |dest, body, oob_url| + reset_stanzas! + reset_redis! + + stub_request(:post, BW_MESSAGES_URL).to_return( + status: 201, body: JSON.dump(id: "bw-msg-fallback") + ) + bw_req = stub_request(:post, BW_MESSAGES_URL).with( + body: hash_including(media: oob_url) + ).to_return( + status: 201, body: JSON.dump(id: "bw-msg-nanp") + ) + stub_request(:head, oob_url).to_return( + status: 200, + headers: { + "Content-Length" => "500", + "Content-Type" => "image/jpeg" + } + ) + + process_stanza(build_oob_message(dest, body, oob_url)) + + assert_requested :head, oob_url + assert_requested bw_req + } + end + em :test_nanp_oob_attempts_mms +end