Как сделать строку Ruby безопасной для файловой системы?
У меня есть записи пользователей как имена файлов. Конечно, это не очень хорошая идея, поэтому я хочу отказаться от всего, кроме [a-z]
, [a-z]
, [0-9]
, _
и -
.
Например:
my§document$is°° very&interesting___thisIs%nice445.doc.pdf
должен стать
my_document_is_____very_interesting___thisIs_nice445_doc.pdf
а затем идеально
my_document_is_very_interesting_thisIs_nice445_doc.pdf
Есть ли хороший и элегантный способ для этого?
Ответы
Ответ 1
Из http://devblog.muziboo.com/2008/06/17/attachment-fu-sanitize-filename-regex-and-unicode-gotcha/:
def sanitize_filename(filename)
returning filename.strip do |name|
# NOTE: File.basename doesn't work right with Windows paths on Unix
# get only the filename, not the whole path
name.gsub!(/^.*(\\|\/)/, '')
# Strip out the non-ascii character
name.gsub!(/[^0-9A-Za-z.\-]/, '_')
end
end
Ответ 2
Я хотел бы предложить решение, которое отличается от старого. Обратите внимание, что старый использует устаревший returning
. Кстати, это в любом случае , характерное для Rails, и вы явно не упоминали Rails в своем вопросе (только как тег). Кроме того, существующее решение не может кодировать .doc.pdf
в _doc.pdf
, как вы просили. И, конечно же, он не сбрасывает символы подчеркивания в один.
Здесь мое решение:
def sanitize_filename(filename)
# Split the name when finding a period which is preceded by some
# character, and is followed by some character other than a period,
# if there is no following period that is followed by something
# other than a period (yeah, confusing, I know)
fn = filename.split /(?<=.)\.(?=[^.])(?!.*\.[^.])/m
# We now have one or two parts (depending on whether we could find
# a suitable period). For each of these parts, replace any unwanted
# sequence of characters with an underscore
fn.map! { |s| s.gsub /[^a-z0-9\-]+/i, '_' }
# Finally, join the parts with a period and return the result
return fn.join '.'
end
Вы не указали всю информацию об конверсии. Таким образом, я делаю следующие предположения:
- Должно быть не более одного расширения имени файла, что означает, что должно быть не более одного периода в имени файла
- Прослеживающие периоды не отмечают начало расширения
- Ведущие периоды не отмечают начало расширения
- Любая последовательность символов за пределами
A
- Z
, A
- Z
, 0
- 9
и -
должна быть свернута в один _
(т.е. подчеркивание само считается как запрещенный символ, а строка '$%__°#'
станет '_'
- а не '___'
из частей '$%'
, '__'
и '°#'
)
Сложная часть этого - это то, где я разбил имя файла на основную часть и расширение. С помощью регулярного выражения я ищу последний период, за которым следует что-то другое, чем период, так что не существует следующих периодов, соответствующих тем же критериям в строке. Однако ему должен предшествовать некоторый символ, чтобы убедиться, что это не первый символ в строке.
Мои результаты тестирования функции:
1.9.3p125 :006 > sanitize_filename 'my§document$is°° very&interesting___thisIs%nice445.doc.pdf'
=> "my_document_is_very_interesting_thisIs_nice445_doc.pdf"
который я думаю, это то, что вы просили. Надеюсь, это хорошо и элегантно.
Ответ 3
Если вы используете Rails, вы также можете использовать параметр String #. Это не особенно важно для этого, но вы получите удовлетворительный результат.
"my§document$is°° very&interesting___thisIs%nice445.doc.pdf".parameterize
Ответ 4
Для Rails я обнаружил, что хочу сохранить любые расширения файлов, но используя parameterize
для остальной части символов:
filename = "my§doc$is°° very&itng___thsIs%nie445.doc.pdf"
cleaned = filename.split(".").map(&:parameterize).join(".")
Детали и идеи реализации см. в источнике: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/transliterate.rb
def parameterize(string, separator: "-", preserve_case: false)
# Turn unwanted chars into the separator.
parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
#... some more stuff
end
Ответ 5
Есть библиотека, которая может быть полезна, особенно если вы заинтересованы в замене странных символов Unicode на ASCII: unidecode.
irb(main):001:0> require 'unidecoder'
=> true
irb(main):004:0> "Grzegżółka".to_ascii
=> "Grzegzolka"
Ответ 6
В Rails вы также можете использовать sanitize
из ActiveStorage :: Filename:
ActiveStorage::Filename.new("foo:bar.jpg").sanitized # => "foo-bar.jpg"
ActiveStorage::Filename.new("foo/bar.jpg").sanitized # => "foo-bar.jpg"