web-dev-qa-db-fra.com

Comment sécuriser une chaîne Ruby pour un système de fichiers?

J'ai des entrées d'utilisateur en tant que noms de fichiers. Bien sûr, ce n’est pas une bonne idée, je souhaite donc tout supprimer, à l'exception de [a-z], [A-Z], [0-9], _ et -.

Par exemple:

my§document$is°°   very&interesting___thisIs%Nice445.doc.pdf

devraient devenir

my_document_is_____very_interesting___thisIs_Nice445_doc.pdf

et idéalement

my_document_is_very_interesting_thisIs_Nice445_doc.pdf

Existe-t-il un moyen agréable et élégant de procéder?

39
marcgg

De 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
26
miku

J'aimerais suggérer une solution différente de l'ancienne. Notez que l'ancien utilise le deprecated returning. En passant, il s'agit de toute façon spécifique à Rails , et vous n'avez pas explicitement mentionné Rails dans votre question (uniquement en tant que balise). En outre, la solution existante ne parvient pas à coder .doc.pdf dans _doc.pdf, comme vous l'avez demandé. Et, bien sûr, cela ne réduit pas les traits de soulignement en un seul.

Voici ma solution:

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

Vous n'avez pas spécifié tous les détails de la conversion. Ainsi, je fais les hypothèses suivantes:

  • Il devrait y avoir au plus une extension de nom de fichier, ce qui signifie qu'il devrait y avoir au plus une période dans le nom de fichier.
  • Les périodes de fin ne marquent pas le début d'une extension
  • Les périodes intermédiaires ne marquent pas le début d'une extension
  • Toute séquence de caractères au-delà de AZ, az, 09 et - doit être réduite en un seul _ (le trait de soulignement est lui-même considéré comme non autorisé, et la chaîne '$%__°#' deviendrait '_' - plutôt que '___' des parties '$%', '__' et '°#')

La partie compliquée de ceci est où je divise le nom de fichier en partie principale et extension. À l'aide d'une expression régulière, je cherche la dernière période, suivie de quelque chose d'autre qu'une période, de sorte qu'il n'y ait pas de périodes suivantes correspondant aux mêmes critères dans la chaîne. Il doit cependant être précédé d'un caractère pour s'assurer que ce n'est pas le premier caractère de la chaîne.

Mes résultats après avoir testé la fonction:

1.9.3p125 :006 > sanitize_filename 'my§document$is°°   very&interesting___thisIs%Nice445.doc.pdf'
 => "my_document_is_very_interesting_thisIs_Nice445_doc.pdf"

ce que je pense est ce que vous avez demandé. J'espère que c'est gentil et assez élégant.

57
Anders Sjöqvist

Si vous utilisez Rails, vous pouvez également utiliser String # parameterize. Ce n’est pas spécialement destiné à cela, mais vous obtiendrez un résultat satisfaisant.

"my§document$is°°   very&interesting___thisIs%Nice445.doc.pdf".parameterize
16
albandiguer

Pour Rails, je souhaitais conserver les extensions de fichier, mais en utilisant parameterize pour le reste des caractères:

filename = "my§doc$is°° very&itng___thsIs%nie445.doc.pdf"
cleaned = filename.split(".").map(&:parameterize).join(".")

Détails et idées de mise en œuvre voir source: 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
0
Blair Anderson

Dans Rails, vous pourrez également utiliser sanitize à partir de ActiveStorage :: Nom du fichier :

ActiveStorage::Filename.new("foo:bar.jpg").sanitized # => "foo-bar.jpg"
ActiveStorage::Filename.new("foo/bar.jpg").sanitized # => "foo-bar.jpg"
0
morgler

Une bibliothèque peut être utile, en particulier si vous souhaitez remplacer des caractères Unicode étranges par ASCII: unidecode .

irb(main):001:0> require 'unidecoder'
=> true
irb(main):004:0> "Grzegżółka".to_ascii
=> "Grzegzolka"
0
Jan Warchoł