signature_parser.rb 1.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940
  1. # frozen_string_literal: true
  2. class SignatureParser
  3. class ParsingError < StandardError; end
  4. # The syntax of this header is defined in:
  5. # https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-4
  6. # See https://datatracker.ietf.org/doc/html/rfc7235#appendix-C
  7. # and https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
  8. # In addition, ignore a `Signature ` string prefix that was added by old versions
  9. # of `node-http-signatures`
  10. TOKEN_RE = /[0-9a-zA-Z!#$%&'*+.^_`|~-]+/
  11. # qdtext and quoted_pair are not exactly according to spec but meh
  12. QUOTED_STRING_RE = /"([^\\"]|(\\.))*"/
  13. PARAM_RE = /(?<key>#{TOKEN_RE})\s*=\s*((?<value>#{TOKEN_RE})|(?<quoted_value>#{QUOTED_STRING_RE}))/
  14. def self.parse(raw_signature)
  15. # Old versions of node-http-signature add an incorrect "Signature " prefix to the header
  16. raw_signature = raw_signature.delete_prefix('Signature ')
  17. params = {}
  18. scanner = StringScanner.new(raw_signature)
  19. # Use `skip` instead of `scan` as we only care about the subgroups
  20. while scanner.skip(PARAM_RE)
  21. # This is not actually correct with regards to quoted pairs, but it's consistent
  22. # with our previous implementation, and good enough in practice.
  23. params[scanner[:key]] = scanner[:value] || scanner[:quoted_value][1...-1]
  24. scanner.skip(/\s*/)
  25. return params if scanner.eos?
  26. raise ParsingError unless scanner.skip(/\s*,\s*/)
  27. end
  28. raise ParsingError
  29. end
  30. end