Merge branch '2411-check-copyright'
[arvados.git] / sdk / ruby / lib / arvados / keep.rb
index bcab5fc69909623c33eebdbb6ca9ae333ed724e0..b2096b5ea0ebf05248fe45690c4f5b4c377a4be0 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
 module Keep
   class Locator
     # A Locator is used to parse and manipulate Keep locator strings.
@@ -35,7 +39,7 @@ module Keep
     def self.parse(tok)
       begin
         Locator.parse!(tok)
-      rescue ArgumentError => e
+      rescue ArgumentError
         nil
       end
     end
@@ -47,19 +51,19 @@ module Keep
         raise ArgumentError.new "locator is nil or empty"
       end
 
-      m = LOCATOR_REGEXP.match(tok.strip)
+      m = LOCATOR_REGEXP.match(tok)
       unless m
         raise ArgumentError.new "not a valid locator #{tok}"
       end
 
-      tokhash, _, toksize, _, trailer = m[1..5]
+      tokhash, _, toksize, _, _, trailer = m[1..6]
       tokhints = []
       if trailer
         trailer.split('+').each do |hint|
-          if hint =~ /^[[:upper:]][[:alnum:]@_-]+$/
+          if hint =~ /^[[:upper:]][[:alnum:]@_-]*$/
             tokhints.push(hint)
           else
-            raise ArgumentError.new "unknown hint #{hint}"
+            raise ArgumentError.new "invalid hint #{hint}"
           end
         end
       end
@@ -97,8 +101,8 @@ module Keep
   end
 
   class Manifest
-    STREAM_REGEXP = /(\.)((\/+.*[^\/])*)$/
-    FILE_REGEXP = /^[[:digit:]]+:[[:digit:]]+:(?!\/).*[^\/]$/
+    STRICT_STREAM_TOKEN_REGEXP = /^(\.)(\/[^\/\s]+)*$/
+    STRICT_FILE_TOKEN_REGEXP = /^[[:digit:]]+:[[:digit:]]+:([^\s\/]+(\/[^\s\/]+)*)$/
 
     # Class to parse a manifest text and provide common views of that data.
     def initialize(manifest_text)
@@ -112,7 +116,7 @@ module Keep
         stream_name = nil
         block_tokens = []
         file_tokens = []
-        line.scan /\S+/ do |token|
+        line.scan(/\S+/) do |token|
           if stream_name.nil?
             stream_name = unescape token
           elsif file_tokens.empty? and Locator.valid? token
@@ -152,7 +156,7 @@ module Keep
       @text.each_line do |line|
         stream_name = nil
         in_file_tokens = false
-        line.scan /\S+/ do |token|
+        line.scan(/\S+/) do |token|
           if stream_name.nil?
             stream_name = unescape token
           elsif in_file_tokens or not Locator.valid? token
@@ -228,22 +232,25 @@ module Keep
       false
     end
 
-    # Verify that a given manifest is valid as per the manifest format definition.
-    # Valid format: stream name + one or more locators + one or more files for each stream in manifest.
+    # Verify that a given manifest is valid according to
     # https://arvados.org/projects/arvados/wiki/Keep_manifest_format
-    def self.valid?(manifest)
-      raise ArgumentError.new "Invalid manifest: does not end with new line" if !manifest.end_with?("\n")
+    def self.validate! manifest
+      raise ArgumentError.new "No manifest found" if !manifest
+
+      return true if manifest.empty?
+
+      raise ArgumentError.new "Invalid manifest: does not end with newline" if !manifest.end_with?("\n")
       line_count = 0
       manifest.each_line do |line|
         line_count += 1
 
-        words = line.split(/[[:space:]]/)
+        words = line[0..-2].split(/ /)
         raise ArgumentError.new "Manifest invalid for stream #{line_count}: missing stream name" if words.empty?
 
         count = 0
 
         word = words.shift
-        count += 1 if word =~ STREAM_REGEXP and !word.include? '//'
+        count += 1 if word =~ STRICT_STREAM_TOKEN_REGEXP and word !~ /\/\.\.?(\/|$)/
         raise ArgumentError.new "Manifest invalid for stream #{line_count}: missing or invalid stream name #{word.inspect if word}" if count != 1
 
         count = 0
@@ -255,7 +262,7 @@ module Keep
         raise ArgumentError.new "Manifest invalid for stream #{line_count}: missing or invalid locator #{word.inspect if word}" if count == 0
 
         count = 0
-        while(word =~ FILE_REGEXP and !word.include? '//')
+        while word =~ STRICT_FILE_TOKEN_REGEXP and ($~[1].split('/') & ['..','.']).empty?
           word = words.shift
           count += 1
         end
@@ -265,6 +272,21 @@ module Keep
         elsif count == 0
           raise ArgumentError.new "Manifest invalid for stream #{line_count}: no file tokens"
         end
+
+        # Ruby's split() method silently drops trailing empty tokens
+        # (which are not allowed by the manifest format) so we have to
+        # check trailing spaces manually.
+        raise ArgumentError.new "Manifest invalid for stream #{line_count}: trailing space" if line.end_with? " \n"
+      end
+      true
+    end
+
+    def self.valid? manifest
+      begin
+        validate! manifest
+        true
+      rescue ArgumentError
+        false
       end
     end
   end