Skip to content

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

Closed
sqrrrl opened this Issue Apr 8, 2013 · 1 comment

1 participant

@sqrrrl
Google member
sqrrrl commented Apr 8, 2013

(From https://code.google.com/p/google-api-ruby-client/issues/detail?id=56)

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

Aug 30, 2012
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)

Aug 30, 2012
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.

Aug 31, 2012
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

Dec 11, 2012
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?

Dec 12, 2012
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.

@sqrrrl
Google member
sqrrrl commented Jun 7, 2013

Fixed for 0.7 (soon)

@sqrrrl sqrrrl closed this Jun 7, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.