diff --git a/spec/std/oauth/params_spec.cr b/spec/std/oauth/params_spec.cr deleted file mode 100644 index 787987c53f15..000000000000 --- a/spec/std/oauth/params_spec.cr +++ /dev/null @@ -1,12 +0,0 @@ -require "spec" -require "oauth" - -describe OAuth::Params do - it "builds" do - params = OAuth::Params.new - params.add "foo", "value1" - params.add "bar", "a+b" - params.add "a=", "=/=" - params.to_s.should eq("a%253D%3D%253D%252F%253D%26bar%3Da%252Bb%26foo%3Dvalue1") - end -end diff --git a/src/oauth/params.cr b/src/oauth/params.cr deleted file mode 100644 index 637c39905bda..000000000000 --- a/src/oauth/params.cr +++ /dev/null @@ -1,28 +0,0 @@ -# :nodoc: -struct OAuth::Params - def initialize - @params = [] of {String, String} - end - - def add(key : String, value : String?) : Nil - if value - @params << {URI.encode_www_form(key, space_to_plus: false), URI.encode_www_form(value, space_to_plus: false)} - end - end - - def add_query(query : String) : Nil - URI::Params.parse(query) do |key, value| - add key, value - end - end - - def to_s(io : IO) : Nil - @params.sort_by! &.[0] - @params.each_with_index do |(key, value), i| - io << "%26" if i > 0 - URI.encode_www_form key, io, space_to_plus: false - io << "%3D" - URI.encode_www_form value, io, space_to_plus: false - end - end -end diff --git a/src/oauth/signature.cr b/src/oauth/signature.cr index f8b46e7e34c8..1b239c0a719e 100644 --- a/src/oauth/signature.cr +++ b/src/oauth/signature.cr @@ -39,7 +39,7 @@ struct OAuth::Signature auth_header.to_s end - private def base_string(request, tls, params) + private def base_string(request, tls, normalized_params : String) host, port = host_and_port(request, tls) String.build do |str| @@ -55,36 +55,45 @@ struct OAuth::Signature uri_path = request.path.presence || "/" URI.encode_www_form(uri_path, str, space_to_plus: false) str << '&' - str << params + URI.encode_www_form(normalized_params, str, space_to_plus: false) end end - private def gather_params(request, ts, nonce) - params = Params.new + private def gather_params(request, ts, nonce) : String + params = URI::Params.new + params.add "oauth_consumer_key", @consumer_key params.add "oauth_nonce", nonce params.add "oauth_signature_method", "HMAC-SHA1" params.add "oauth_timestamp", ts - params.add "oauth_token", @oauth_token + if token = @oauth_token + params.add "oauth_token", token + end params.add "oauth_version", "1.0" @extra_params.try &.each do |key, value| params.add key, value end + # Inline query parsing if query = request.query - params.add_query query + URI::Params.parse(query) do |k, v| + params.add k, v + end end + # Inline form body parsing body = request.body content_type = request.headers["Content-type"]? if body && content_type == "application/x-www-form-urlencoded" form = body.gets_to_end - params.add_query form + URI::Params.parse(form) do |k, v| + params.add k, v + end request.body = form end - params + oauth_normalize_params(params) end private def host_and_port(request, tls) @@ -97,4 +106,24 @@ struct OAuth::Signature {host_header, nil} end end + + private def oauth_normalize_params(params : URI::Params) : String + # Encode names + values first (RFC 5849 ยง3.4.1.3.2) + pairs = params.map do |key, value| + { + URI.encode_www_form(key.to_s, space_to_plus: false), + URI.encode_www_form(value.to_s, space_to_plus: false), + } + end + + # Sort by encoded key then encoded value + pairs.sort_by! { |(k, v)| {k, v} } + + # Build final normalized string WITHOUT re-encoding + String.build do |io| + pairs.join(io, "&") do |(key, value), io| + io << key << "=" << value + end + end + end end