idevice 1.1.5.0

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.
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