Merge branch 'master' into 3112-report-bug
[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     if tok.nil? or tok.empty?
39       raise ArgumentError.new "locator is nil or empty"
40     end
41
42     m = /^([[:xdigit:]]{32})(\+([[:digit:]]+))?(\+([[:upper:]][[:alnum:]+@_-]*))?$/.match(tok.strip)
43     unless m
44       raise ArgumentError.new "not a valid locator #{tok}"
45     end
46     unless m[2]
47       Rails.logger.debug ArgumentError.new "missing size hint on #{tok}"
48     end
49
50     tokhash, _, toksize, _, trailer = m[1..5]
51     tokhints = []
52     if trailer
53       trailer.split('+').each do |hint|
54         if hint =~ /^[[:upper:]][[:alnum:]@_-]+$/
55           tokhints.push(hint)
56         else
57           raise ArgumentError.new "unknown hint #{hint}"
58         end
59       end
60     end
61
62     Locator.new(tokhash, toksize, tokhints)
63   end
64
65   # Returns the signature hint supplied with this locator,
66   # or nil if the locator was not signed.
67   def signature
68     @hints.grep(/^A/).first
69   end
70
71   # Returns an unsigned Locator.
72   def without_signature
73     Locator.new(@hash, @size, @hints.reject { |o| o.start_with?("A") })
74   end
75
76   def strip_hints
77     Locator.new(@hash, @size, [])
78   end
79
80   def strip_hints!
81     @hints = []
82     self
83   end
84
85   def hash
86     @hash
87   end
88
89   def size
90     @size
91   end
92
93   def hints
94     @hints
95   end
96
97   def to_s
98     if @size
99       [ @hash, @size, *@hints ].join('+')
100     else
101       [ @hash, *@hints ].join('+')
102     end
103   end
104 end