Merge branch 'main' into 18842-arv-mount-disk-config
[arvados.git] / sdk / ruby / lib / arvados / keep.rb
index d271610339f98aef135b996b6512e13579473a77..e391b7a6ca027aacad81a38d85ef7ddd05133c43 100644 (file)
@@ -101,9 +101,14 @@ module Keep
   end
 
   class Manifest
-    STRICT_STREAM_TOKEN_REGEXP = /^(\.)(\/[^\/\s]+)*$/
-    STRICT_FILE_TOKEN_REGEXP = /^[[:digit:]]+:[[:digit:]]+:([^\s\/]+(\/[^\s\/]+)*)$/
-    EMPTY_DOT_FILE_TOKEN_REGEXP = /^0:0:\.$/
+    STREAM_TOKEN_REGEXP = /^([^\000-\040\\]|\\[0-3][0-7][0-7])+$/
+    STREAM_NAME_REGEXP = /^(\.)(\/[^\/]+)*$/
+
+    EMPTY_DIR_TOKEN_REGEXP = /^0:0:\.$/ # The exception when a file can have '.' as a name
+    FILE_TOKEN_REGEXP = /^[[:digit:]]+:[[:digit:]]+:([^\000-\040\\]|\\[0-3][0-7][0-7])+$/
+    FILE_NAME_REGEXP = /^[[:digit:]]+:[[:digit:]]+:([^\/]+(\/[^\/]+)*)$/
+
+    NON_8BIT_ENCODED_CHAR = /[^\\]\\[4-7][0-7][0-7]/
 
     # Class to parse a manifest text and provide common views of that data.
     def initialize(manifest_text)
@@ -132,15 +137,14 @@ module Keep
       end
     end
 
-    def self.unescape(s, except=[])
+    def self.unescape(s)
       return nil if s.nil?
 
       # Parse backslash escapes in a Keep manifest stream or file name.
       s.gsub(/\\(\\|[0-7]{3})/) do |_|
-        if $1 == '\\'
+        case $1
+        when '\\'
           '\\'
-        elsif except.include? $1
-          $1
         else
           $1.to_i(8).chr
         end
@@ -244,7 +248,7 @@ module Keep
     end
 
     # Verify that a given manifest is valid according to
-    # https://arvados.org/projects/arvados/wiki/Keep_manifest_format
+    # https://dev.arvados.org/projects/arvados/wiki/Keep_manifest_format
     def self.validate! manifest
       raise ArgumentError.new "No manifest found" if !manifest
 
@@ -261,8 +265,9 @@ module Keep
         count = 0
 
         word = words.shift
-        unescaped_word = unescape(word, except=["040"])
-        count += 1 if unescaped_word =~ STRICT_STREAM_TOKEN_REGEXP and unescaped_word !~ /\/\.\.?(\/|$)/
+        raise ArgumentError.new "Manifest invalid for stream #{line_count}: >8-bit encoded chars not allowed on stream token #{word.inspect}" if word =~ NON_8BIT_ENCODED_CHAR
+        unescaped_word = unescape(word)
+        count += 1 if word =~ STREAM_TOKEN_REGEXP and unescaped_word =~ STREAM_NAME_REGEXP and unescaped_word !~ /\/\.\.?(\/|$)/
         raise ArgumentError.new "Manifest invalid for stream #{line_count}: missing or invalid stream name #{word.inspect if word}" if count != 1
 
         count = 0
@@ -274,8 +279,9 @@ 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 unescape(word) =~ EMPTY_DOT_FILE_TOKEN_REGEXP or
-          (unescape(word, except=["040"]) =~ STRICT_FILE_TOKEN_REGEXP and ($~[1].split('/') & ['..', '.']).empty?)
+        raise ArgumentError.new "Manifest invalid for stream #{line_count}: >8-bit encoded chars not allowed on file token #{word.inspect}" if word =~ NON_8BIT_ENCODED_CHAR
+        while unescape(word) =~ EMPTY_DIR_TOKEN_REGEXP or
+          (word =~ FILE_TOKEN_REGEXP and unescape(word) =~ FILE_NAME_REGEXP and ($~[1].split('/') & ['..', '.']).empty?)
           word = words.shift
           count += 1
         end