From 36eb041fcf3a145b18bae008ca2d0fbfb77fd52a Mon Sep 17 00:00:00 2001 From: Phillip Davis Date: Fri, 3 Apr 2026 15:34:46 -0400 Subject: [PATCH] backport: lower max media size to 1MB --- sgx-bwmsgsv2.rb | 5 +- test/property/generators/stanza.rb | 118 ++++++++++++++++++ .../rantly_extensions/data_extensions.rb | 22 ++-- test/property/test_max_media_size.rb | 63 ++++++++++ test/test_helper.rb | 5 +- 5 files changed, 202 insertions(+), 11 deletions(-) create mode 100644 test/property/generators/stanza.rb create mode 100644 test/property/test_max_media_size.rb diff --git a/sgx-bwmsgsv2.rb b/sgx-bwmsgsv2.rb index 52a47b4bc3713021c7720221f65249b3dfafbff1..b5a069d11272235802cb6ca9ddd0d19410ec4995 100755 --- a/sgx-bwmsgsv2.rb +++ b/sgx-bwmsgsv2.rb @@ -158,6 +158,9 @@ MMS_MIME_TYPES = [ "video/x-flv" ] +# 1 MB +MAX_MEDIA_SIZE = 1000000 + def panic(e) if e.is_a?(Exception) Sentry.capture_exception(e, hint: { background: false }) @@ -430,7 +433,7 @@ module SGXbwmsgsv2 EM::HttpRequest.new(URI.parse(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 || + if http.response_header["CONTENT_LENGTH"].to_i > MAX_MEDIA_SIZE || !MMS_MIME_TYPES.include?(http.response_header["CONTENT_TYPE"]) unless body.include?(un.text) s.body = body.empty? ? un.text : "#{body}\n#{un.text}" diff --git a/test/property/generators/stanza.rb b/test/property/generators/stanza.rb new file mode 100644 index 0000000000000000000000000000000000000000..ca9a5741b0a3c65628b9eff75025888a03e94c6c --- /dev/null +++ b/test/property/generators/stanza.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require "jennifer" +require_relative "../rantly_extensions/data_extensions" + +class MessageStanza + # @param redis [FakeRedis] + def initialize(redis) + @redis = redis + end + + include Jennifer.rant(self) { |component| + recv_nums(transient: true) { + array(range(1, 5)) { + choose( + value { nanpa_phone }, + value { non_nanp_phone }, + value { shortcode } + ) + } + } + to derived_from(:recv_nums), transient: true do |recv_nums| + recv_nums.map { |num| "#{num}@#{component}" } + end + phone(transient: true) { nanpa_phone } + from(transient: true) { bare_jid } + media(transient: true) { + array(range(0, 5)) { + [media_url, range(0, MAX_MEDIA_SIZE << 1)] + } + } + registered?(transient: true) { true } + body(transient: true) { choose(nil, "", value { message_body }) } + id(transient: true) { SecureRandom.uuid } + subject { _utf8_body(range(1, 10)) } + stanza derived_from(:body, :to, :from, :subject, :media, :id, :recv_nums) { |body, to_jids, from, subject, media, id, recv_nums| + stanza_body = body + if media.any? && boolean + url = media.first.first + stanza_body = stanza_body.to_s.empty? ? url : "#{stanza_body}\n#{url}" + end + + if recv_nums.length == 1 + m = Blather::Stanza::Message.new(to_jids.first, stanza_body) + else + m = Blather::Stanza::Message.new(component, stanza_body) + addrs = Nokogiri::XML::Node.new('addresses', m.document) + addrs['xmlns'] = 'http://jabber.org/protocol/address' + recv_nums.each do |num| + addr = Nokogiri::XML::Node.new('address', m.document) + addr['type'] = 'to' + addr['uri'] = "sms:#{num}" + addrs.add_child(addr) + end + m.add_child(addrs) + end + + m.from = from + m.id = id + m.subject = subject if subject + + media.each do |url, _size| + 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 = url + x.add_child(url_node) + m.add_child(x) + end + + m + } + redis_state derived_from(:from, :phone, :registered?), transient: true do |from, phone, registered| + @redis.reset! + @redis.set("catapult_jid-", "HERE") + next unless registered + + @redis.set("catapult_jid-#{phone}", from) + @redis.rpush("catapult_cred-#{from}", 'account', 'token', 'secret', phone) + end + http_stubs derived_from(:media, :registered?), transient: true do |media, registered| + WebMock.reset! + next [] unless registered + + media.map do |url, size| + ext = File.extname(URI.parse(url).path) + content_type = freq( + [95, proc { Rack::Mime.mime_type(ext) }], + [5, proc { Rack::Mime.mime_type(".#{string(:alpha)}") }] + ) + WebMock::API.stub_request(:head, url).to_return( + status: 200, + headers: { + "Content-Length" => size.to_s, + "Content-Type" => content_type + } + ) + end + end + bandwidth_stub derived_from(:registered?, :media), transient: true do |registered, _media| + next unless registered + + WebMock::API.stub_request(:post, BW_MESSAGES_URL).to_return( + status: 202, + body: JSON.dump(id: "bw-msg-#{SecureRandom.hex(4)}") + ) + end + assertions derived_from(:http_stubs, :bandwidth_stub) { |stubs, bw| + a = stubs.map { |stub| -> { assert_requested(stub) } } + a << -> { assert_requested(bw) } if bw + a + } + written_state(transient: true) { + SGXbwmsgsv2.instance_variable_set(:@written, []) + } + } +end diff --git a/test/property/rantly_extensions/data_extensions.rb b/test/property/rantly_extensions/data_extensions.rb index 09755b2909f0e1a44ba9a7e6559e6a442ba905e8..a1d683aee46387b54effd65107964d1b41b768ca 100644 --- a/test/property/rantly_extensions/data_extensions.rb +++ b/test/property/rantly_extensions/data_extensions.rb @@ -131,28 +131,34 @@ class Rantly # @param ascii_only [Boolean, nil] truthy=force ASCII, nil=random # @param nil_pct [Integer] weight (out of 100) for nil result # @param empty_pct [Integer] weight (out of 100) for empty string result + # @param segments [Integer, nil] exact number of segments, overrides length generation # @return [String, nil] - def message_body(ascii_only: nil, nil_pct: 2, empty_pct: 2) + def message_body(ascii_only: nil, nil_pct: 2, empty_pct: 2, segments: nil) text_pct = 100 - nil_pct - empty_pct freq( [nil_pct, proc { nil }], [empty_pct, proc { "" }], - [text_pct, proc { _message_body_text(ascii_only: ascii_only) }] + [text_pct, proc { _message_body_text(ascii_only: ascii_only, segments: segments) }] ) end # @param ascii_only [Boolean, nil] + # @param segments [Integer, nil] # @return [String] - def _message_body_text(ascii_only: nil) + def _message_body_text(ascii_only: nil, segments: nil) use_ascii = ascii_only || boolean segment_size = use_ascii ? SEGMENT_SIZE_GSM7 : SEGMENT_SIZE_UCS2 - threshold = segment_size * 3 - len = freq( - [70, proc { range(1, threshold) }], - [30, proc { range(threshold + 1, threshold + segment_size * 2) }] - ) + len = if segments + range(segment_size * (segments - 1) + 1, segment_size * segments) + else + threshold = segment_size * 3 + freq( + [70, proc { range(1, threshold) }], + [30, proc { range(threshold + 1, threshold + segment_size * 2) }] + ) + end body = if use_ascii sized(len) { string(:print) } diff --git a/test/property/test_max_media_size.rb b/test/property/test_max_media_size.rb new file mode 100644 index 0000000000000000000000000000000000000000..be56fd184403d3eff9e3a57c0730c75a1575cb83 --- /dev/null +++ b/test/property/test_max_media_size.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "test_helper" +require_relative "../../sgx-bwmsgsv2" +require "rantly/minitest_extensions" +require_relative "generators/stanza" + +class MaxMediaSizePropertyTest < Minitest::Test + def test_oversized_media_inlined_as_text + property_of { + MessageStanza.new(REDIS) + .recv_nums { [nanpa_phone] } + .media { [[deliverable_media_url, range(MAX_MEDIA_SIZE + 1, MAX_MEDIA_SIZE << 1)]] } + .body { message_body(segments: 1) } + .bandwidth_stub { |_registered, media| + (url, *), * = media + WebMock::API.stub_request(:post, BW_MESSAGES_URL).with( + body: WebMock::API.hash_including(text: /#{Regexp.escape(url)}/) + ).to_return( + status: 202, body: JSON.dump(id: "bw-msg-oversized") + ) + } + .generate(ARGV[0]) + }.check { |_metadata, example| + process_stanza(example["stanza"]) + example["assertions"].each { |a| instance_exec(&a) } + } + end + em :test_oversized_media_inlined_as_text + + def test_undersized_media_sent_as_mms + property_of { + MessageStanza.new(REDIS) + .recv_nums { [nanpa_phone] } + .media { [[deliverable_media_url, range(1, MAX_MEDIA_SIZE)]] } + .http_stubs { |media, _registered| + media.map do |u, s| + ext = File.extname(URI.parse(u).path) + WebMock::API.stub_request(:head, u).to_return( + status: 200, + headers: { + "Content-Length" => s.to_s, + "Content-Type" => Rack::Mime.mime_type(ext) + } + ) + end + } + .bandwidth_stub { |_registered, media| + (url, *), * = media + WebMock::API.stub_request(:post, BW_MESSAGES_URL).with( + body: WebMock::API.hash_including(media: url) + ).to_return( + status: 202, body: JSON.dump(id: "bw-msg-undersized") + ) + } + .generate(ARGV[0]) + }.check { |_metadata, example| + process_stanza(example["stanza"]) + example["assertions"].each { |a| instance_exec(&a) } + } + end + em :test_undersized_media_sent_as_mms +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 98ba247c2034a8f4d60f0b381bc369846b9d1aba..801533658b82e948a6587308e956812c8e6af5f9 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -138,7 +138,7 @@ def build_group_mms(body, dests, from: "test@example.com", id: nil) m end -# @param body [String] +# @param body [String, nil] # @param dests [Array] # @param from [String] # @param id [String, nil] @@ -147,7 +147,8 @@ def build_outbound_message(body, dests, from: "test@example.com", id: nil) if dests.length == 1 || dests.is_a?(String) dests = [dests] if dests.is_a?(String) - m = Blather::Stanza::Message.new("#{dests.first}@#{ARGV[0]}", body) + m = Blather::Stanza::Message.new("#{dests.first}@#{ARGV[0]}") + m.body = body if body m.from = from m.id = id if id m