Безопасное экранирование и чтение пути к файлу в ruby - PullRequest
0 голосов
/ 14 мая 2010

Мне нужно сохранить немного информации о некоторых файлах. Ничего особенного, поэтому я подумал, что мне нужно будет добавить одну строчку для каждого текстового файла. Примерно так:

# write
io.print "%i %s %s\n" % [File.mtime(fname), fname, Digest::SHA1.file(fname).hexdigest]
# read
io.each do |line|
  mtime, name, hash = line.scanf "%i %s %s"
end

Конечно, это не работает, потому что имя файла может содержать пробелы (прерывая scanf) и разрывы строк (прерывая IO # каждый).

Проблему разрыва строки можно избежать, отказавшись от использования каждого из них и выполнив кучу get ('')

while not io.eof?
  mtime = Time.at(io.gets(" ").to_i)
  name = io.gets " "
  hash = io.gets "\n"
end

Другое дело - пробелы в именах. Теперь нам нужно сбежать.
примечание: мне нравится пробел в качестве разделителя записей, но у меня не возникнет проблем с его изменением для более удобного использования. В случае имен файлов, однако, единственное, что может помочь, это ascii nul "\ 0", но файл с разделителем nul больше не является текстовым файлом ...

Изначально у меня была стена текста с подробным описанием итераций моей борьбы за создание правильной экранирующей функции и ее взаимностью, но это было просто скучно и не очень полезно. Я просто дам вам окончательный результат:

def write_name(io, val)
  io << val.gsub(/([\\ ])/, "\\\\\\1") # yes that' 6 backslashes !
end

def read_name(io)
  name, continued = "", true
  while continued
    continued = false
    name += io.gets(' ').gsub(/\\(.)/) do |c|
      if c=="\\\\"
        "\\"
      elsif c=="\\ "
        continued=true
        " "
      else
        raise "unexpected backslash escape  : %p (%s %i)" % [c, io.path, io.pos]
      end
    end
  end
  return name.chomp(' ')
end

Я совсем не доволен read_name. Слишком долго и неловко, я чувствую, что это не должно быть так сложно.

Пытаясь заставить это работать, я пытался найти другие способы:

  • способ кодирования bittorrent / сериализации php: добавьте префикс имени файла с длиной имени, затем просто io.read (name_len.to_i). Это работает, но это настоящая лава, чтобы отредактировать файл вручную. На данный момент мы на полпути к двоичному формату.

  • String # inspect: Это выглядит специально для этой цели! За исключением того, что кажется, что единственный способ вернуть значение через eval. Ненавижу идею об оценке строки, которую я не сгенерировал из надежных данных.

Итак. Мнения? Есть ли какая-нибудь библиотека, которая может сделать все это? Я что-то упускаю из виду? Как бы вы это сделали?

Ответы [ 2 ]

1 голос
/ 14 мая 2010

CSV, как уже упоминалось, является хорошим выбором. Другой - YAML («Yaml - не язык разметки»), который может обрабатывать больше произвольных данных, чем CSV. Вот некоторые данные:

require 'pp'
require 'yaml'

h = {
  :first_name => 'Fred',
  :last_name => 'Flinstone',
  :children => ['Bam Bam', 'Pebbles'],
  :exclamation => 'Yabba Dabba Doo',
}

Давайте запишем данные в файл в формате YAML:

File.open('/tmp/foo.yaml', 'w') do |file|
  file.write h.to_yaml
end

Теперь посмотрим, как выглядит YAML:

$ cat /tmp/foo.yaml
---
:exclamation: Yabba Dabba Doo
:first_name: Fred
:last_name: Flinstone
:children:
- Bam Bam
- Pebbles

И, наконец, давайте восстановим данные из файла YAML:

pp YAML.load_file('/tmp/foo.yaml')
# => {:exclamation=>"Yabba Dabba Doo",
# =>  :first_name=>"Fred",
# =>  :last_name=>"Flinstone",
# =>  :children=>["Bam Bam", "Pebbles"]}
1 голос
/ 14 мая 2010

Когда вы говорите «сохранить», вы имеете в виду хранить информацию в файле?

Вы можете использовать CSV-модуль из стандартной библиотеки Ruby. Это будет означать, что ваш разделитель - запятая, а не пробел, но он будет обрабатывать все побеги и побеги для вас.

  • Если значение содержит пробелы, это значение заключено в "quotes"

  • Если значение содержит кавычки, то символ кавычки экранируется как 2 символа кавычки, например, "hello" станет """hello"""

Для записи деталей в файл:

require 'csv'

outfile = File.open('csvout', 'wb')
CSV::Writer.generate(outfile) do |csv|
  csv << [File.mtime(fname), fname, Digest::SHA1.file(fname).hexdigest]
end
outfile.close

Чтобы прочитать их обратно:

CSV::Reader.parse(File.open('csvout', 'rb')) do |row|
  p row
end
...