class Vines::Storage::Local
A storage implementation that persists data to YAML files on the local file system.
Public Class Methods
# File lib/vines/storage/local.rb, line 11 def initialize(&block) @dir = nil instance_eval(&block) unless @dir && File.directory?(@dir) && File.writable?(@dir) raise 'Must provide a writable storage directory' end %w[user vcard fragment].each do |sub| sub = File.expand_path(sub, @dir) Dir.mkdir(sub, 0700) unless File.exists?(sub) end end
Public Instance Methods
# File lib/vines/storage/local.rb, line 88 def destroy_message(id) # do nothing end
# File lib/vines/storage/local.rb, line 24 def dir(dir=nil) dir ? @dir = File.expand_path(dir) : @dir end
# File lib/vines/storage/local.rb, line 66 def find_fragment(jid, node) jid = JID.new(jid).bare.to_s return if jid.empty? file = 'fragment/%s' % fragment_id(jid, node) Nokogiri::XML(read(file)).root rescue nil end
# File lib/vines/storage/local.rb, line 80 def find_messages(jid) {} end
# File lib/vines/storage/local.rb, line 28 def find_user(jid) jid = JID.new(jid).bare.to_s file = "user/#{jid}" unless jid.empty? record = YAML.load(read(file)) rescue nil return User.new(jid: jid).tap do |user| user.name, user.password = record.values_at('name', 'password') (record['roster'] || {}).each_pair do |jid, props| user.roster << Contact.new( jid: jid, name: props['name'], subscription: props['subscription'], ask: props['ask'], groups: props['groups'] || []) end end if record end
# File lib/vines/storage/local.rb, line 53 def find_vcard(jid) jid = JID.new(jid).bare.to_s return if jid.empty? file = "vcard/#{jid}" Nokogiri::XML(read(file)).root rescue nil end
# File lib/vines/storage/local.rb, line 73 def save_fragment(jid, node) jid = JID.new(jid).bare.to_s return if jid.empty? file = 'fragment/%s' % fragment_id(jid, node) save(file, node.to_xml) end
# File lib/vines/storage/local.rb, line 84 def save_message(from, to, message) # do nothing end
# File lib/vines/storage/local.rb, line 45 def save_user(user) record = {'name' => user.name, 'password' => user.password, 'roster' => {}} user.roster.each do |contact| record['roster'][contact.jid.bare.to_s] = contact.to_h end save("user/#{user.jid.bare}", YAML.dump(record)) end
# File lib/vines/storage/local.rb, line 60 def save_vcard(jid, card) jid = JID.new(jid).bare.to_s return if jid.empty? save("vcard/#{jid}", card.to_xml) end
Private Instance Methods
Resolves a relative file name into an absolute path inside the storage directory.
file - A fully-qualified or relative file name String.
Returns the fully-qualified file path String.
Raises RuntimeError if the resolved path is outside of the storage directory. This prevents directory path traversals with maliciously crafted JIDs.
# File lib/vines/storage/local.rb, line 104 def absolute_path(file) File.expand_path(file, @dir).tap do |absolute| parent = File.dirname(File.dirname(absolute)) raise 'path traversal' unless parent == @dir end end
Generates a unique file id for the user's private XML fragment.
Private XML fragment storage needs to uniquely identify fragment files on disk. We combine the user's JID
with a SHA-1 hash of the element's name and namespace to avoid special characters in the file name.
jid - A bare JID
identifying the user who owns this fragment. node - A Nokogiri::XML::Node for the XML to be stored.
Returns an id String suitable for use in a file name.
# File lib/vines/storage/local.rb, line 145 def fragment_id(jid, node) id = Digest::SHA1.hexdigest("#{node.name}:#{node.namespace.href}") "#{jid}-#{id}" end
Read the file from the filesystem and return its contents as a String. All files are assumed to be encoded as UTF-8.
file - A fully-qualified or relative file name String.
Returns the file content as a UTF-8 encoded String.
# File lib/vines/storage/local.rb, line 117 def read(file) file = absolute_path(file) File.read(file, encoding: 'utf-8') end
Write the content to the file. Make sure to consistently encode files we read and write as UTF-8.
file - A fully-qualified or relative file name String. content - The String to write.
Returns nothing.
# File lib/vines/storage/local.rb, line 129 def save(file, content) file = absolute_path(file) File.open(file, 'w:utf-8') {|f| f.write(content) } File.chmod(0600, file) end