File: parser.rb

package info (click to toggle)
ruby-simple-oauth 0.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 372 kB
  • sloc: ruby: 1,722; makefile: 4; sh: 4
file content (107 lines) | stat: -rw-r--r-- 3,317 bytes parent folder | download
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