如何在Ruby on Rails中检测PDF、DOC/DOCX文件是否受密码保护?
刚好我之前在Rails项目里处理过类似的文件上传校验需求,给你整理几种靠谱的纯Ruby实现方案,针对PDF、DOC、DOCX分别说明:
最常用的是ruby-pdf-reader这个库,它能直接读取PDF的元数据并检测加密状态。
首先在Gemfile里添加:
gem 'pdf-reader'
然后执行bundle install。
接下来写检测方法:
def password_protected_pdf?(file_path) reader = PDF::Reader.new(file_path) !reader.decrypt.nil? # 如果需要解密,说明文件是加密的 rescue PDF::Reader::EncryptedPDFError true # 直接抛出加密异常,肯定是受密码保护的 rescue StandardError => e # 处理文件损坏或其他异常,这里可以根据需求返回false或者抛出错误 false end
解释一下:这个方法会先尝试初始化PDF读取器,要是遇到加密的PDF,要么会抛出EncryptedPDFError,要么reader.decrypt会返回非nil值(表示需要密码才能解密)。两种情况都可以判定为受密码保护。
DOC是旧版的OLE格式文件,我们可以用ruby-ole库来检查它的加密属性。
先添加Gem:
gem 'ruby-ole'
检测方法示例:
require 'ole/storage' def password_protected_doc?(file_path) ole = Ole::Storage.open(file_path) # 加密的DOC会有一个名为"\x05Password"的OLE流 ole.entries.any? { |entry| entry.name == "\x05Password" } rescue Ole::Storage::FormatError => e # 文件不是合法的DOC或者损坏 false rescue StandardError => e false ensure ole.close if ole end
这个方法是通过检查OLE存储里是否存在特定的加密标记流来判断的,这是旧版DOC加密的典型特征。
DOCX本质是ZIP压缩包,加密的DOCX会在压缩包内包含加密相关的配置文件,比如EncryptionInfo.xml。我们可以用rubyzip库来读取ZIP内容进行判断。
添加Gem:
gem 'rubyzip'
检测方法:
require 'zip' def password_protected_docx?(file_path) Zip::File.open(file_path) do |zip_file| # 加密的DOCX必然包含EncryptionInfo.xml文件 zip_file.find_entry('EncryptionInfo.xml').present? end rescue Zip::Error => e # 文件不是合法的DOCX或者损坏 false rescue StandardError => e false end
另外,如果你想更严谨,还可以尝试读取ZIP里的XML内容,不过只要存在EncryptionInfo.xml基本就能确定是受密码保护的DOCX了。
比如你用Active Storage的话,可以在模型里添加自定义验证:
class Attachment < ApplicationRecord has_one_attached :file validate :file_not_password_protected private def file_not_password_protected return unless file.attached? temp_file = Tempfile.new(['upload', file.filename.extension_with_delimiter]) temp_file.binmode temp_file.write(file.download) temp_file.close case file.filename.extension.downcase when 'pdf' if password_protected_pdf?(temp_file.path) errors.add(:file, '不能上传受密码保护的PDF文件') end when 'doc' if password_protected_doc?(temp_file.path) errors.add(:file, '不能上传受密码保护的DOC文件') end when 'docx' if password_protected_docx?(temp_file.path) errors.add(:file, '不能上传受密码保护的DOCX文件') end end ensure temp_file.unlink if temp_file end end
注意这里要把前面写的三个检测方法放到模型里或者一个工具类里,方便调用。
另外要提醒的是:这些方法只能检测是否存在密码保护,不能破解密码,完全符合你的需求。如果遇到异常文件(比如损坏的文件),要做好异常处理,避免误判。
内容的提问来源于stack exchange,提问作者nurav

