diff --git a/app/services/whatsapp/populate_template_parameters_service.rb b/app/services/whatsapp/populate_template_parameters_service.rb index 3f9f64b91..6ea3e6e05 100644 --- a/app/services/whatsapp/populate_template_parameters_service.rb +++ b/app/services/whatsapp/populate_template_parameters_service.rb @@ -34,8 +34,9 @@ class Whatsapp::PopulateTemplateParametersService return nil if url.blank? sanitized_url = sanitize_parameter(url) - validate_url(sanitized_url) - build_media_type_parameter(sanitized_url, media_type.downcase, media_name) + normalized_url = normalize_url(sanitized_url) + validate_url(normalized_url) + build_media_type_parameter(normalized_url, media_type.downcase, media_name) end def build_named_parameter(parameter_name, value) @@ -138,9 +139,20 @@ class Whatsapp::PopulateTemplateParametersService sanitized[0...1000] # Limit length to prevent DoS end + def normalize_url(url) + # Use Addressable::URI for better URL normalization + # It handles spaces, special characters, and encoding automatically + Addressable::URI.parse(url).normalize.to_s + rescue Addressable::URI::InvalidURIError + # Fallback: simple space encoding if Addressable fails + url.gsub(' ', '%20') + end + def validate_url(url) return if url.blank? + # url is already normalized by the caller + uri = URI.parse(url) raise ArgumentError, "Invalid URL scheme: #{uri.scheme}. Only http and https are allowed" unless %w[http https].include?(uri.scheme) raise ArgumentError, 'URL too long (max 2000 characters)' if url.length > 2000 diff --git a/spec/services/whatsapp/populate_template_parameters_service_spec.rb b/spec/services/whatsapp/populate_template_parameters_service_spec.rb new file mode 100644 index 000000000..05390bd90 --- /dev/null +++ b/spec/services/whatsapp/populate_template_parameters_service_spec.rb @@ -0,0 +1,70 @@ +require 'rails_helper' + +describe Whatsapp::PopulateTemplateParametersService do + let(:service) { described_class.new } + + describe '#normalize_url' do + it 'normalizes URLs with spaces' do + url_with_spaces = 'https://example.com/path with spaces' + normalized = service.send(:normalize_url, url_with_spaces) + + expect(normalized).to eq('https://example.com/path%20with%20spaces') + end + + it 'handles URLs with special characters' do + url = 'https://example.com/path?query=test value' + normalized = service.send(:normalize_url, url) + + expect(normalized).to include('https://example.com/path') + expect(normalized).not_to include(' ') + end + + it 'returns valid URLs unchanged' do + url = 'https://example.com/valid-path' + normalized = service.send(:normalize_url, url) + + expect(normalized).to eq(url) + end + end + + describe '#build_media_parameter' do + context 'when URL contains spaces' do + it 'normalizes the URL before building media parameter' do + url_with_spaces = 'https://example.com/image with spaces.jpg' + result = service.build_media_parameter(url_with_spaces, 'IMAGE') + + expect(result[:type]).to eq('image') + expect(result[:image][:link]).to eq('https://example.com/image%20with%20spaces.jpg') + end + end + + context 'when URL contains special characters in query string' do + it 'normalizes the URL correctly' do + url = 'https://example.com/video.mp4?title=My Video' + result = service.build_media_parameter(url, 'VIDEO', 'test_video') + + expect(result[:type]).to eq('video') + expect(result[:video][:link]).not_to include(' ') + end + end + + context 'when URL is already valid' do + it 'builds media parameter without changing URL' do + url = 'https://example.com/document.pdf' + result = service.build_media_parameter(url, 'DOCUMENT', 'test.pdf') + + expect(result[:type]).to eq('document') + expect(result[:document][:link]).to eq(url) + expect(result[:document][:filename]).to eq('test.pdf') + end + end + + context 'when URL is blank' do + it 'returns nil' do + result = service.build_media_parameter('', 'IMAGE') + + expect(result).to be_nil + end + end + end +end