ipreader 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ .redcar
5
+ test/try_*
6
+ test/tmp/*
7
+ bin/try_*
8
+ coverage/*
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :test do
4
+ gem "fakefs", :git => "git://github.com/vertiginous/fakefs.git"
5
+ gem "mocha"
6
+ end
7
+
8
+ gem "haml", ">=3.0.24"
9
+ gem "sqlite3-ruby", :require => "sqlite3"
10
+
11
+ #group :development do
12
+ #end
13
+
14
+ # Specify your gem's dependencies in ipreader.gemspec
15
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,31 @@
1
+ GIT
2
+ remote: git://github.com/vertiginous/fakefs.git
3
+ revision: 7a35534e02128c95650d59fd95d34ef7cb04313b
4
+ specs:
5
+ fakefs (0.3.1)
6
+
7
+ PATH
8
+ remote: .
9
+ specs:
10
+ ipreader (0.0.1)
11
+
12
+ GEM
13
+ remote: http://rubygems.org/
14
+ specs:
15
+ haml (3.0.25)
16
+ mocha (0.9.9)
17
+ rake
18
+ rake (0.8.7)
19
+ sqlite3 (1.3.3-x86-mingw32)
20
+ sqlite3-ruby (1.3.3)
21
+ sqlite3 (>= 1.3.3)
22
+
23
+ PLATFORMS
24
+ x86-mingw32
25
+
26
+ DEPENDENCIES
27
+ fakefs!
28
+ haml (>= 3.0.24)
29
+ ipreader!
30
+ mocha
31
+ sqlite3-ruby
data/README ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/bin/ext_sms ADDED
@@ -0,0 +1,12 @@
1
+ ##!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), ".."))
4
+
5
+ dir_name = "/users/{user}/AppData/Roaming/Apple\ Computer/MobileSync/Backup/{directory name}"
6
+ output_format = "html"
7
+
8
+ dir_name = ARGV[0] if ARGV.length == 1
9
+ output_format = ARGV[1] if ARGV.length == 2
10
+
11
+ require "ipreader"
12
+ Ipreader.Controller.read_backups(dir_name, output_format)
data/bin/ext_sms.rb ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #$:.unshift File.expand_path(File.join(File.dirname(__FILE__), ".."))
4
+
5
+ require 'optparse'
6
+ require_relative "../ipreader"
7
+
8
+ OptionParser.accept(Date, /(\d+)-(\d+)-(\d+)/) do |d, mon, day, year|
9
+ Date.new( year.to_i, mon.to_i, day.to_i )
10
+ end
11
+
12
+ opts = OptionParser.new
13
+
14
+ opts.banner = "Usage: ext_sms [ options ]"
15
+
16
+ opts.on("-h", "--help", "-?", "show this message") do
17
+ puts opts
18
+ exit
19
+ end
20
+
21
+ format = "html"
22
+ opts.on("-fo", "--format FORMAT", String, "Output format, one of [html, csv, xml]") do |fmt|
23
+ format = fmt
24
+ end
25
+
26
+ backup_path = ""
27
+ opts.on("-p", "--path PATHNAME", "Path to backup directory") do |pth|
28
+ backup_path = pth
29
+ end
30
+
31
+ # TODO add date filters
32
+ filters = {}
33
+ opts.on("-fi", "--filters 'COLUMN_NAME:value'", String, "Filter to apply, columns [address:value[, text:value]]") do |fltrs|
34
+ fltrs.scan(/(\w+):\s*(\w+)/) do |name, value|
35
+ filters[name] = "%" + value + "%"
36
+ end
37
+ end
38
+
39
+ begin
40
+ ARGV << "-h" if ARGV.empty?
41
+ opts.parse!(ARGV)
42
+ backup_reader = Ipreader::Controller.new(backup_path, format, filters)
43
+ puts backup_reader.start
44
+ rescue OptionParser::ParseError => e
45
+ STDERR.puts e.message, "\n", opts
46
+ exit(-1)
47
+ end
48
+
49
+
@@ -0,0 +1,82 @@
1
+ require 'sqlite3'
2
+ require 'date'
3
+
4
+ module Ipreader
5
+
6
+ module Database
7
+
8
+ module Utils
9
+
10
+ def is_sqlite3?(full_name)
11
+ return false if full_name.nil?
12
+ file_data = File.open(full_name, "r")
13
+ file_header = file_data.gets
14
+ file_header =~ /^SQLite format 3/
15
+ end
16
+
17
+ def sms_db?(full_name)
18
+ return false if full_name.nil?
19
+ SQLite3::Database.new(full_name) do |db|
20
+ begin
21
+ if db.execute("SELECT count(*) FROM SQLITE_MASTER WHERE type='table' AND name='message'").flatten.first > 0
22
+ return true
23
+ else
24
+ return false
25
+ end
26
+ rescue SQLite3::SQLException, SQLite3::IOException, NilClass => e
27
+ return false
28
+ end
29
+ end
30
+ end
31
+
32
+ def read_sms_table(full_name, filters = {} )
33
+
34
+ sms_list = Array.new
35
+ SQLite3::Database.new(full_name) do |db|
36
+
37
+ message_table_columns = db.execute2("SELECT * FROM message LIMIT 0").flatten
38
+ column_keys = filters.keys.collect { |k| k.to_s }
39
+ non_matched_columns = column_keys - message_table_columns
40
+ if non_matched_columns.length > 0
41
+ raise ArgumentError, "can't find column [#{non_matched_columns}] in messages"
42
+ exit
43
+ else
44
+ optional_filters = ""
45
+ filters.keys.each do |filter|
46
+ optional_filters += " AND #{filter} LIKE :#{filter}"
47
+ end
48
+ sql = "SELECT rowid, date(date,'unixepoch'), address, text, flags, time(date,'unixepoch')"
49
+ sql += " FROM message"
50
+ sql += " WHERE text is not null #{optional_filters}"
51
+ sql += " ORDER BY address AND date ASC"
52
+
53
+ # puts "sql=#{sql}"
54
+ # sql += " LIMIT 5"
55
+
56
+ begin
57
+ db.execute(sql, filters) do |row|
58
+ direction = case row[4]
59
+ when 2 then "to"
60
+ when 3 then "from"
61
+ else "??"
62
+ end
63
+ # TODO connascence of position
64
+ row_hash = { :rowid => row[0],
65
+ :on_date => row[1],
66
+ :at_time => row[5],
67
+ :address => row[2],
68
+ :direction => direction,
69
+ :text => row[3]
70
+ }
71
+ sms_list << row_hash
72
+ end
73
+ rescue SQLite3::Exception => e
74
+ end
75
+ end
76
+ end
77
+ return sms_list
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,42 @@
1
+ module Ipreader
2
+
3
+ module Display
4
+
5
+ # KNOWN_FORMATS = [ "xml", "csv", "html" ]
6
+ # NO_TEMPLATE = "no template chosen"
7
+ # TEMPLATE_LOCATION = "templates"
8
+
9
+ @missing = ''
10
+
11
+ def which_template(format)
12
+ known_fmt = lambda { |item| KNOWN_FORMATS.include?(item) }
13
+ case format
14
+ when known_fmt
15
+ "#{Ipreader::TEMPLATE_LOCATION}/sms.#{format.strip}.haml"
16
+ else
17
+ @missing = format
18
+ "#{Ipreader::TEMPLATE_LOCATION}/sms.missing.haml"
19
+ end
20
+ end
21
+
22
+ def template_location(templ)
23
+ File.join(File.dirname(__FILE__), which_template(templ))
24
+ end
25
+
26
+ def display_sms( template_type = "html" , conversations = nil)
27
+ temp_loc = template_location(template_type)
28
+ render_template(File.read(temp_loc), conversations)
29
+ end
30
+
31
+ def render_template( template = nil, conversations = nil )
32
+ unless template.nil?
33
+ Haml::Engine.new(template).render( Object.new, :conversations => conversations, :missing => @missing )
34
+ else
35
+ Ipreader::NO_TEMPLATE
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+
42
+ end
data/ipreader/init.rb ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ "rowid","on_date","at_time","direction","text"
2
+ - conversations.each do |sms|
3
+ #{sms[:rowid]},#{sms[:on_date]},#{sms[:at_time]},#{sms[:direction]},#{sms[:text]}
@@ -0,0 +1,10 @@
1
+ !!! Strict
2
+ %html
3
+ %head
4
+ %body
5
+ - conversations.each do |sms|
6
+ %p== #{ sms[:rowid] }
7
+ %p== #{ sms[:on_date] }
8
+ %p== #{ sms[:at_time] }
9
+ %p{ :class => sms[:direction] }
10
+ = sms[:text]
@@ -0,0 +1 @@
1
+ I'am missing a template for "#{missing}"
@@ -0,0 +1,6 @@
1
+ - conversations.each do |sms|
2
+ %rowid== #{ sms[:rowid] }
3
+ %on-date== #{ sms[:on_date] }
4
+ %on-time== #{ sms[:at_time] }
5
+ %direction== #{ sms[:direction] }
6
+ %text== #{ sms[:text] }
@@ -0,0 +1,7 @@
1
+ module Ipreader
2
+ module Backup
3
+ module Reader
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
data/ipreader.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.dirname(__FILE__)
3
+ #$:.push File.join( File.dirname(__FILE__),'ipreader' )
4
+ require "ipreader/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "ipreader"
8
+ s.version = Ipreader::Backup::Reader::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["David Rabbich"]
11
+ s.email = ["davidrabbich@yahoo.co.uk"]
12
+ s.homepage = "http://rubygems.org/gems/ipreader"
13
+ s.summary = %q{ Reads an unencrypted IPhone backup store and queries the sms data within it. }
14
+ s.description = %q{ Takes as input a directory location of the Apple backup store and interrogates the back up data for SMS data }
15
+
16
+ s.rubyforge_project = "ipreader"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+ end
data/ipreader.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'date'
2
+ require 'haml'
3
+ require 'sqlite3'
4
+
5
+ require_relative "ipreader/database_utils"
6
+ require_relative "ipreader/display"
7
+
8
+ module Ipreader
9
+
10
+ KNOWN_FORMATS = [ "xml", "csv", "html" ]
11
+ NO_TEMPLATE = "no template chosen"
12
+ LIBRARY_LOCATION = "ipreader"
13
+ TEMPLATE_LOCATION = "templates"
14
+
15
+ class Controller
16
+
17
+ include Ipreader::Database::Utils
18
+ include Ipreader::Display
19
+
20
+ def initialize(backup_path = nil, format = "html" , filters = {} )
21
+ @conversations = []
22
+ @backup_path = backup_path
23
+ @template_type = format
24
+ @filters = filters
25
+ end
26
+
27
+ def start
28
+ read_backups
29
+ display_sms(@template_type, @conversations.flatten)
30
+ end
31
+
32
+ # control which backup directories to read through
33
+ # TODO more than one backup directory
34
+ # TODO more than one database type
35
+ def read_backups
36
+ Dir.foreach(@backup_path) do |file_name|
37
+ full_name = File.join(@backup_path, file_name)
38
+ unless File.directory?(full_name)
39
+ if is_sqlite3?(full_name)
40
+ @conversations << read_sms_table(full_name, @filters) if sms_db?(full_name)
41
+ end
42
+ end
43
+ end
44
+ # @conversations
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,46 @@
1
+ require_relative "test_helper"
2
+ require_relative "../ipreader/database_utils"
3
+
4
+ require 'fakefs/safe'
5
+
6
+ class TestDatabaseUtils < Test::Unit::TestCase
7
+
8
+ include FakeFS
9
+
10
+ class IpDbUtils
11
+ include Ipreader::Database::Utils
12
+ end
13
+
14
+ def setup
15
+ @ipdbutils = IpDbUtils.new
16
+ FakeFS.activate!
17
+ FileSystem.clear
18
+ end
19
+
20
+ def teardown
21
+ FakeFS.deactivate!
22
+ end
23
+
24
+ must "is_sqlite3? returns true the given file IS in a Sqlite3 format" do
25
+ test_db_path = File.join(test_root, "tmp", "sqlite3_valid_fake.sqlite3" )
26
+ File.open(test_db_path, 'w') { |f| f.write "SQLite format 3" }
27
+ assert @ipdbutils.is_sqlite3?(test_db_path), "first characters of a valid SQLite3 database should be 'SQLite format 3'"
28
+ end
29
+
30
+ must "is_sqlite3? returns false if the given file is NOT a valid Sqlite3 format" do
31
+ test_bad_path = File.join(test_root, "tmp", "sqlite3_invalid_fake.MSSQL" )
32
+ File.open(test_bad_path, 'w') { |f| f.write "SQL Server 2000" }
33
+ refute @ipdbutils.is_sqlite3?(test_bad_path), "should return false to a non SQLite3 file"
34
+ end
35
+
36
+ must "return false if the SQLite3 file is malformed" do
37
+ test_malformed_path = File.join(test_root, "tmp", "sqlite3_malformed_fake.sqlite3" )
38
+ File.open(test_malformed_path, 'w') { |f| f.write "xxxxxxxxxxxxxxSQLite format 3" }
39
+ refute @ipdbutils.is_sqlite3?(test_malformed_path), "first characters are not a valid SQLite3 database"
40
+ end
41
+
42
+ must "fail gracefully if the given file does not exist" do
43
+ refute @ipdbutils.is_sqlite3?(nil), "should return false when no file is given"
44
+ end
45
+
46
+ end
@@ -0,0 +1,98 @@
1
+ require_relative "test_helper"
2
+ require_relative "../ipreader/database_utils"
3
+ require "sqlite3"
4
+
5
+ class TestDatabaseUtils < Test::Unit::TestCase
6
+
7
+ SMS_TABLE_CREATE = "CREATE TABLE message (rowid INTEGER, date TEXT, address TEXT, text TEXT, flags INTEGER)"
8
+
9
+ class IpDbUtils
10
+ include Ipreader::Database::Utils
11
+ end
12
+
13
+ def setup
14
+ @db_name = File.join(File.dirname(__FILE__), "tmp/dummy.sqlite3")
15
+ file_teardown(@db_name) if File.exists?(@db_name)
16
+ @ipdbutils = IpDbUtils.new
17
+ @db = SQLite3::Database.new @db_name
18
+ end
19
+
20
+ def teardown
21
+ @db.close unless @db.closed?
22
+ GC.start
23
+ file_teardown(@db_name)
24
+ end
25
+
26
+ def file_teardown(file_name)
27
+ File.delete(file_name) if File.exists?(file_name)
28
+ end
29
+
30
+ must "recognise an sms backup" do
31
+ @db.execute SMS_TABLE_CREATE
32
+ assert @ipdbutils.sms_db?(@db_name)
33
+ end
34
+
35
+ must "recognise when database is not an sms backup" do
36
+ @db.execute "CREATE TABLE smerf (name VARCHAR(30))"
37
+ refute @ipdbutils.sms_db?(@db_name)
38
+ end
39
+
40
+ must "fail gracefully if passed a file name that is not a SQLite3 database" do
41
+ dummy_file = File.join(test_root, "tmp/empty.sqlite3")
42
+ File.open(dummy_file, "w") { |df| df.puts " " } # "w" truncates existing db contents
43
+ refute @ipdbutils.sms_db?(dummy_file)
44
+ end
45
+
46
+ must "fail gracefully if passed a nil reference" do
47
+ refute @ipdbutils.sms_db?(nil)
48
+ end
49
+
50
+ def setup_testdb
51
+ @db.execute SMS_TABLE_CREATE
52
+ @test_sms = []
53
+ (1..4).each do |test_item|
54
+ insert_sms = "INSERT INTO message (rowid, date, address, text, flags) VALUES( '#{test_item}', '2011-01-31 12:00:00', '+4479867#{test_item}#{test_item}#{test_item}#{test_item}', 'sms text #{test_item}', '2' );"
55
+ @db.execute(insert_sms)
56
+ @test_sms << {
57
+ :rowid => test_item,
58
+ :on_date => "2011/01/31",
59
+ :at_time => "12:00:00",
60
+ :address => "+4479867#{test_item}#{test_item}#{test_item}#{test_item}",
61
+ :direction => 'to',
62
+ :text => "sms text #{test_item}"
63
+ }
64
+ end
65
+ end
66
+
67
+ must "return all rows when no filter is applied" do
68
+ setup_testdb
69
+ test_result = @ipdbutils.read_sms_table(@db_name)
70
+ assert_equal test_result, @test_sms
71
+ assert_equal @test_sms.length, test_result.length
72
+ end
73
+
74
+ must "correctly use the options hash to filter sms data" do
75
+ setup_testdb
76
+ result = []
77
+ result << @test_sms.first
78
+ options = { :address => "+44798671111" }
79
+ test_result = @ipdbutils.read_sms_table(@db_name, options)
80
+ assert_equal test_result, result
81
+ assert_equal test_result.length, 1
82
+ end
83
+
84
+ must "ignore case when filtering sms data" do
85
+ setup_testdb
86
+ options = { :text => "sms text 1" }
87
+ assert_equal @ipdbutils.read_sms_table(@db_name, options).length, 1
88
+ options = { :text => "SMS TEXT 1" }
89
+ assert_equal @ipdbutils.read_sms_table(@db_name, options).length, 1
90
+ end
91
+
92
+ must "ignore surplus options without erroring" do
93
+ setup_testdb
94
+ options = { :telephone => "011232131232" }
95
+ assert_raises(ArgumentError) { @ipdbutils.read_sms_table(@db_name, options) }
96
+ end
97
+
98
+ end
@@ -0,0 +1,66 @@
1
+ require_relative "test_helper"
2
+ require_relative "../ipreader/display"
3
+
4
+ class TestDisplay < Test::Unit::TestCase
5
+
6
+ class IpDisplay
7
+ include Ipreader::Display
8
+ end
9
+
10
+ def setup
11
+ @display = IpDisplay.new
12
+ @unknown_fmt = "any-unknown-format"
13
+ end
14
+
15
+ known_template_formats.each do |fmt|
16
+ must "respond with 'templates/sms.#{fmt}.haml' when display_format gets a known format '#{fmt}'" do
17
+ assert_equal @display.which_template(fmt), "templates/sms.#{fmt}.haml"
18
+ end
19
+ end
20
+ must "respond with missing template when display_format gets an unknown format of #{@unknown_fmt}" do
21
+ assert_equal @display.which_template(@unknown_fmt), "templates/sms.missing.haml"
22
+ end
23
+
24
+ must "have an real life template for each of the list of known formats" do
25
+ known_template_formats.each do |fmt|
26
+ assert File.exists?(@display.template_location(fmt)), "template #{fmt} does not actually exist"
27
+ end
28
+ end
29
+
30
+ must "have a library directory where the library constant defines it" do
31
+ library_path = File.join(app_root, library_loc)
32
+ assert File.directory?(library_path), "library #{library_path} does not actually exist"
33
+ end
34
+
35
+ must "have a template directory where the template_path constant defines it" do
36
+ template_path = File.join(app_root, library_loc, template_loc)
37
+ assert File.directory?(template_path), "template directory #{template_path} does not actually exist"
38
+ end
39
+
40
+ must "create the correct fully qualified template name" do
41
+ assert_equal @display.template_location("html"), File.join(templates_path,"sms.html.haml")
42
+ end
43
+
44
+ # def display_sms(template)
45
+ # temp_loc = template_location(template)
46
+ # display_template(File.read(temp_loc))
47
+ # end
48
+ # must "read from the correct template location" do
49
+ # mock_template = flexmock(File).should_receive(:read).with("html").and_return(:ok)
50
+ # assert_equal :ok, @display.display_sms("html")
51
+ # end
52
+
53
+ # def render_template( template = nil )
54
+ # unless template.nil?
55
+ # Haml::Engine.new(template).render( Object.new, :conversations => @conversations, :missing => @missing )
56
+ # else
57
+ # NO_TEMPLATE
58
+ # end
59
+ # end
60
+ must "return a no template message if no template is passed" do
61
+ assert_equal @display.render_template(nil), no_template
62
+ end
63
+ # must "render the and html template correctly" do
64
+ # end
65
+
66
+ end
@@ -0,0 +1,33 @@
1
+ require "minitest/unit"
2
+ require_relative "test_unit_extensions"
3
+ require "flexmock/test_unit"
4
+ require_relative "../ipreader"
5
+
6
+
7
+ def template_loc
8
+ Ipreader::TEMPLATE_LOCATION
9
+ end
10
+
11
+ def library_loc
12
+ Ipreader::LIBRARY_LOCATION
13
+ end
14
+
15
+ def no_template
16
+ Ipreader::NO_TEMPLATE
17
+ end
18
+
19
+ def known_template_formats
20
+ Ipreader::KNOWN_FORMATS
21
+ end
22
+
23
+ def app_root
24
+ File.expand_path(File.join(File.dirname(__FILE__),".."))
25
+ end
26
+
27
+ def test_root
28
+ File.join(app_root,"test")
29
+ end
30
+
31
+ def templates_path
32
+ File.join(app_root, library_loc, template_loc)
33
+ end
@@ -0,0 +1,20 @@
1
+ module MiniTest
2
+ class Unit
3
+ # Used to fix a minor minitest/unit incompatibility in flexmock
4
+ AssertionFailedError = Class.new(StandardError)
5
+ class TestCase
6
+ def self.must(name, &block)
7
+ test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
8
+ defined = instance_method(test_name) rescue false
9
+ raise "#{test_name} is already defined in #{self}" if defined
10
+ if block_given?
11
+ define_method(test_name, &block)
12
+ else
13
+ define_method(test_name) do
14
+ flunk "No implementation provided for #{name}"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ipreader
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease: !!null
6
+ platform: ruby
7
+ authors:
8
+ - David Rabbich
9
+ autorequire: !!null
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-03-15 00:00:00.000000000 +00:00
13
+ default_executable: !!null
14
+ dependencies: []
15
+ description: ! ' Takes as input a directory location of the Apple backup store and
16
+ interrogates the back up data for SMS data '
17
+ email:
18
+ - davidrabbich@yahoo.co.uk
19
+ executables:
20
+ - ext_sms
21
+ - ext_sms.rb
22
+ extensions: []
23
+ extra_rdoc_files: []
24
+ files:
25
+ - .gitignore
26
+ - Gemfile
27
+ - Gemfile.lock
28
+ - README
29
+ - Rakefile
30
+ - bin/ext_sms
31
+ - bin/ext_sms.rb
32
+ - ipreader.gemspec
33
+ - ipreader.rb
34
+ - ipreader/database_utils.rb
35
+ - ipreader/display.rb
36
+ - ipreader/init.rb
37
+ - ipreader/templates/sms.csv.haml
38
+ - ipreader/templates/sms.html.haml
39
+ - ipreader/templates/sms.missing.haml
40
+ - ipreader/templates/sms.xml.haml
41
+ - ipreader/version.rb
42
+ - test/test_database_utils.rb
43
+ - test/test_database_utils2.rb
44
+ - test/test_display.rb
45
+ - test/test_helper.rb
46
+ - test/test_unit_extensions.rb
47
+ has_rdoc: true
48
+ homepage: http://rubygems.org/gems/ipreader
49
+ licenses: []
50
+ post_install_message: !!null
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project: ipreader
68
+ rubygems_version: 1.5.0
69
+ signing_key: !!null
70
+ specification_version: 3
71
+ summary: Reads an unencrypted IPhone backup store and queries the sms data within
72
+ it.
73
+ test_files: []