1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
require "strscan"
require_relative "errors"
module SimpleOAuth
# Parses OAuth Authorization headers
#
# @api private
class Parser
# Pattern to match OAuth key-value pairs: key="value"
PARAM_PATTERN = /(\w+)="([^"]*)"\s*(,?)\s*/
# OAuth scheme prefix
OAUTH_PREFIX = /OAuth\s+/
# The StringScanner instance for parsing the header
#
# @return [StringScanner] the scanner
attr_reader :scanner
# The parsed OAuth attributes
#
# @return [Hash{Symbol => String}] the parsed attributes
attr_reader :attributes
# Creates a new Parser for the given header string
#
# @param header [String, #to_s] the OAuth Authorization header string
# @return [Parser] a new parser instance
def initialize(header)
@scanner = StringScanner.new(header.to_s)
@attributes = {}
end
# Parses the OAuth Authorization header
#
# @param valid_keys [Array<Symbol>] the valid OAuth parameter keys
# @return [Hash{Symbol => String}] the parsed attributes
# @raise [SimpleOAuth::ParseError] if the header is malformed
def parse(valid_keys)
scan_oauth_prefix
scan_params(valid_keys)
verify_complete
attributes
end
private
# Scans and validates the OAuth prefix
#
# @return [void]
# @raise [SimpleOAuth::ParseError] if the header doesn't start with "OAuth "
def scan_oauth_prefix
return if scanner.scan(OAUTH_PREFIX)
raise ParseError, "Authorization header must start with 'OAuth '"
end
# Scans all key-value parameters from the header
#
# @param valid_keys [Array<Symbol>] the valid OAuth parameter keys
# @return [void]
def scan_params(valid_keys)
while scanner.scan(PARAM_PATTERN)
key = scanner[1] #: String
value = scanner[2] #: String
comma = scanner[3] #: String
validate_comma_separator(key, comma)
store_if_valid(key, value, valid_keys)
end
end
# Validates that a comma separator exists between parameters
#
# @param key [String] the parameter key for error messages
# @param comma [String] the comma separator (empty string if missing)
# @return [void]
# @raise [SimpleOAuth::ParseError] if comma is missing and more content follows
def validate_comma_separator(key, comma)
return if !comma.empty? || scanner.eos?
raise ParseError,
"Expected comma after '#{key}' parameter at position #{scanner.pos}: #{scanner.rest.inspect}"
end
# Stores the parameter if it's a valid OAuth key
#
# @param key [String] the raw parameter key (e.g., "oauth_consumer_key")
# @param value [String] the parameter value
# @param valid_keys [Array<Symbol>] the valid OAuth parameter keys
# @return [void]
def store_if_valid(key, value, valid_keys)
parsed_key = valid_keys.find { |k| "oauth_#{k}".eql?(key) }
attributes[parsed_key] = Header.unescape(value) if parsed_key
end
# Verifies that the entire header was parsed
#
# @return [void]
# @raise [SimpleOAuth::ParseError] if unparsed content remains
def verify_complete
return if scanner.eos?
raise ParseError,
"Could not parse parameter at position #{scanner.pos}: #{scanner.rest.inspect}"
end
end
end
|