ip2k / google-api-ruby-client

Automatically exported from code.google.com/p/google-api-ruby-client
Apache License 2.0
0 stars 0 forks source link

When providing parameters to execute(), + is replaced by space #56

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
Call execute() using google-api-ruby-client version 0.4.5:

result = @client.execute(
   api_method: @calendar.events.list,
   parameters: {
      calendarId: calendar_id,
      timeMax: "2012-08-29T00:00:00+02:00",
      timeMin: "2012-08-28T00:00:00+02:00"
   })

timeMax & timeMin parameters are replaced internally by:
2012-08-29T00:00:00 02:00

which then is encoded to 2012-07-29T00%3A00%3A00+02%3A00

The end result is a bad request because Google Calendar API wants 
2012-07-29T00%3A00%3A00%2B02%3A00

After some debugging, it appears that Faraday 0.8.4 does this :

    # File faraday/utils.rb 

    # Adapted from Rack
    def parse_query(qs)
      params = {}

      (qs || '').split(DEFAULT_SEP).each do |p|
        k, v = p.split('=', 2).map { |x| unescape(x) }        <---------

        if cur = params[k]
          if cur.class == Array then params[k] << v
          else params[k] = [cur, v]
          end
        else
          params[k] = v
        end
      end
      params
    end

unescape() method is from file cgi/util.rb from Ruby stdlib:

  # File cgi/util.rb

  # URL-decode a string with encoding(optional).
  #   string = CGI::unescape("%27Stop%21%27+said+Fred")
  #      # => "'Stop!' said Fred"
  def CGI::unescape(string,encoding=@@accept_charset)
    str=string.tr('+', ' ').force_encoding(Encoding::ASCII_8BIT).gsub(/((?:%[0-9a-fA-F]{2})+)/) do
      [$1.delete('%')].pack('H*')
    end.force_encoding(encoding)
    str.valid_encoding? ? str : str.force_encoding(string.encoding)
  end

As you see unescape replaces '+' by ' ' (space) and this causes the issue.

parse_query() from Faraday is being called by api_client/discovery/method.rb 
generate_request()

      # File api_client/discovery/method.rb
      def generate_request(parameters={}, body='', headers=[], options={})
        options[:connection] ||= Faraday.default_connection
        if body.respond_to?(:string)
          body = body.string
        elsif body.respond_to?(:to_str)
          body = body.to_str
        else
          raise TypeError, "Expected String or StringIO, got #{body.class}."
        end
        if !headers.kind_of?(Array) && !headers.kind_of?(Hash)
          raise TypeError, "Expected Hash or Array, got #{headers.class}."
        end
        method = self.http_method
        uri = self.generate_uri(parameters)
        headers = headers.to_a if headers.kind_of?(Hash)
        return options[:connection].build_request(
          method.to_s.downcase.to_sym
        ) do |req|
          req.url(Addressable::URI.parse(uri).normalize.to_s)
          req.url(uri.to_s)        <---------
          req.headers = Faraday::Utils::Headers.new(headers)
          req.body = body
        end
      end

Original issue reported on code.google.com by tkrotoff on 30 Aug 2012 at 7:36

GoogleCodeExporter commented 9 years ago
Oupsss

Inside generate_request(), you should ignore req.url(uri.to_s), it's me trying 
to understand what was going on :)
The line that calls Faraday and then unescape() is
req.url(Addressable::URI.parse(uri).normalize.to_s)

Original comment by tkrotoff on 30 Aug 2012 at 10:46

GoogleCodeExporter commented 9 years ago
I don't think it is a bug. + sign should probably not be inside an url.
The solution here is not to pass the local time (+02:00) but the UTC time 
instead:

      result = @client.execute(
        api_method: @calendar.events.list,
        parameters: {
          calendarId: calendar_id,
          timeMin: time_min.utc.iso8601,
          timeMax: time_max.utc.iso8601
        })

timeMin and timeMax are now:
"2012-07-28T22:00:00Z"
so no more + sign.

Original comment by tkrotoff on 31 Aug 2012 at 12:32

GoogleCodeExporter commented 9 years ago
I'm in complete agreement that avoiding the '+' character is a best practice. 
However, I still consider this a bug. Unfortunately, it's not one that's likely 
to be fixed soon. Way too many dependencies involved in this issue. That said, 
Faraday may end up exposing a mechanism by which handling parameters could be 
delegated to something else.

https://github.com/technoweenie/faraday/issues/182

Original comment by bobaman@google.com on 31 Aug 2012 at 9:03

GoogleCodeExporter commented 9 years ago
I'm having a similar issue, but haven't been able to resolve it by using 
.utc.iso6801.

I'm calling:
    @result = client.execute({
                api_method: service.freebusy.query,
                parameters: { 
                  timeMin: Time.now.utc.iso8601,
                  timeMax: 3.days.from_now.utc.iso8601,
                  items: [{id: "#{@calendar.calendar_id}"}]
                },
                headers: {'Content-Type' => 'application/json'}
    })

I'm getting back a 400, "Missing timeMin parameter" error. Am I missing 
something else with how I need to submit these params?

Original comment by forr...@skedipity.com on 12 Dec 2012 at 4:20

GoogleCodeExporter commented 9 years ago
Finally got this working and the fix was non-intuitive for me:
@result = client.execute({
    api_method: service.freebusy.query,
    body: JSON.dump({
        timeMin: Time.now.utc.iso8601,
        timeMax: 3.days.from_now.utc.iso8601,
        items: [{id: "#{@calendar.calendar_id}"}]
    },
    headers: {'Content-Type' => 'application/json'}
})

Basically, when timeMin was sent in the parameters field, it wasn't interpreted 
correctly, but when sent in the body, it was.

Original comment by forr...@skedipity.com on 12 Dec 2012 at 4:48

GoogleCodeExporter commented 9 years ago
See https://github.com/google/google-api-ruby-client/issues/38 for further 
updates.

Original comment by sba...@google.com on 8 Apr 2013 at 8:48