idevice 1.1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/NOTICE +202 -0
  5. data/README.md +74 -0
  6. data/Rakefile +37 -0
  7. data/examples/idevimgmount +58 -0
  8. data/examples/idumplockdownvalues +64 -0
  9. data/examples/ifetchcrashreports +54 -0
  10. data/examples/ilistapps +38 -0
  11. data/examples/ilistudids +26 -0
  12. data/examples/ilog +35 -0
  13. data/examples/installipa +51 -0
  14. data/examples/iremoveapp +42 -0
  15. data/examples/irestart +26 -0
  16. data/examples/iscreenshotr +39 -0
  17. data/idevice.gemspec +33 -0
  18. data/lib/idevice.rb +69 -0
  19. data/lib/idevice/afc.rb +518 -0
  20. data/lib/idevice/c.rb +129 -0
  21. data/lib/idevice/diagnostics_relay.rb +185 -0
  22. data/lib/idevice/file_relay.rb +83 -0
  23. data/lib/idevice/heartbeat.rb +99 -0
  24. data/lib/idevice/house_arrest.rb +138 -0
  25. data/lib/idevice/idevice.rb +208 -0
  26. data/lib/idevice/image_mounter.rb +117 -0
  27. data/lib/idevice/installation_proxy.rb +193 -0
  28. data/lib/idevice/lockdown.rb +350 -0
  29. data/lib/idevice/misagent.rb +112 -0
  30. data/lib/idevice/mobilebackup.rb +183 -0
  31. data/lib/idevice/mobilebackup2.rb +174 -0
  32. data/lib/idevice/mobilesync.rb +306 -0
  33. data/lib/idevice/notification_proxy.rb +168 -0
  34. data/lib/idevice/plist.rb +366 -0
  35. data/lib/idevice/restore.rb +176 -0
  36. data/lib/idevice/sbservices.rb +152 -0
  37. data/lib/idevice/screenshotr.rb +88 -0
  38. data/lib/idevice/version.rb +3 -0
  39. data/lib/idevice/webinspector.rb +96 -0
  40. data/spec/afc_devicespec.rb +409 -0
  41. data/spec/diagnostics_relay_devicespec.rb +125 -0
  42. data/spec/file_relay_devicespec.rb +45 -0
  43. data/spec/heartbeat_devicespec.rb +39 -0
  44. data/spec/idevice_devicespec.rb +93 -0
  45. data/spec/idevice_spec.rb +29 -0
  46. data/spec/image_mounter_devicespec.rb +65 -0
  47. data/spec/installation_proxy_devicespec.rb +54 -0
  48. data/spec/lockdown_devicespec.rb +106 -0
  49. data/spec/misagent_devicespec.rb +43 -0
  50. data/spec/mobilebackup2_devicespec.rb +58 -0
  51. data/spec/mobilebackup_devicespec.rb +41 -0
  52. data/spec/mobilesync_devicespec.rb +62 -0
  53. data/spec/notification_proxy_devicespec.rb +45 -0
  54. data/spec/plist_spec.rb +176 -0
  55. data/spec/restore_devicespec.rb +72 -0
  56. data/spec/samples/plist.bin +0 -0
  57. data/spec/samples/plist.xml +10 -0
  58. data/spec/sbservices_devicespec.rb +64 -0
  59. data/spec/screenshotr_devicespec.rb +39 -0
  60. data/spec/spec_helper.rb +73 -0
  61. data/spec/webinspector_devicespec.rb +36 -0
  62. metadata +233 -0
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2013 Eric Monti - Bluebox Security
4
+ #
5
+ # Licensed to the Apache Software Foundation (ASF) under one
6
+ # or more contributor license agreements. See the NOTICE file
7
+ # distributed with this work for additional information
8
+ # regarding copyright ownership. The ASF licenses this file
9
+ # to you under the Apache License, Version 2.0 (the
10
+ # "License"); you may not use this file except in compliance
11
+ # with the License. You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing,
16
+ # software distributed under the License is distributed on an
17
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
+ # KIND, either express or implied. See the License for the
19
+ # specific language governing permissions and limitations
20
+ # under the License.
21
+
22
+
23
+ require 'open3'
24
+ require 'bundler/setup'
25
+ require 'idevice'
26
+
27
+ outfile = ARGV.shift
28
+ if outfile.nil? or ARGV.shift
29
+ $stderr.puts "Usage: #{File.basename $0} path/to/output.cpio"
30
+ exit(1)
31
+ end
32
+
33
+ frc = Idevice::FileRelayClient.attach
34
+ $stderr.puts "[.] Requesting crash reports"
35
+ len = 0
36
+ File.open(outfile, 'w') do |outf|
37
+ Open3.popen3("cpio -t") do |w,r,e|
38
+ begin
39
+ frc.request_sources("CrashReporter") do |chunk|
40
+ w.write(chunk)
41
+ outf.write(chunk)
42
+ len += chunk.size
43
+ end
44
+ ensure
45
+ w.close
46
+ puts "[+] Wrote #{len} bytes to #{outfile}",
47
+ "Contents:",
48
+ r.read
49
+
50
+ r.close
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2013 Eric Monti - Bluebox Security
4
+ #
5
+ # Licensed to the Apache Software Foundation (ASF) under one
6
+ # or more contributor license agreements. See the NOTICE file
7
+ # distributed with this work for additional information
8
+ # regarding copyright ownership. The ASF licenses this file
9
+ # to you under the Apache License, Version 2.0 (the
10
+ # "License"); you may not use this file except in compliance
11
+ # with the License. You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing,
16
+ # software distributed under the License is distributed on an
17
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
+ # KIND, either express or implied. See the License for the
19
+ # specific language governing permissions and limitations
20
+ # under the License.
21
+
22
+
23
+ require 'bundler/setup'
24
+ require 'idevice'
25
+
26
+ instpxy = Idevice::InstProxyClient.attach
27
+
28
+ lst = instpxy.browse.sort{|a,b| a["Path"] <=> b["Path"] }.map do |app|
29
+ puts "%s App: %s (%s - %s)\n exe: %s/%s\n\n" % app.values_at(
30
+ "ApplicationType",
31
+ "CFBundleDisplayName",
32
+ "CFBundleIdentifier",
33
+ "CFBundleVersion",
34
+ "Path",
35
+ "CFBundleExecutable",
36
+ )
37
+ end
38
+
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2013 Eric Monti - Bluebox Security
4
+ #
5
+ # Licensed to the Apache Software Foundation (ASF) under one
6
+ # or more contributor license agreements. See the NOTICE file
7
+ # distributed with this work for additional information
8
+ # regarding copyright ownership. The ASF licenses this file
9
+ # to you under the Apache License, Version 2.0 (the
10
+ # "License"); you may not use this file except in compliance
11
+ # with the License. You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing,
16
+ # software distributed under the License is distributed on an
17
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
+ # KIND, either express or implied. See the License for the
19
+ # specific language governing permissions and limitations
20
+ # under the License.
21
+
22
+
23
+ require 'bundler/setup'
24
+ require 'idevice'
25
+
26
+ puts Idevice.device_list
data/examples/ilog ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2013 Eric Monti - Bluebox Security
4
+ #
5
+ # Licensed to the Apache Software Foundation (ASF) under one
6
+ # or more contributor license agreements. See the NOTICE file
7
+ # distributed with this work for additional information
8
+ # regarding copyright ownership. The ASF licenses this file
9
+ # to you under the Apache License, Version 2.0 (the
10
+ # "License"); you may not use this file except in compliance
11
+ # with the License. You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing,
16
+ # software distributed under the License is distributed on an
17
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
+ # KIND, either express or implied. See the License for the
19
+ # specific language governing permissions and limitations
20
+ # under the License.
21
+
22
+
23
+ require 'bundler/setup'
24
+ require 'idevice'
25
+
26
+ idev = Idevice::Idevice.attach
27
+ ldcli = Idevice::LockdownClient.attach(idevice:idev)
28
+ syslog_ldsvc = ldcli.start_service("com.apple.syslog_relay")
29
+ syslog_relay = idev.connect(syslog_ldsvc[:port])
30
+ begin
31
+ syslog_relay.receive_all(1) {|chunk| $stdout.write chunk } while true
32
+ rescue Interrupt
33
+ exit 0
34
+ end
35
+
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2013 Eric Monti - Bluebox Security
4
+ #
5
+ # Licensed to the Apache Software Foundation (ASF) under one
6
+ # or more contributor license agreements. See the NOTICE file
7
+ # distributed with this work for additional information
8
+ # regarding copyright ownership. The ASF licenses this file
9
+ # to you under the Apache License, Version 2.0 (the
10
+ # "License"); you may not use this file except in compliance
11
+ # with the License. You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing,
16
+ # software distributed under the License is distributed on an
17
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
+ # KIND, either express or implied. See the License for the
19
+ # specific language governing permissions and limitations
20
+ # under the License.
21
+
22
+ require 'bundler/setup'
23
+ require 'idevice'
24
+
25
+ ipa_path = ARGV.shift
26
+ if ipa_path.nil? or ARGV.shift
27
+ $stderr.puts "Usage: #{File.basename} /path/to/an.ipa"
28
+ exit(1)
29
+ end
30
+
31
+ ipa = Pathname(ipa_path)
32
+
33
+ idev = Idevice::Idevice.attach
34
+
35
+ remote_path = Pathname("PublicStaging").join(ipa.basename)
36
+
37
+ afc = Idevice::AFCClient.attach(idevice:idev)
38
+ $stderr.puts "[.] Uploading #{ipa} to #{remote_path}"
39
+ afc.put_path(ipa.to_s, remote_path.to_s)
40
+
41
+ instpxy = Idevice::InstProxyClient.attach(idevice:idev)
42
+ $stderr.puts "[.] Requesting installation of #{remote_path}"
43
+ finished = false
44
+ instpxy.install(remote_path.to_s) do |name, status|
45
+ p [name, status]
46
+ finished = (status["Error"] or status["Status"] == "Complete")
47
+ end
48
+
49
+ while not finished
50
+ #nop
51
+ end
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2013 Eric Monti - Bluebox Security
4
+ #
5
+ # Licensed to the Apache Software Foundation (ASF) under one
6
+ # or more contributor license agreements. See the NOTICE file
7
+ # distributed with this work for additional information
8
+ # regarding copyright ownership. The ASF licenses this file
9
+ # to you under the Apache License, Version 2.0 (the
10
+ # "License"); you may not use this file except in compliance
11
+ # with the License. You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing,
16
+ # software distributed under the License is distributed on an
17
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
+ # KIND, either express or implied. See the License for the
19
+ # specific language governing permissions and limitations
20
+ # under the License.
21
+
22
+
23
+ require 'bundler/setup'
24
+ require 'idevice'
25
+
26
+ appid = ARGV.shift
27
+ if appid.nil? or ARGV.shift
28
+ $stderr.puts "Usage: #{File.basename} appid"
29
+ exit(1)
30
+ end
31
+
32
+ instpxy = Idevice::InstProxyClient.attach
33
+ $stderr.puts "[.] Requesting uninstallation of #{appid}"
34
+ finished = false
35
+ instpxy.uninstall(appid) do |name, status|
36
+ p [name, status]
37
+ finished = (status["Error"] or status["Status"] == "Complete")
38
+ end
39
+
40
+ until finished
41
+ #nop
42
+ end
data/examples/irestart ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2013 Eric Monti - Bluebox Security
4
+ #
5
+ # Licensed to the Apache Software Foundation (ASF) under one
6
+ # or more contributor license agreements. See the NOTICE file
7
+ # distributed with this work for additional information
8
+ # regarding copyright ownership. The ASF licenses this file
9
+ # to you under the Apache License, Version 2.0 (the
10
+ # "License"); you may not use this file except in compliance
11
+ # with the License. You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing,
16
+ # software distributed under the License is distributed on an
17
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
+ # KIND, either express or implied. See the License for the
19
+ # specific language governing permissions and limitations
20
+ # under the License.
21
+
22
+
23
+ require 'bundler/setup'
24
+ require 'idevice'
25
+
26
+ Idevice::DiagnosticsRelayClient.attach.restart(Idevice::DiagnosticsRelayClient::FLAG_WAIT_FOR_DISCONNECT)
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2013 Eric Monti - Bluebox Security
4
+ #
5
+ # Licensed to the Apache Software Foundation (ASF) under one
6
+ # or more contributor license agreements. See the NOTICE file
7
+ # distributed with this work for additional information
8
+ # regarding copyright ownership. The ASF licenses this file
9
+ # to you under the Apache License, Version 2.0 (the
10
+ # "License"); you may not use this file except in compliance
11
+ # with the License. You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing,
16
+ # software distributed under the License is distributed on an
17
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18
+ # KIND, either express or implied. See the License for the
19
+ # specific language governing permissions and limitations
20
+ # under the License.
21
+
22
+
23
+ require 'bundler/setup'
24
+ require 'idevice'
25
+
26
+ outname = ARGV.shift || "iscreenshot.tiff"
27
+
28
+ begin
29
+ sshotr = Idevice::ScreenShotrClient.attach()
30
+ rescue Idevice::LockdownError
31
+ $stderr.puts "[-] Error: unable to connect to screenshotr",
32
+ " Hint: the DeveloperTools dmg must be mounted on device"
33
+ exit(1)
34
+ end
35
+
36
+ image = sshotr.take_screenshot
37
+ ret = File.open(outname, "w") {|f| f.write image }
38
+ puts "[+] wrote #{ret} bytes to #{outname}"
39
+ exit(0)
data/idevice.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'idevice/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "idevice"
8
+ spec.version = Idevice::VERSION
9
+ spec.authors = ["Eric Monti"]
10
+ spec.email = ["monti@bluebox.com"]
11
+ spec.description = %q{Ruby FFI bindings for libimobiledevice}
12
+ spec.summary = %q{Ruby FFI bindings for libimobiledevice.
13
+
14
+ The ruby Idevice library was written primarily as a research tool for
15
+ prototyping iOS tools that use USB as well as a tool to aid in
16
+ reverse-engineering new areas of the iOS USB protocols.
17
+ }
18
+ spec.homepage = "https://github.com/blueboxsecurity/idevice"
19
+ spec.license = "Apache License, Version 2.0"
20
+
21
+ spec.files = `git ls-files`.split($/)
22
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
23
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_dependency "ffi"
27
+ spec.add_dependency "plist"
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.3"
30
+ spec.add_development_dependency "rake"
31
+ spec.add_development_dependency "rspec"
32
+ spec.add_development_dependency "pry"
33
+ end
data/lib/idevice.rb ADDED
@@ -0,0 +1,69 @@
1
+ #
2
+ # Copyright (c) 2013 Eric Monti - Bluebox Security
3
+ #
4
+ # Licensed to the Apache Software Foundation (ASF) under one
5
+ # or more contributor license agreements. See the NOTICE file
6
+ # distributed with this work for additional information
7
+ # regarding copyright ownership. The ASF licenses this file
8
+ # to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance
10
+ # with the License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing,
15
+ # software distributed under the License is distributed on an
16
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ # KIND, either express or implied. See the License for the
18
+ # specific language governing permissions and limitations
19
+ # under the License.
20
+
21
+ require 'rubygems'
22
+
23
+ require 'idevice/version'
24
+ require 'idevice/c'
25
+ require 'idevice/plist'
26
+ require 'idevice/idevice'
27
+ require 'idevice/lockdown'
28
+ require 'idevice/house_arrest'
29
+ require 'idevice/afc'
30
+ require 'idevice/installation_proxy'
31
+ require 'idevice/misagent'
32
+ require 'idevice/diagnostics_relay'
33
+ require 'idevice/file_relay'
34
+ require 'idevice/heartbeat'
35
+ require 'idevice/image_mounter'
36
+ require 'idevice/mobilebackup'
37
+ require 'idevice/mobilebackup2'
38
+ require 'idevice/mobilesync'
39
+ require 'idevice/notification_proxy'
40
+ require 'idevice/restore'
41
+ require 'idevice/sbservices'
42
+ require 'idevice/screenshotr'
43
+ require 'idevice/webinspector'
44
+
45
+ module Idevice
46
+ def self.debug_level= num
47
+ C.idevice_set_debug_level(num)
48
+ end
49
+
50
+ def self.device_list
51
+ FFI::MemoryPointer.new(:int) do |countp|
52
+ FFI::MemoryPointer.new(:pointer) do |devices|
53
+ ierr = C.idevice_get_device_list(devices, countp)
54
+ if ierr == :SUCCESS
55
+ ret = []
56
+ count = countp.read_int
57
+ if count > 0
58
+ devices.read_pointer.read_array_of_pointer(count).map { |sp| ret << sp.read_string }
59
+ end
60
+ C.idevice_device_list_free(devices.read_pointer)
61
+ return ret
62
+ else
63
+ raise Idevice::IdeviceError, "Library error: #{ierr}"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,518 @@
1
+ #
2
+ # Copyright (c) 2013 Eric Monti - Bluebox Security
3
+ #
4
+ # Licensed to the Apache Software Foundation (ASF) under one
5
+ # or more contributor license agreements. See the NOTICE file
6
+ # distributed with this work for additional information
7
+ # regarding copyright ownership. The ASF licenses this file
8
+ # to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance
10
+ # with the License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing,
15
+ # software distributed under the License is distributed on an
16
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ # KIND, either express or implied. See the License for the
18
+ # specific language governing permissions and limitations
19
+ # under the License.
20
+
21
+ require 'idevice/c'
22
+ require 'idevice/idevice'
23
+ require 'idevice/lockdown'
24
+ require 'idevice/house_arrest'
25
+
26
+ module Idevice
27
+ class AFCError < IdeviceLibError
28
+ end
29
+
30
+ AFC_DEFAULT_CHUNKSIZE = 8192
31
+
32
+ class AFCClient < C::ManagedOpaquePointer
33
+ include LibHelpers
34
+
35
+ def self.release(ptr)
36
+ C.afc_client_free(ptr) unless ptr.null?
37
+ end
38
+
39
+ def self.attach(opts={})
40
+ if opts[:appid]
41
+ return HouseArrestClient.attach(opts).afc_client_for_container(opts[:appid])
42
+
43
+ else
44
+ identifier =
45
+ if opts[:root]
46
+ "com.apple.afc2"
47
+ elsif opts[:afc_identifier]
48
+ opts[:afc_identifier]
49
+ else
50
+ "com.apple.afc"
51
+ end
52
+
53
+ _attach_helper(identifier, opts) do |idevice, ldsvc, p_afc|
54
+ err = C.afc_client_new(idevice, ldsvc, p_afc)
55
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
56
+
57
+ afc = p_afc.read_pointer
58
+ raise AFCError, "afc_client_new returned a NULL afc_client_t pointer" if afc.null?
59
+ return new(afc)
60
+ end
61
+ end
62
+ end
63
+
64
+ def device_info(key=nil)
65
+ ret = nil
66
+ if key
67
+ FFI::MemoryPointer.new(:pointer) do |p_value|
68
+ err = C.afc_get_device_info_key(self, key, p_value)
69
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
70
+
71
+ value = p_value.read_pointer
72
+ unless value.null?
73
+ ret = value.read_string
74
+ C.free(value)
75
+ end
76
+ end
77
+ else
78
+ FFI::MemoryPointer.new(:pointer) do |p_infos|
79
+ err = C.afc_get_device_info(self, p_infos)
80
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
81
+ ret = _infolist_to_hash(p_infos)
82
+ end
83
+ end
84
+ return ret
85
+ end
86
+
87
+ def read_directory(path='.')
88
+ ret = nil
89
+ FFI::MemoryPointer.new(:pointer) do |p_dirlist|
90
+ err = C.afc_read_directory(self, path, p_dirlist)
91
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
92
+ ret = _unbound_list_to_array(p_dirlist)
93
+ end
94
+
95
+ raise AFCError, "afc_read_directory returned a null directory list for path: #{path}" if ret.nil?
96
+ return ret
97
+ end
98
+ alias :ls :read_directory
99
+
100
+ def file_info(path)
101
+ ret = nil
102
+ FFI::MemoryPointer.new(:pointer) do |p_fileinfo|
103
+ err = C.afc_get_file_info(self, path, p_fileinfo)
104
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
105
+ ret = _infolist_to_hash(p_fileinfo)
106
+
107
+ # convert string values to something more useful
108
+ ret[:st_ifmt] = ret[:st_ifmt].to_sym if ret[:st_ifmt]
109
+
110
+ [:st_blocks, :st_nlink, :st_size].each do |k|
111
+ ret[k] = ret[k].to_i if ret[k]
112
+ end
113
+
114
+ [:st_birthtime, :st_mtime].each do |k|
115
+ ret[k] = _afctime_to_time(ret[k])
116
+ end
117
+ end
118
+ raise AFCError, "afc_get_file_info returned null info for path: #{path}" if ret.nil?
119
+ return ret
120
+ end
121
+ alias :stat :file_info
122
+
123
+ def touch(path)
124
+ open(path, 'a'){ }
125
+ return true
126
+ end
127
+
128
+ def make_directory(path)
129
+ err = C.afc_make_directory(self, path)
130
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
131
+ return true
132
+ end
133
+ alias :mkdir :make_directory
134
+
135
+ def symlink(from, to)
136
+ err = C.afc_make_link(self, :SYMLINK, from, to)
137
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
138
+ return true
139
+ end
140
+ alias :ln_s :symlink
141
+
142
+ def hardlink(from, to)
143
+ err = C.afc_make_link(self, :HARDLINK, from, to)
144
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
145
+ return true
146
+ end
147
+ alias :ln :hardlink
148
+
149
+ def rename_path(from, to)
150
+ err = C.afc_rename_path(self, from, to)
151
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
152
+ return true
153
+ end
154
+ alias :mv :rename_path
155
+
156
+ def remove_path(path)
157
+ err = C.afc_remove_path(self, path)
158
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
159
+ return true
160
+ end
161
+ alias :rm :remove_path
162
+
163
+ def truncate(path, size)
164
+ err = C.afc_truncate(self, path, size)
165
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
166
+ return true
167
+ end
168
+
169
+ def cat(path, chunksz=nil, &block)
170
+ AFCFile.open(self, path, 'r') { |f| return f.read_all(chunksz, &block) }
171
+ end
172
+
173
+ def size(path)
174
+ res = file_info(path)
175
+ return res[:st_size].to_i
176
+ end
177
+
178
+ def mtime(path)
179
+ info = file_info(path)
180
+ return info[:st_mtime]
181
+ end
182
+
183
+ def ctime(path)
184
+ info = file_info(path)
185
+ return info[:st_birthtime]
186
+ end
187
+
188
+ def put_path(frompath, topath, chunksz=nil)
189
+ chunksz ||= AFC_DEFAULT_CHUNKSIZE
190
+ wlen = 0
191
+
192
+ File.open(frompath, 'r') do |from|
193
+ AFCFile.open(self, topath, 'w') do |to|
194
+ while chunk = from.read(chunksz)
195
+ to.write(chunk)
196
+ yield chunk.size if block_given?
197
+ wlen+=chunk.size
198
+ end
199
+ end
200
+ end
201
+
202
+ return wlen
203
+ end
204
+
205
+ def getpath(frompath, topath, chunksz=nil)
206
+ wlen = 0
207
+ AFCFile.open(self, frompath, 'r') do |from|
208
+ File.open(topath, 'w') do |to|
209
+ from.read_all(chunksz) do |chunk|
210
+ to.write(chunk)
211
+ yield chunk.size if block_given?
212
+ wlen += chunk.size
213
+ end
214
+ end
215
+ end
216
+ return wlen
217
+ end
218
+
219
+ def set_file_time(path, time)
220
+ tval = case time
221
+ when Time
222
+ _time_to_afctime(time)
223
+ when DateTime
224
+ _time_to_afctime(time.to_time)
225
+ else
226
+ time
227
+ end
228
+ err = C.afc_set_file_time(self, path, tval)
229
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
230
+ return true
231
+ end
232
+
233
+ def open(path, mode=nil, &block)
234
+ AFCFile.open(self,path,mode,&block)
235
+ end
236
+
237
+ private
238
+ def _afctime_to_time(timestr)
239
+ # using DateTime to ensure handling as UTC
240
+ DateTime.strptime(timestr[0..-10], "%s").to_time
241
+ end
242
+
243
+ def _time_to_afctime(timeval)
244
+ timeval.utc.to_i * 1000000000
245
+ end
246
+ end
247
+
248
+ # short name alias to AFCClient
249
+ AFC = AFCClient
250
+
251
+ class AFCFile
252
+ def self.open(afcclient, path, mode=:RDONLY)
253
+ m = case mode
254
+ when Symbol
255
+ mode
256
+ when 'r',nil
257
+ :RDONLY
258
+ when 'r+'
259
+ :RW
260
+ when 'w'
261
+ :WRONLY
262
+ when 'w+'
263
+ :WR
264
+ when 'a'
265
+ :APPEND
266
+ when 'a+'
267
+ :RDAPPEND
268
+ else
269
+ raise ArgumentError, "invalid file mode: #{mode.inspect}"
270
+ end
271
+
272
+ afcfile=nil
273
+ FFI::MemoryPointer.new(:uint64) do |p_handle|
274
+ err =C.afc_file_open(afcclient, path, m, p_handle)
275
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
276
+ afcfile = new(afcclient, p_handle.read_uint64)
277
+ end
278
+
279
+ begin
280
+ yield(afcfile)
281
+ ensure
282
+ afcfile.close unless afcfile.closed?
283
+ end
284
+ end
285
+
286
+ def initialize(afcclient, handle)
287
+ @afcclient = afcclient
288
+ @handle = handle
289
+ @closed = false
290
+ end
291
+
292
+ def close
293
+ err = C.afc_file_close(@afcclient, @handle)
294
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
295
+ @closed = true
296
+ end
297
+
298
+ def closed?
299
+ @closed == true
300
+ end
301
+
302
+ def open?
303
+ not closed?
304
+ end
305
+
306
+ def lock(op)
307
+ err = C.afc_file_lock(@afcclient, @handle, op)
308
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
309
+ return true
310
+ end
311
+
312
+ def seek(offset, whence)
313
+ err = C.afc_file_seek(@afcclient, @handle, offset, whence)
314
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
315
+ end
316
+
317
+ def tell
318
+ FFI::MemoryPointer.new(:pointer) do |p_pos|
319
+ err = C.afc_file_tell(@afcclient, @handle, p_pos)
320
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
321
+ return p_pos.read_uint16
322
+ end
323
+ end
324
+ alias :pos :tell
325
+
326
+ def pos=(offset)
327
+ seek(offset, :SEEK_SET)
328
+ end
329
+
330
+ def rewind
331
+ self.pos=0
332
+ end
333
+
334
+ def read_all(chunksz=nil)
335
+ chunksz ||= AFC_DEFAULT_CHUNKSIZE
336
+ ret = nil
337
+ FFI::MemoryPointer.new(chunksz) do |buf|
338
+ FFI::MemoryPointer.new(:uint32) do |p_rlen|
339
+ while (err=C.afc_file_read(@afcclient, @handle, buf, buf.size, p_rlen)) == :SUCCESS
340
+ rlen = p_rlen.read_uint32
341
+ chunk = buf.read_bytes(rlen)
342
+ if block_given?
343
+ yield chunk
344
+ else
345
+ ret ||= StringIO.new unless block_given?
346
+ ret << chunk
347
+ end
348
+ break if rlen == 0
349
+ end
350
+ raise AFCError, "AFC error: #{err}" unless [:SUCCESS, :END_OF_DATA].include?(err)
351
+ end
352
+ end
353
+
354
+ return ret.string unless ret.nil?
355
+ end
356
+
357
+ def read(len=nil, &block)
358
+ return read_all(nil, &block) if len.nil?
359
+
360
+ ret = nil
361
+
362
+ FFI::MemoryPointer.new(len) do |buf|
363
+ FFI::MemoryPointer.new(:uint32) do |p_rlen|
364
+ while (err=C.afc_file_read(@afcclient, @handle, buf, len, p_rlen)) == :SUCCESS
365
+ rlen = p_rlen.read_uint32
366
+ ret ||= StringIO.new
367
+ ret << buf.read_bytes(rlen)
368
+ len -= rlen
369
+ break if len <= 0
370
+ end
371
+ raise AFCError, "AFC error: #{err}" unless [:SUCCESS, :END_OF_DATA].include?(err)
372
+ end
373
+ end
374
+
375
+ return ret.string unless ret.nil?
376
+ end
377
+
378
+ def write(data)
379
+ bytes_written = 0
380
+ FFI::MemoryPointer.from_bytes(data) do |p_data|
381
+ FFI::MemoryPointer.new(:uint32) do |p_wlen|
382
+ while bytes_written < p_data.size
383
+ err = C.afc_file_write(@afcclient, @handle, p_data, p_data.size, p_wlen)
384
+ raise AFCError, "AFC error: #{err}" if err != :SUCCESS
385
+
386
+ wlen = p_wlen.read_uint32
387
+ p_data += wlen
388
+ bytes_written += wlen
389
+ end
390
+ end
391
+ end
392
+ return bytes_written
393
+ end
394
+
395
+ end
396
+
397
+ module C
398
+ ffi_lib 'imobiledevice'
399
+
400
+ typedef enum(
401
+ :SUCCESS , 0,
402
+ :UNKNOWN_ERROR , 1,
403
+ :OP_HEADER_INVALID , 2,
404
+ :NO_RESOURCES , 3,
405
+ :READ_ERROR , 4,
406
+ :WRITE_ERROR , 5,
407
+ :UNKNOWN_PACKET_TYPE , 6,
408
+ :INVALID_ARG , 7,
409
+ :OBJECT_NOT_FOUND , 8,
410
+ :OBJECT_IS_DIR , 9,
411
+ :PERM_DENIED , 10,
412
+ :SERVICE_NOT_CONNECTED, 11,
413
+ :OP_TIMEOUT , 12,
414
+ :TOO_MUCH_DATA , 13,
415
+ :END_OF_DATA , 14,
416
+ :OP_NOT_SUPPORTED , 15,
417
+ :OBJECT_EXISTS , 16,
418
+ :OBJECT_BUSY , 17,
419
+ :NO_SPACE_LEFT , 18,
420
+ :OP_WOULD_BLOCK , 19,
421
+ :IO_ERROR , 20,
422
+ :OP_INTERRUPTED , 21,
423
+ :OP_IN_PROGRESS , 22,
424
+ :INTERNAL_ERROR , 23,
425
+
426
+ :MUX_ERROR , 30,
427
+ :NO_MEM , 31,
428
+ :NOT_ENOUGH_DATA , 32,
429
+ :DIR_NOT_EMPTY , 33,
430
+ ), :afc_error_t
431
+
432
+ typedef enum(
433
+ :RDONLY , 0x00000001, # r O_RDONLY
434
+ :RW , 0x00000002, # r+ O_RDWR | O_CREAT
435
+ :WRONLY , 0x00000003, # w O_WRONLY | O_CREAT | O_TRUNC
436
+ :WR , 0x00000004, # w+ O_RDWR | O_CREAT | O_TRUNC
437
+ :APPEND , 0x00000005, # a O_WRONLY | O_APPEND | O_CREAT
438
+ :RDAPPEND , 0x00000006, # a+ O_RDWR | O_APPEND | O_CREAT
439
+ ), :afc_file_mode_t
440
+
441
+ typedef enum(
442
+ :HARDLINK , 1,
443
+ :SYMLINK , 2,
444
+ ), :afc_link_type_t
445
+
446
+ typedef enum(
447
+ :SHARED, (1 | 4),
448
+ :EXCLUSIVE, (2 | 4),
449
+ :UNLOCK, (8 | 4),
450
+ ), :afc_lock_op_t;
451
+
452
+ typedef enum( :SEEK_SET, :SEEK_CUR, :SEEK_END ), :whence_t
453
+
454
+ # afc_error_t afc_client_new(idevice_t device, lockdownd_service_descriptor_t service, afc_client_t *client);
455
+ attach_function :afc_client_new, [Idevice, LockdownServiceDescriptor, :pointer], :afc_error_t
456
+
457
+ # afc_error_t afc_client_free(afc_client_t client);
458
+ attach_function :afc_client_free, [AFCClient], :afc_error_t
459
+
460
+ # afc_error_t afc_get_device_info(afc_client_t client, char ***infos);
461
+ attach_function :afc_get_device_info, [AFCClient, :pointer], :afc_error_t
462
+
463
+ # afc_error_t afc_read_directory(afc_client_t client, const char *dir, char ***list);
464
+ attach_function :afc_read_directory, [AFCClient, :string, :pointer], :afc_error_t
465
+
466
+ # afc_error_t afc_get_file_info(afc_client_t client, const char *filename, char ***infolist);
467
+ attach_function :afc_get_file_info, [AFCClient, :string, :pointer], :afc_error_t
468
+
469
+ # afc_error_t afc_file_open(afc_client_t client, const char *filename, afc_file_mode_t file_mode, uint64_t *handle);
470
+ attach_function :afc_file_open, [AFCClient, :string, :afc_file_mode_t, :pointer], :afc_error_t
471
+
472
+ # afc_error_t afc_file_close(afc_client_t client, uint64_t handle);
473
+ attach_function :afc_file_close, [AFCClient, :uint64], :afc_error_t
474
+
475
+ # afc_error_t afc_file_lock(afc_client_t client, uint64_t handle, afc_lock_op_t operation);
476
+ attach_function :afc_file_lock, [AFCClient, :uint64, :afc_lock_op_t], :afc_error_t
477
+
478
+ # afc_error_t afc_file_read(afc_client_t client, uint64_t handle, char *data, uint32_t length, uint32_t *bytes_read);
479
+ attach_function :afc_file_read, [AFCClient, :uint64, :pointer, :uint32, :pointer], :afc_error_t
480
+
481
+ # afc_error_t afc_file_write(afc_client_t client, uint64_t handle, const char *data, uint32_t length, uint32_t *bytes_written);
482
+ attach_function :afc_file_write, [AFCClient, :uint64, :pointer, :uint32, :pointer], :afc_error_t
483
+
484
+ # afc_error_t afc_file_seek(afc_client_t client, uint64_t handle, int64_t offset, int whence);
485
+ attach_function :afc_file_seek, [AFCClient, :uint64, :int64, :whence_t], :afc_error_t
486
+
487
+ # afc_error_t afc_file_tell(afc_client_t client, uint64_t handle, uint64_t *position);
488
+ attach_function :afc_file_tell, [AFCClient, :uint64, :pointer], :afc_error_t
489
+
490
+ # afc_error_t afc_file_truncate(afc_client_t client, uint64_t handle, uint64_t newsize);
491
+ attach_function :afc_file_truncate, [AFCClient, :uint64, :uint64], :afc_error_t
492
+
493
+ # afc_error_t afc_remove_path(afc_client_t client, const char *path);
494
+ attach_function :afc_remove_path, [AFCClient, :string], :afc_error_t
495
+
496
+ # afc_error_t afc_rename_path(afc_client_t client, const char *from, const char *to);
497
+ attach_function :afc_rename_path, [AFCClient, :string, :string], :afc_error_t
498
+
499
+ # afc_error_t afc_make_directory(afc_client_t client, const char *dir);
500
+ attach_function :afc_make_directory, [AFCClient, :string], :afc_error_t
501
+
502
+ # afc_error_t afc_truncate(afc_client_t client, const char *path, uint64_t newsize);
503
+ attach_function :afc_truncate, [AFCClient, :string, :uint64], :afc_error_t
504
+
505
+ # afc_error_t afc_make_link(afc_client_t client, afc_link_type_t linktype, const char *target, const char *linkname);
506
+ attach_function :afc_make_link, [AFCClient, :afc_link_type_t, :string, :string], :afc_error_t
507
+
508
+ # afc_error_t afc_set_file_time(afc_client_t client, const char *path, uint64_t mtime);
509
+ attach_function :afc_set_file_time, [AFCClient, :string, :uint64], :afc_error_t
510
+
511
+ # afc_error_t afc_get_device_info_key(afc_client_t client, const char *key, char **value);
512
+ attach_function :afc_get_device_info_key, [AFCClient, :string, :pointer], :afc_error_t
513
+
514
+ #afc_error_t afc_client_new_from_house_arrest_client(house_arrest_client_t client, afc_client_t *afc_client);
515
+ attach_function :afc_client_new_from_house_arrest_client, [HouseArrestClient, :pointer], :afc_error_t
516
+
517
+ end
518
+ end