Rails/Rack: "ArgumentError: invalid %-encoding" for POST data - ruby-on-rails-3

Our ruby on rails site has a URI that one of our partners POSTs XML data to.
Since we don't want to deal with XML, we literally just stuff the raw data into a database column and don't go any further with processing it.
However, one of the posts we received gave us this error in airbrake:
ArgumentError: invalid %-encoding ("http://ns.hr-xml.org/2004-08-02"
userId="" password=""><BackgroundReportPackage type="report">
<ProviderReferenceId>....
With backtrace:
vendor/ruby-1.9.3/lib/ruby/1.9.1/uri/common.rb:898:in `decode_www_form_component'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/utils.rb:41:in `unescape'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/utils.rb:94:in `block (2 levels) in parse_nested_query'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/utils.rb:94:in `map'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/utils.rb:94:in `block in parse_nested_query'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/utils.rb:93:in `each'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/utils.rb:93:in `parse_nested_query'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/request.rb:332:in `parse_query'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/request.rb:209:in `POST'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/methodoverride.rb:26:in `method_override'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/methodoverride.rb:14:in `call'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/runtime.rb:17:in `call'
vendor/bundle/ruby/1.9.1/gems/activesupport-3.2.13/lib/active_support/cache/strategy/local_cache.rb:72:in `call'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/lock.rb:15:in `call'
vendor/bundle/ruby/1.9.1/gems/actionpack-3.2.13/lib/action_dispatch/middleware/static.rb:63:in `call'
vendor/bundle/ruby/1.9.1/gems/rack-cache-1.2/lib/rack/cache/context.rb:136:in `forward'
vendor/bundle/ruby/1.9.1/gems/rack-cache-1.2/lib/rack/cache/context.rb:143:in `pass'
vendor/bundle/ruby/1.9.1/gems/rack-cache-1.2/lib/rack/cache/context.rb:155:in `invalidate'
vendor/bundle/ruby/1.9.1/gems/rack-cache-1.2/lib/rack/cache/context.rb:71:in `call!'
vendor/bundle/ruby/1.9.1/gems/rack-cache-1.2/lib/rack/cache/context.rb:51:in `call'
vendor/bundle/ruby/1.9.1/gems/railties-3.2.13/lib/rails/engine.rb:479:in `call'
vendor/bundle/ruby/1.9.1/gems/railties-3.2.13/lib/rails/application.rb:223:in `call'
vendor/bundle/ruby/1.9.1/gems/railties-3.2.13/lib/rails/railtie/configurable.rb:30:in `method_missing'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/deflater.rb:13:in `call'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/content_length.rb:14:in `call'
vendor/bundle/ruby/1.9.1/gems/railties-3.2.13/lib/rails/rack/log_tailer.rb:17:in `call'
vendor/bundle/ruby/1.9.1/gems/thin-1.4.1/lib/thin/connection.rb:80:in `block in pre_process'
vendor/bundle/ruby/1.9.1/gems/thin-1.4.1/lib/thin/connection.rb:78:in `catch'
vendor/bundle/ruby/1.9.1/gems/thin-1.4.1/lib/thin/connection.rb:78:in `pre_process'
vendor/bundle/ruby/1.9.1/gems/thin-1.4.1/lib/thin/connection.rb:53:in `process'
vendor/bundle/ruby/1.9.1/gems/thin-1.4.1/lib/thin/connection.rb:38:in `receive_data'
vendor/bundle/ruby/1.9.1/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run_machine'
vendor/bundle/ruby/1.9.1/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run'
vendor/bundle/ruby/1.9.1/gems/thin-1.4.1/lib/thin/backends/base.rb:63:in `start'
vendor/bundle/ruby/1.9.1/gems/thin-1.4.1/lib/thin/server.rb:159:in `start'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/handler/thin.rb:13:in `run'
vendor/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/server.rb:268:in `start'
vendor/bundle/ruby/1.9.1/gems/railties-3.2.13/lib/rails/commands/server.rb:70:in `start'
vendor/bundle/ruby/1.9.1/gems/railties-3.2.13/lib/rails/commands.rb:55:in `block in <top (required)>'
vendor/bundle/ruby/1.9.1/gems/railties-3.2.13/lib/rails/commands.rb:50:in `tap'
vendor/bundle/ruby/1.9.1/gems/railties-3.2.13/lib/rails/commands.rb:50:in `<top (required)>'
script/rails:6:in `require'
script/rails:6:in `<main>'
The issue is that the POST contains the data:
<ChargeOrComplaint>DRIVE WHILE BLOOD ALCOHOL LEVEL IS 0.08% OR MORE</ChargeOrComplaint>
Presumably this is valid XML, but the naked % at the end of 0.08% is causing the error, since it's coming via HTTP and I guess rack is expecting it to be URL encoded.
The backtrace indicates this is happening before it even gets to our code, so I don't think it has anything to do with how we're processing it.
My questions, then:
1) Where does the issue lie? Ruby 1.9.3's implementation of decode_www_form_component (at the top of the stack trace)? Rack? Our partner's POST data or headers? Our handling of the POST?
2) Does XML data POSTed via HTTP need to be URL encoded?
3) Is there a header that this POST needs to have for Rack to interpret it correctly? (i.e.: that it's XML binary data, and not URL encoded).
4) If I can't get our partner to change what they're posting to us, how could we work around it? Some Rack middleware?

I'm guessing your partner is probably POSTing the data to you as "x-www-form-urlencoded", making Rack try to parse it that way. If they can change what they're sending, I suspect making their content-type "text/xml" will fix this.
If you can't get them to change what they send, then yes, I think you'd have to use Rack middleware (or monkeypatching). Although you could poke around the Rack source, maybe there's a setting to avoid doing any parsing.

There's some debate in various forums as to where the responsibility lies for catching invalid encoding errors in request body content, but neither rack nor rails handles it, both leaving it to the app to handle. To work around invalid %-encoding in POST data in my app, I used a similar solution to this related question: Rails ArgumentError: invalid %-encoding
I added this middleware in app/middleware/invalid_post_data_interceptor.rb to intercept invalid post data:
class InvalidPostDataInterceptor
def initialize(app)
#app = app
end
def call(env)
request_content = Rack::Request.new(env).POST rescue :bad_form_data
headers = {'Content-Type' => 'text/plain'}
if request_content == :bad_form_data
[400, headers, ['Bad Request']]
else
#app.call(env)
end
end
end
Then added it to the middleware stack by adding this to application.rb:
config.middleware.insert_before Rack::Runtime, "InvalidPostDataInterceptor"

In my case the reason was extra newlines after the headers and before the request body. I'm guessing there is Content-Length inconsistency which throws off the parser. If you are setting headers programmatically make sure they don't have trailing newlines.

Related

Relation#as will change behavior in 4.0. Use `map_to` instead

In hanami guide
https://hanamirb.org/guides/1.2/associations/has-many/#usage
when I use the method
#book = BookRepository.new.find_with_tickets(params[:id])
I got a message:
[deprecated] Relation#as will change behavior in 4.0. Use `map_to` instead
=> Called at:
/Users/saika/Documents/local/kadai-hanami/ticket_api/lib/ticket_api/repositories/book_repository.rb:7:in `find_with_tickets'
/Users/saika/Documents/local/kadai-hanami/ticket_api/apps/api/controllers/books/show.rb:9:in `call'
/Users/saika/.rvm/gems/ruby-2.4.0/gems/hanami-controller-1.2.0/lib/hanami/action/callbacks.rb:195:in `call'
/Users/saika/.rvm/gems/ruby-2.4.0/gems/hanami-controller-1.2.0/lib/hanami/action/callable.rb:71:in `block in call'
/Users/saika/.rvm/gems/ruby-2.4.0/gems/hanami-controller-1.2.0/lib/hanami/action/throwable.rb:145:in `block in _rescue'
/Users/saika/.rvm/gems/ruby-2.4.0/gems/hanami-controller-1.2.0/lib/hanami/action/throwable.rb:143:in `catch'
I do not know what should I instead use 'map_to'.
you should replace #as with #map_to, and it will work as usual.

intermittent "Moped::Protocol::Command failed with error nil" errors in Resque workers

I'm using Rails 3.2, Mongoid 3.1.7, Moped 1.5.3, and Resque 1.24.1
I'm repeatedly getting "Moped::Protocol::Command failed with error nil" [1] errors in my Resque workers.
This issue, and this one in the Moped repo make me think this is probably due to Resque forking, but they're both about older versions of Moped than I'm using, and I've been unable to find any specific information specific to configuring Resque to work nicely with Mongoid.
How do I resolve the errors in my Resque workers?
[1] Full error:
Exception: Moped::Errors::OperationFailure
Error: The operation: #<Moped::Protocol::Command #length=58 #request_id=3 #response_to=0 #op_code=2004 #flags=[] #full_collection_name="admin.$cmd" #skip=0 #limit=-1 #selector={:ismaster=>1} #fields=nil> failed with error nil
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:99:in `block in command'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:600:in `[]'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:600:in `block (3 levels) in flush'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:599:in `map'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:599:in `block (2 levels) in flush'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:150:in `ensure_connected'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:595:in `block in flush'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:610:in `logging'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:594:in `flush'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:583:in `process'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:92:in `command'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/node.rb:409:in `refresh'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/cluster.rb:171:in `block in refresh'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/cluster.rb:185:in `each'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/cluster.rb:185:in `refresh'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/cluster.rb:135:in `nodes'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/cluster.rb:206:in `with_primary'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/session/context.rb:108:in `with_node'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/cursor.rb:137:in `load_docs'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/cursor.rb:25:in `each'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/query.rb:76:in `each'
/shared/bundle/ruby/2.2.0/gems/moped-1.5.3/lib/moped/query.rb:76:in `each'
/shared/bundle/ruby/2.2.0/gems/mongoid-3.1.7/lib/mongoid/contextual/mongo.rb:423:in `entries'
/shared/bundle/ruby/2.2.0/gems/mongoid-3.1.7/lib/mongoid/contextual/mongo.rb:423:in `check_existence'
/shared/bundle/ruby/2.2.0/gems/mongoid-3.1.7/lib/mongoid/contextual/mongo.rb:146:in `exists?'
/shared/bundle/ruby/2.2.0/gems/mongoid-3.1.7/lib/mongoid/contextual.rb:19:in `exists?'
/shared/bundle/ruby/2.2.0/gems/mongoid-3.1.7/lib/mongoid/validations/uniqueness.rb:273:in `validate_root'
/shared/bundle/ruby/2.2.0/gems/mongoid-3.1.7/lib/mongoid/validations/uniqueness.rb:52:in `block in validate_each'
/shared/bundle/ruby/2.2.0/gems/mongoid-3.1.7/lib/mongoid/validations/queryable.rb:23:in `with_query'
/shared/bundle/ruby/2.2.0/gems/mongoid-3.1.7/lib/mongoid/validations/uniqueness.rb:46:in `validate_each'
/shared/bundle/ruby/2.2.0/gems/activemodel-3.2.22/lib/active_model/validator.rb:153:in `block in validate'
/shared/bundle/ruby/2.2.0/gems/activemodel-3.2.22/lib/active_model/validator.rb:150:in `each'
/shared/bundle/ruby/2.2.0/gems/activemodel-3.2.22/lib/active_model/validator.rb:150:in `validate'
/shared/bundle/ruby/2.2.0/gems/activesupport-3.2.22/lib/active_support/callbacks.rb:310:in `_callback_before_265'
/shared/bundle/ruby/2.2.0/gems/activesupport-3.2.22/lib/active_support/callbacks.rb:418:in `_run__123721054543109017__validate__3207805434451540367__callbacks'
/shared/bundle/ruby/2.2.0/gems/activesupport-3.2.22/lib/active_support/callbacks.rb:405:in `__run_callback'
/shared/bundle/ruby/2.2.0/gems/activesupport-3.2.22/lib/active_support/callbacks.rb:385:in `_run_validate_callbacks'
/shared/bundle/ruby/2.2.0/gems/activesupport-3.2.22/lib/active_support/callbacks.rb:81:in `run_callbacks'
/shared/bundle/ruby/2.2.0/gems/mongoid-3.1.7/lib/mongoid/callbacks.rb:130:in `run_callbacks'
/shared/bundle/ruby/2.2.0/gems/activemodel-3.2.22/lib/active_model/validations.rb:228:in `run_validations!'
/shared/bundle/ruby/2.2.0/gems/activemodel-3.2.22/lib/active_model/validations/callbacks.rb:53:in `block in run_validations!'
/shared/bundle/ruby/2.2.0/gems/activesupport-3.2.22/lib/active_support/callbacks.rb:403:in `_run__123721054543109017__validation__3207805434451540367__callbacks'
/shared/bundle/ruby/2.2.0/gems/activesupport-3.2.22/lib/active_support/callbacks.rb:405:in `__run_callback'
/shared/bundle/ruby/2.2.0/gems/activesupport-3.2.22/lib/active_support/callbacks.rb:385:in `_run_validation_callbacks'
/shared/bundle/ruby/2.2.0/gems/activesupport-3.2.22/lib/active_support/callbacks.rb:81:in `run_callbacks'
/shared/bundle/ruby/2.2.0/gems/mongoid-3.1.7/lib/mongoid/callbacks.rb:130:in `run_callbacks'
/shared/bundle/ruby/2.2.0/gems/activemodel-3.2.22/lib/active_model/validations/callbacks.rb:53:in `run_validations!'
/shared/bundle/ruby/2.2.0/gems/activemodel-3.2.22/lib/active_model/validations.rb:195:in `valid?'
/shared/bundle/ruby/2.2.0/gems/mongoid-3.1.7/lib/mongoid/validations.rb:84:in `valid?'
/releases/20160315172839/app/performers/garmin_receive_push.rb:11:in `block in perform'
/releases/20160315172839/app/performers/garmin_receive_push.rb:9:in `map'
/releases/20160315172839/app/performers/garmin_receive_push.rb:9:in `perform'
This seems to have worked. Went from 30+ errors in my Resque workers yesterday to 0 so far in the past 18 hours.
Added to my Resque config:
Resque.before_fork do
::Mongoid.default_session.disconnect
end
I don't have a very coherent description of exactly what was going wrong here. Resque workers fork a child process, and then the child processes the job, so my current operating assumption is that this is similar to the problems described with Unicorn and Passenger forking in the linked issues.

Rails 3.1 client_side_validations Gem error uninitialized constant error on uniqueness constraint

I have a model named Business for which I have a uniqueness validation on email. The ajax request gets sent to the url
http://localhost.com/validators/uniqueness?case_sensitive=true&business%5Bemail%5D=mabid.mah%40gmail.com
But it gives me a strange error
uninitialized constant Busines
The s is missing where did it go ? here is the stack trace
activesupport (3.2.3) lib/active_support/inflector/methods.rb:229:in `block in constantize'
activesupport (3.2.3) lib/active_support/inflector/methods.rb:228:in `each'
activesupport (3.2.3) lib/active_support/inflector/methods.rb:228:in `constantize'
activesupport (3.2.3) lib/active_support/core_ext/string/inflections.rb:54:in `constantize'
client_side_validations (3.1.4) lib/client_side_validations/middleware.rb:59:in `is_unique?'
client_side_validations (3.1.4) lib/client_side_validations/middleware.rb:45:in `response'
client_side_validations (3.1.4) lib/client_side_validations/middleware.rb:16:in `call'
Realized this is an issue with rails inflector classify
The classify method does not work with singular names like "Business"
http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-classify
To workaround add the following to your config/initializers/inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
inflect.singular(/ess$/i, 'ess')
end

How to add a calculated value to an array in rails?

I have a Product option that I calculate a weighted score for in Rails.
I'd like to return an array that adds the calculated product score to the array. I tried:
products.inject {|p| p.exponential_discount_score(0.01) }
But got:
NoMethodError: undefined method `exponential_discount_score' for 246.86645269006013:Float
from (irb):39:in `block in irb_binding'
from (irb):39:in `each'
from (irb):39:in `inject'
from (irb):39
from /Users/justin/.rvm/gems/ruby-1.9.2-p290#rails-3.1rc4/gems/railties-3.1.1/lib/rails/commands/console.rb:45:in `start'
from /Users/justin/.rvm/gems/ruby-1.9.2-p290#rails-3.1rc4/gems/railties-3.1.1/lib/rails/commands/console.rb:8:in `start'
from /Users/justin/.rvm/gems/ruby-1.9.2-p290#rails-3.1rc4/gems/railties-3.1.1/lib/rails/commands.rb:40:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
I thought inject would work, but I guess I'm doing something wrong? I also tried .collect but couldn't write the block correctly.
The problem here is that the method you call to calculate score doesn't exist.
When you've created it, you can call something like :
products.collect {|p| p.exponential_discount_score(0.01) }.inject(:+)

how to handle Geokit::Geocoders::GeocodeError in rails 3

Hi, I am using Geokit plugin in Ruby on Rails 3, it works fine for me. But when I give origin value not in a proper way it throws Geokit::Geocoders::GeocodeError
Here is my code:
#listing = Listing.geo_scope(:origin=>"sdfaasssssssdfdfsdfdfdfdfsdfsdfsdfsdfsdf")
Error:
Geokit::Geocoders::GeocodeError: Geokit::Geocoders::GeocodeError
from C:/Ruby192/lib/ruby/gems/1.9.1/gems/geokit-1.5.0/lib/geokit/mappable.rb:282:in `normalize'
from C:/Ruby192/lib/ruby/gems/1.9.1/gems/geokit-rails3-0.1.2/lib/geokit-rails3/acts_as_mappable.rb:229:in `normalize_point_to_lat_lng'
from C:/Ruby192/lib/ruby/gems/1.9.1/gems/geokit-rails3-0.1.2/lib/geokit-rails3/acts_as_mappable.rb:189:in `extract_origin_from_options'
from C:/Ruby192/lib/ruby/gems/1.9.1/gems/geokit-rails3-0.1.2/lib/geokit-rails3/acts_as_mappable.rb:111:in `geo_scope'
from (irb):3
from C:/Ruby192/lib/ruby/gems/1.9.1/gems/railties-3.0.3/lib/rails/commands/console.rb:44:in `start'
from C:/Ruby192/lib/ruby/gems/1.9.1/gems/railties-3.0.3/lib/rails/commands/console.rb:8:in `start'
from C:/Ruby192/lib/ruby/gems/1.9.1/gems/railties-3.0.3/lib/rails/commands.rb:23:in `<top (required)>'
from D:/ariv/projects/RubyMine/rentstore/script/rails:6:in `require'
from D:/ariv/projects/RubyMine/rentstore/script/rails:6:in `<top (required)>'
from -e:1:in `load'
from -e:1:in `<main>
How to handle this error?
Thanks,
L. Arivarasan
I dont know what you really mean, but maybe you want something like this
begin
#listing = Listing.geo_scope(:origin=>"sdfaasssssssdfdfsdfdfdfdfsdfsdfsdfsdfsdf")
rescue Geokit::Geocoders::GeocodeError
# handle the error here :-)
end