+
+ # Verify that a given manifest is valid according to
+ # https://dev.arvados.org/projects/arvados/wiki/Keep_manifest_format
+ 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[0..-2].split(/ /)
+ raise ArgumentError.new "Manifest invalid for stream #{line_count}: missing stream name" if words.empty?
+
+ count = 0
+
+ word = words.shift
+ 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
+ word = words.shift
+ while word =~ Locator::LOCATOR_REGEXP
+ word = words.shift
+ count += 1
+ end
+ raise ArgumentError.new "Manifest invalid for stream #{line_count}: missing or invalid locator #{word.inspect if word}" if count == 0
+
+ count = 0
+ 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
+
+ if word
+ raise ArgumentError.new "Manifest invalid for stream #{line_count}: invalid file token #{word.inspect}"
+ 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