From 62d15daf1f7009b532bced2423d2e3878c4a6628 Mon Sep 17 00:00:00 2001 From: Ari Summer Date: Sat, 28 Mar 2020 10:39:34 -0600 Subject: [PATCH 1/4] Pass custom HTTP::FormData with encoder for post requests --- lib/twitter/rest/form_encoder.rb | 22 ++++++++++++++++++++++ lib/twitter/rest/request.rb | 8 +++++++- twitter.gemspec | 4 ++-- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 lib/twitter/rest/form_encoder.rb diff --git a/lib/twitter/rest/form_encoder.rb b/lib/twitter/rest/form_encoder.rb new file mode 100644 index 000000000..506043d45 --- /dev/null +++ b/lib/twitter/rest/form_encoder.rb @@ -0,0 +1,22 @@ +class FormEncoder + def self.encode(data) + unescaped_chars = /[^a-z0-9\-\.\_\~]/i + data.map do |k, v| + if v.nil? + ::URI::DEFAULT_PARSER.escape(k.to_s, unescaped_chars) + elsif v.respond_to?(:to_ary) + v.to_ary.map do |w| + str = ::URI::DEFAULT_PARSER.escape(k.to_s, unescaped_chars) + unless w.nil? + str << '=' + str << ::URI::DEFAULT_PARSER.escape(w.to_s, unescaped_chars) + end + end.join('&') + else + str = ::URI::DEFAULT_PARSER.escape(k.to_s, unescaped_chars) + str << '=' + str << ::URI::DEFAULT_PARSER.escape(v.to_s, unescaped_chars) + end + end.join('&') + end +end diff --git a/lib/twitter/rest/request.rb b/lib/twitter/rest/request.rb index aa633d4df..71fb55681 100644 --- a/lib/twitter/rest/request.rb +++ b/lib/twitter/rest/request.rb @@ -7,6 +7,7 @@ require 'twitter/headers' require 'twitter/rate_limit' require 'twitter/utils' +require 'twitter/rest/form_encoder' module Twitter module REST @@ -44,7 +45,12 @@ def perform private def request_options - options = {@options_key => @options} + if @options_key == :form + options = {form: HTTP::FormData.create(@options, encoder: FormEncoder.method(:encode))} + else + options = {@options_key => @options} + end + if @params if options[:params] options[:params].merge(@params) diff --git a/twitter.gemspec b/twitter.gemspec index 3e0dcf95e..a366d45ff 100644 --- a/twitter.gemspec +++ b/twitter.gemspec @@ -6,8 +6,8 @@ Gem::Specification.new do |spec| spec.add_dependency 'addressable', '~> 2.3' spec.add_dependency 'buftok', '~> 0.2.0' spec.add_dependency 'equalizer', '~> 0.0.11' - spec.add_dependency 'http', '~> 4.0' - spec.add_dependency 'http-form_data', '~> 2.0' + spec.add_dependency 'http', '~> 4.4' + spec.add_dependency 'http-form_data', '~> 2.3' spec.add_dependency 'http_parser.rb', '~> 0.6.0' spec.add_dependency 'memoizable', '~> 0.4.0' spec.add_dependency 'multipart-post', '~> 2.0' From bdbdca3edb10198f37bfeeba5086caee2fcd72ee Mon Sep 17 00:00:00 2001 From: Ari Summer Date: Sat, 28 Mar 2020 10:58:41 -0600 Subject: [PATCH 2/4] Add request spec --- spec/twitter/rest/request_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/twitter/rest/request_spec.rb b/spec/twitter/rest/request_spec.rb index 31a0103e9..813d7d5b3 100644 --- a/spec/twitter/rest/request_spec.rb +++ b/spec/twitter/rest/request_spec.rb @@ -8,6 +8,7 @@ describe '#request' do it 'encodes the entire body when no uploaded media is present' do stub_post('/1.1/statuses/update.json').with(body: {status: 'Update'}).to_return(body: fixture('status.json'), headers: {content_type: 'application/json; charset=utf-8'}) + expect_any_instance_of(HTTP::Client).to receive(:post).with('https://api.twitter.com/1.1/statuses/update.json', form: instance_of(HTTP::FormData::Urlencoded)).and_call_original @client.update('Update') expect(a_post('/1.1/statuses/update.json').with(body: {status: 'Update'})).to have_been_made end @@ -18,6 +19,11 @@ expect(a_request(:post, 'https://upload.twitter.com/1.1/media/upload.json')).to have_been_made expect(a_post('/1.1/statuses/update.json').with(body: {status: 'Update', media_ids: '470030289822314497'})).to have_been_made end + it 'uses custom HTTP::FormData::Urlencoded instance for form requests' do + stub_post('/1.1/statuses/update.json').with(body: {status: 'Update'}).to_return(body: fixture('status.json'), headers: {content_type: 'application/json; charset=utf-8'}) + expect_any_instance_of(HTTP::Client).to receive(:post).with('https://api.twitter.com/1.1/statuses/update.json', form: instance_of(HTTP::FormData::Urlencoded)).and_call_original + @client.update('Update') + end context 'when using a proxy' do before do From ac786b8b10f09eae75729683015ad85ab6c8dd08 Mon Sep 17 00:00:00 2001 From: Ari Summer Date: Sat, 28 Mar 2020 11:07:50 -0600 Subject: [PATCH 3/4] Refactor and add FormEncoder specs --- lib/twitter/rest/form_encoder.rb | 37 +++++++++++++++----------- lib/twitter/rest/request.rb | 2 +- spec/twitter/rest/form_encoder_spec.rb | 9 +++++++ 3 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 spec/twitter/rest/form_encoder_spec.rb diff --git a/lib/twitter/rest/form_encoder.rb b/lib/twitter/rest/form_encoder.rb index 506043d45..501bfc81b 100644 --- a/lib/twitter/rest/form_encoder.rb +++ b/lib/twitter/rest/form_encoder.rb @@ -1,22 +1,27 @@ -class FormEncoder - def self.encode(data) - unescaped_chars = /[^a-z0-9\-\.\_\~]/i - data.map do |k, v| - if v.nil? - ::URI::DEFAULT_PARSER.escape(k.to_s, unescaped_chars) - elsif v.respond_to?(:to_ary) - v.to_ary.map do |w| - str = ::URI::DEFAULT_PARSER.escape(k.to_s, unescaped_chars) - unless w.nil? +module Twitter + module REST + class FormEncoder + UNESCAPED_CHARS = /[^a-z0-9\-\.\_\~]/i + + def self.encode(data) + data.map do |k, v| + if v.nil? + ::URI::DEFAULT_PARSER.escape(k.to_s, UNESCAPED_CHARS) + elsif v.respond_to?(:to_ary) + v.to_ary.map do |w| + str = ::URI::DEFAULT_PARSER.escape(k.to_s, UNESCAPED_CHARS) + unless w.nil? + str << '=' + str << ::URI::DEFAULT_PARSER.escape(w.to_s, UNESCAPED_CHARS) + end + end.join('&') + else + str = ::URI::DEFAULT_PARSER.escape(k.to_s, UNESCAPED_CHARS) str << '=' - str << ::URI::DEFAULT_PARSER.escape(w.to_s, unescaped_chars) + str << ::URI::DEFAULT_PARSER.escape(v.to_s, UNESCAPED_CHARS) end end.join('&') - else - str = ::URI::DEFAULT_PARSER.escape(k.to_s, unescaped_chars) - str << '=' - str << ::URI::DEFAULT_PARSER.escape(v.to_s, unescaped_chars) end - end.join('&') + end end end diff --git a/lib/twitter/rest/request.rb b/lib/twitter/rest/request.rb index 71fb55681..a68944427 100644 --- a/lib/twitter/rest/request.rb +++ b/lib/twitter/rest/request.rb @@ -46,7 +46,7 @@ def perform def request_options if @options_key == :form - options = {form: HTTP::FormData.create(@options, encoder: FormEncoder.method(:encode))} + options = {form: HTTP::FormData.create(@options, encoder: Twitter::REST::FormEncoder.method(:encode))} else options = {@options_key => @options} end diff --git a/spec/twitter/rest/form_encoder_spec.rb b/spec/twitter/rest/form_encoder_spec.rb new file mode 100644 index 000000000..50dc371ec --- /dev/null +++ b/spec/twitter/rest/form_encoder_spec.rb @@ -0,0 +1,9 @@ +require 'helper' + +describe Twitter::REST::FormEncoder do + describe '.encode' do + it 'encodes asterisk correctly' do + expect(described_class.encode({status: 'Update *'})).to eq('status=Update%20%2A') + end + end +end From d16ca590ed0c2849eb58737e4b4dc1785d205372 Mon Sep 17 00:00:00 2001 From: Ari Summer Date: Sat, 28 Mar 2020 11:16:22 -0600 Subject: [PATCH 4/4] Improve spec --- spec/twitter/rest/request_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/twitter/rest/request_spec.rb b/spec/twitter/rest/request_spec.rb index 813d7d5b3..4959d8bc5 100644 --- a/spec/twitter/rest/request_spec.rb +++ b/spec/twitter/rest/request_spec.rb @@ -22,6 +22,7 @@ it 'uses custom HTTP::FormData::Urlencoded instance for form requests' do stub_post('/1.1/statuses/update.json').with(body: {status: 'Update'}).to_return(body: fixture('status.json'), headers: {content_type: 'application/json; charset=utf-8'}) expect_any_instance_of(HTTP::Client).to receive(:post).with('https://api.twitter.com/1.1/statuses/update.json', form: instance_of(HTTP::FormData::Urlencoded)).and_call_original + expect(Twitter::REST::FormEncoder).to receive(:encode).with({status: 'Update'}).and_call_original @client.update('Update') end