ipreader 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []