Merge branch '2800-python-global-state' into 2800-pgs
[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 strip_hints
70     Locator.new(@hash, @size, [])
71   end
72
73   def strip_hints!
74     @hints = []
75     self
76   end
77
78   def hash
79     @hash
80   end
81
82   def size
83     @size
84   end
85
86   def hints
87     @hints
88   end
89
90   def to_s
91     [ @hash, @size, *@hints ].join('+')
92   end
93 end