2800: Allow api() caller to specify api host and token.
[arvados.git] / services / api / app / models / locator.rb
1 # A Locator is used to parse and manipulate Keep locator strings.
2 #
3 # Locators obey the following syntax:
4 #
5 #   locator      ::= address hint*
6 #   address      ::= digest size-hint
7 #   digest       ::= <32 hexadecimal digits>
8 #   size-hint    ::= "+" [0-9]+
9 #   hint         ::= "+" hint-type hint-content
10 #   hint-type    ::= [A-Z]
11 #   hint-content ::= [A-Za-z0-9@_-]+
12 #
13 # Individual hints may have their own required format:
14
15 #   sign-hint      ::= "+A" <40 lowercase hex digits> "@" sign-timestamp
16 #   sign-timestamp ::= <8 lowercase hex digits>
17
18 class Locator
19   def initialize(hasharg, sizearg, hintarg)
20     @hash = hasharg
21     @size = sizearg
22     @hints = hintarg
23   end
24
25   # Locator.parse returns a Locator object parsed from the string tok.
26   # Returns nil if tok could not be parsed as a valid locator.
27   def self.parse(tok)
28     begin
29       Locator.parse!(tok)
30     rescue ArgumentError => e
31       nil
32     end
33   end
34
35   # Locator.parse! returns a Locator object parsed from the string tok,
36   # raising an ArgumentError if tok cannot be parsed.
37   def self.parse!(tok)
38     m = /^([[:xdigit:]]{32})(\+([[:digit:]]+))?(\+([[:upper:]][[:alnum:]+@_-]*))?$/.match(tok.strip)
39     unless m
40       raise ArgumentError.new "could not parse #{tok}"
41     end
42
43     tokhash, _, toksize, _, trailer = m[1..5]
44     tokhints = []
45     if trailer
46       trailer.split('+').each do |hint|
47         if hint =~ /^[[:upper:]][[:alnum:]@_-]+$/
48           tokhints.push(hint)
49         else
50           raise ArgumentError.new "unknown hint #{hint}"
51         end
52       end
53     end
54
55     Locator.new(tokhash, toksize, tokhints)
56   end
57
58   # Returns the signature hint supplied with this locator,
59   # or nil if the locator was not signed.
60   def signature
61     @hints.grep(/^A/).first
62   end
63
64   # Returns an unsigned Locator.
65   def without_signature
66     Locator.new(@hash, @size, @hints.reject { |o| o.start_with?("A") })
67   end
68
69   def hash
70     @hash
71   end
72
73   def size
74     @size
75   end
76
77   def hints
78     @hints
79   end
80
81   def to_s
82     [ @hash, @size, *@hints ].join('+')
83   end
84 end