#!/usr/bin/ruby ## plist.rb v0.1 : Parses XML PropertyList ## Copyright (C) 2010 Franck GUENICHOT ## franck {dot} guenichot {at} orange {dot} fr ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License ## as published by the Free Software Foundation; either version 3 ## of the License, or any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see . ## Written for the Network Forensics Puzzle #3 ## http://forensicscontest.com require "rexml/document" require "rexml/streamlistener" ## CLASSES ## these ones are taken from the book: "Practical Ruby for System Administration" by André Ben Amou ## with minor changes. class PListArray < Array attr_accessor :parent alias :add_object :push def print_all elems = [] self.each {|elem| if elem.class == PListHash elems << " " elems << elem.print_all elsif elem.class == PListArray elems << " " elems << elem.print_all else elems << elem end } elems end end class PListHash < Hash attr_accessor :next_key, :parent def print_all result = [] self.each {|key,value| if value.class == PListHash result << "#{key}: " result << value.print_all result << " " elsif value.class == PListArray result << value.print_all result << " " else result << "#{key}: #{value}" end } result end def add_object(object) raise "no key set" unless @next_key self[@next_key] = object @next_key = nil end end class PListParser include REXML::StreamListener attr_reader :root def initialize @to_read = nil @root = nil @version = 1.0 end def tag_start(name, attributes) # handle a start tag case name when "plist" then @version = attributes["version"] when "array" then add_object(PListArray.new) when "dict" then add_object(PListHash.new) when "data", "integer", "key", "real", "string","true","false","date" then @to_read = name else raise "unknown tag: #{name}" end end def tag_end(name) if @to_read then @to_read = nil else @root = @root.parent || @root end end def text(string) return unless @to_read if @to_read == "key" raise "cannot use key as not within hash" unless @root.is_a?(Hash) @root.next_key = string return end object = case @to_read when "data" then string.strip when "integer" then string.to_i when "real" then string.to_f when "string" then string when "date" then string when "true" then "true" when "false" then "false" end add_object(object) end def self.parse(source) parser = new REXML::Document.parse_stream(source, parser) parser.root end private def add_object(object) @root.add_object(object) if @root if object.is_a?(Array) or object.is_a?(Hash) object.parent = @root @root = object end end end ## well, the minimum... filename = ARGV[ARGV.length-2] query = ARGV[ARGV.length-1] plist = PListParser.parse(File.open(filename)) pagetype = plist['page-type']['template-name'] pagetitle = plist['title'] items_array = plist['items'] puts "Page type is #{pagetype}" puts "Title: #{pagetitle}" puts items_array.print_all #End of plist.rb