honeydew 0.12.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/.rspec +1 -1
  2. data/LICENSE.txt +1 -1
  3. data/README.md +79 -8
  4. data/Rakefile +2 -2
  5. data/examples/cucumber/Gemfile +5 -0
  6. data/{example → examples/cucumber}/Rakefile +0 -0
  7. data/{example → examples/cucumber}/features/example.feature +0 -0
  8. data/{example → examples/cucumber}/features/step_definitions/honeydew_steps.rb +0 -0
  9. data/examples/rspec/spec/browse_spec.rb +16 -0
  10. data/examples/rspec/spec/spec_helper.rb +6 -0
  11. data/honeydew.gemspec +17 -17
  12. data/honeydew.iml +12 -18
  13. data/lib/honeydew.rb +1 -72
  14. data/lib/honeydew/device.rb +79 -44
  15. data/lib/honeydew/device_actions.rb +32 -100
  16. data/lib/honeydew/device_commands.rb +36 -23
  17. data/lib/honeydew/device_matchers.rb +29 -22
  18. data/lib/honeydew/dsl.rb +1 -1
  19. data/lib/honeydew/honeydew.rb +82 -0
  20. data/lib/honeydew/version.rb +1 -1
  21. data/{android-server → server}/.gitignore +0 -0
  22. data/{android-server → server}/pom.xml +16 -6
  23. data/{android-server → server}/repo/com/google/android/uiautomator/4.1.1.4/_maven.repositories +0 -0
  24. data/{android-server → server}/repo/com/google/android/uiautomator/4.1.1.4/uiautomator-4.1.1.4.jar +0 -0
  25. data/{android-server → server}/repo/com/google/android/uiautomator/4.1.1.4/uiautomator-4.1.1.4.pom +0 -0
  26. data/{android-server → server}/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/android-maven-plugin-3.5.2-SNAPSHOT.jar +0 -0
  27. data/{android-server → server}/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/android-maven-plugin-3.5.2-SNAPSHOT.pom +0 -0
  28. data/{android-server → server}/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/maven-metadata-local.xml +0 -0
  29. data/{android-server → server}/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/_maven.repositories +0 -0
  30. data/{android-server → server}/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/commons-jxpath-1.4-SNAPSHOT.jar +0 -0
  31. data/{android-server → server}/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/commons-jxpath-1.4-SNAPSHOT.pom +0 -0
  32. data/{android-server → server}/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/maven-metadata-local.xml +0 -0
  33. data/{android-server → server}/repo/commons-jxpath/commons-jxpath/maven-metadata-local.xml +0 -0
  34. data/server/repo/fi/iki/elonen/nanohttpd-webserver/2.0.3/nanohttpd-webserver-2.0.3.jar +0 -0
  35. data/server/repo/fi/iki/elonen/nanohttpd-webserver/2.0.3/nanohttpd-webserver-2.0.3.pom +9 -0
  36. data/server/repo/fi/iki/elonen/nanohttpd-webserver/maven-metadata-local.xml +12 -0
  37. data/server/repo/fi/iki/elonen/nanohttpd/2.0.3/nanohttpd-2.0.3.jar +0 -0
  38. data/server/repo/fi/iki/elonen/nanohttpd/2.0.3/nanohttpd-2.0.3.pom +9 -0
  39. data/server/repo/fi/iki/elonen/nanohttpd/maven-metadata-local.xml +12 -0
  40. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/Action.java +13 -6
  41. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/ActionsExecutor.java +3 -2
  42. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/Command.java +6 -1
  43. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/Result.java +7 -7
  44. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/TestRunner.java +14 -6
  45. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/Click.java +3 -3
  46. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/ClickAndWaitForNewWindow.java +3 -3
  47. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/DumpWindowHierarchy.java +5 -5
  48. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/HasSettingsMenuItem.java +4 -5
  49. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/InspectOptionInSettingsMenu.java +3 -3
  50. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/IsButtonPresent.java +3 -3
  51. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/IsChildCountEqualTo.java +4 -4
  52. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/IsElementWithNestedTextPresent.java +3 -3
  53. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/IsOptionInSettingsMenuDisabled.java +1 -1
  54. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/IsOptionInSettingsMenuEnabled.java +1 -1
  55. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/IsTextPresent.java +3 -3
  56. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/LaunchApp.java +3 -3
  57. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/LaunchHome.java +3 -3
  58. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/LongClick.java +3 -3
  59. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/PressBack.java +3 -3
  60. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/PressEnter.java +4 -4
  61. data/server/src/main/java/com/amplify/honeydew_server/actions/ScrollToTextByIndex.java +27 -0
  62. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/SelectFromAppsList.java +4 -4
  63. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/SelectMenuInSettings.java +3 -3
  64. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/SetText.java +3 -3
  65. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/SetTextByIndex.java +2 -2
  66. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/SetTextByLabel.java +3 -3
  67. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/Unlock.java +3 -3
  68. data/{android-server/src/main/java/com/uiautomator_cucumber/android_server → server/src/main/java/com/amplify/honeydew_server}/actions/WakeUp.java +4 -4
  69. data/server/src/main/java/com/amplify/honeydew_server/httpd/RemoteCommandReceiver.java +66 -0
  70. data/spec/honeydew/device_matchers_spec.rb +21 -42
  71. data/spec/honeydew/device_spec.rb +38 -36
  72. data/spec/spec_helper.rb +2 -25
  73. metadata +93 -92
  74. data/android-server/src/main/java/com/uiautomator_cucumber/android_server/httpd/NanoHTTPD.java +0 -1100
  75. data/android-server/src/main/java/com/uiautomator_cucumber/android_server/httpd/RemoteCommandReceiver.java +0 -64
  76. data/example/Gemfile +0 -5
  77. data/example/features/support/hooks.rb +0 -4
  78. data/lib/honeydew/hooks.rb +0 -0
  79. data/lib/honeydew/step_definitions.rb +0 -75
  80. data/lib/tasks/honeydew.rake +0 -7
  81. data/todo.md +0 -9
data/spec/spec_helper.rb CHANGED
@@ -1,39 +1,16 @@
1
- # This file was generated by the `rspec --init` command. Conventionally, all
2
- # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
- # Require this file using `require "spec_helper"` to ensure that it is only
4
- # loaded once.
5
- #
6
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
-
8
1
  require 'honeydew'
9
- Dir["honeydew/**/*.rb"].each{|f| require f}
10
2
 
11
3
  RSpec.configure do |config|
12
4
  config.treat_symbols_as_metadata_keys_with_true_values = true
13
- config.run_all_when_everything_filtered = true
14
- config.filter_run :focus
15
-
16
- # Run specs in random order to surface order dependencies. If you find an
17
- # order dependency and want to debug it, you can fix the order by providing
18
- # the seed, which is printed after each run.
19
- # --seed 1234
20
5
  config.order = 'random'
21
6
 
22
- config.around :each do |example|
23
- if example.metadata[:silence_puts]
24
- silence_stream(STDOUT) { example.run }
25
- else
26
- example.run
27
- end
28
- end
29
-
30
7
  config.before do
31
8
  Honeydew.configure do |config|
32
9
  config.timeout = 2.seconds
33
10
  config.port = 7120
34
11
  end
35
- Honeydew::Device.any_instance.stub(:start_uiautomator_server)
36
- Honeydew::Device.any_instance.stub(ensure_tablet_ready: true)
12
+ Honeydew::Device.any_instance.stub(:start_honeydew_server)
13
+ Honeydew::Device.any_instance.stub(ensure_device_ready: true)
37
14
  end
38
15
  end
39
16
 
metadata CHANGED
@@ -1,42 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: honeydew
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.14.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
- - Selva
8
+ - Selvakumar Natesan
9
9
  - Christopher Rex
10
+ - Shyam Vala
11
+ - John Barker
10
12
  autorequire:
11
13
  bindir: bin
12
14
  cert_chain: []
13
- date: 2013-07-02 00:00:00.000000000 Z
15
+ date: 2013-07-15 00:00:00.000000000 Z
14
16
  dependencies:
15
17
  - !ruby/object:Gem::Dependency
16
- name: bundler
17
- requirement: !ruby/object:Gem::Requirement
18
- none: false
19
- requirements:
20
- - - ~>
21
- - !ruby/object:Gem::Version
22
- version: '1.3'
23
- type: :development
24
- prerelease: false
25
- version_requirements: !ruby/object:Gem::Requirement
26
- none: false
27
- requirements:
28
- - - ~>
29
- - !ruby/object:Gem::Version
30
- version: '1.3'
31
- - !ruby/object:Gem::Dependency
32
- name: rake
18
+ name: activesupport
33
19
  requirement: !ruby/object:Gem::Requirement
34
20
  none: false
35
21
  requirements:
36
22
  - - ! '>='
37
23
  - !ruby/object:Gem::Version
38
24
  version: '0'
39
- type: :development
25
+ type: :runtime
40
26
  prerelease: false
41
27
  version_requirements: !ruby/object:Gem::Requirement
42
28
  none: false
@@ -45,14 +31,14 @@ dependencies:
45
31
  - !ruby/object:Gem::Version
46
32
  version: '0'
47
33
  - !ruby/object:Gem::Dependency
48
- name: rspec
34
+ name: json
49
35
  requirement: !ruby/object:Gem::Requirement
50
36
  none: false
51
37
  requirements:
52
38
  - - ! '>='
53
39
  - !ruby/object:Gem::Version
54
40
  version: '0'
55
- type: :development
41
+ type: :runtime
56
42
  prerelease: false
57
43
  version_requirements: !ruby/object:Gem::Requirement
58
44
  none: false
@@ -61,30 +47,30 @@ dependencies:
61
47
  - !ruby/object:Gem::Version
62
48
  version: '0'
63
49
  - !ruby/object:Gem::Dependency
64
- name: cucumber
50
+ name: bundler
65
51
  requirement: !ruby/object:Gem::Requirement
66
52
  none: false
67
53
  requirements:
68
- - - ! '>='
54
+ - - ~>
69
55
  - !ruby/object:Gem::Version
70
- version: '0'
71
- type: :runtime
56
+ version: '1.3'
57
+ type: :development
72
58
  prerelease: false
73
59
  version_requirements: !ruby/object:Gem::Requirement
74
60
  none: false
75
61
  requirements:
76
- - - ! '>='
62
+ - - ~>
77
63
  - !ruby/object:Gem::Version
78
- version: '0'
64
+ version: '1.3'
79
65
  - !ruby/object:Gem::Dependency
80
- name: json
66
+ name: rake
81
67
  requirement: !ruby/object:Gem::Requirement
82
68
  none: false
83
69
  requirements:
84
70
  - - ! '>='
85
71
  - !ruby/object:Gem::Version
86
72
  version: '0'
87
- type: :runtime
73
+ type: :development
88
74
  prerelease: false
89
75
  version_requirements: !ruby/object:Gem::Requirement
90
76
  none: false
@@ -93,14 +79,14 @@ dependencies:
93
79
  - !ruby/object:Gem::Version
94
80
  version: '0'
95
81
  - !ruby/object:Gem::Dependency
96
- name: rest-client
82
+ name: rspec
97
83
  requirement: !ruby/object:Gem::Requirement
98
84
  none: false
99
85
  requirements:
100
86
  - - ! '>='
101
87
  - !ruby/object:Gem::Version
102
88
  version: '0'
103
- type: :runtime
89
+ type: :development
104
90
  prerelease: false
105
91
  version_requirements: !ruby/object:Gem::Requirement
106
92
  none: false
@@ -109,14 +95,14 @@ dependencies:
109
95
  - !ruby/object:Gem::Version
110
96
  version: '0'
111
97
  - !ruby/object:Gem::Dependency
112
- name: activesupport
98
+ name: simplecov
113
99
  requirement: !ruby/object:Gem::Requirement
114
100
  none: false
115
101
  requirements:
116
102
  - - ! '>='
117
103
  - !ruby/object:Gem::Version
118
104
  version: '0'
119
- type: :runtime
105
+ type: :development
120
106
  prerelease: false
121
107
  version_requirements: !ruby/object:Gem::Requirement
122
108
  none: false
@@ -124,9 +110,15 @@ dependencies:
124
110
  - - ! '>='
125
111
  - !ruby/object:Gem::Version
126
112
  version: '0'
127
- description: Automated functional testing on Android with uiautomator and cucumber
113
+ description: ! 'Honeydew is a Ruby driver for UIAutomator which enables automated
114
+ testing of
115
+
116
+ Android devices.
117
+
118
+ '
128
119
  email:
129
120
  - scmp-team@amplify.com
121
+ - jbarker@amplify.com
130
122
  executables: []
131
123
  extensions: []
132
124
  extra_rdoc_files: []
@@ -137,54 +129,12 @@ files:
137
129
  - LICENSE.txt
138
130
  - README.md
139
131
  - Rakefile
140
- - android-server/.gitignore
141
- - android-server/pom.xml
142
- - android-server/repo/com/google/android/uiautomator/4.1.1.4/_maven.repositories
143
- - android-server/repo/com/google/android/uiautomator/4.1.1.4/uiautomator-4.1.1.4.jar
144
- - android-server/repo/com/google/android/uiautomator/4.1.1.4/uiautomator-4.1.1.4.pom
145
- - android-server/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/android-maven-plugin-3.5.2-SNAPSHOT.jar
146
- - android-server/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/android-maven-plugin-3.5.2-SNAPSHOT.pom
147
- - android-server/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/maven-metadata-local.xml
148
- - android-server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/_maven.repositories
149
- - android-server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/commons-jxpath-1.4-SNAPSHOT.jar
150
- - android-server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/commons-jxpath-1.4-SNAPSHOT.pom
151
- - android-server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/maven-metadata-local.xml
152
- - android-server/repo/commons-jxpath/commons-jxpath/maven-metadata-local.xml
153
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/Action.java
154
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/ActionsExecutor.java
155
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/Command.java
156
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/Result.java
157
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/TestRunner.java
158
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/Click.java
159
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/ClickAndWaitForNewWindow.java
160
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/DumpWindowHierarchy.java
161
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/HasSettingsMenuItem.java
162
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/InspectOptionInSettingsMenu.java
163
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsButtonPresent.java
164
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsChildCountEqualTo.java
165
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsElementWithNestedTextPresent.java
166
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsOptionInSettingsMenuDisabled.java
167
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsOptionInSettingsMenuEnabled.java
168
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/IsTextPresent.java
169
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/LaunchApp.java
170
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/LaunchHome.java
171
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/LongClick.java
172
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/PressBack.java
173
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/PressEnter.java
174
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/SelectFromAppsList.java
175
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/SelectMenuInSettings.java
176
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/SetText.java
177
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/SetTextByIndex.java
178
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/SetTextByLabel.java
179
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/Unlock.java
180
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/actions/WakeUp.java
181
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/httpd/NanoHTTPD.java
182
- - android-server/src/main/java/com/uiautomator_cucumber/android_server/httpd/RemoteCommandReceiver.java
183
- - example/Gemfile
184
- - example/Rakefile
185
- - example/features/example.feature
186
- - example/features/step_definitions/honeydew_steps.rb
187
- - example/features/support/hooks.rb
132
+ - examples/cucumber/Gemfile
133
+ - examples/cucumber/Rakefile
134
+ - examples/cucumber/features/example.feature
135
+ - examples/cucumber/features/step_definitions/honeydew_steps.rb
136
+ - examples/rspec/spec/browse_spec.rb
137
+ - examples/rspec/spec/spec_helper.rb
188
138
  - honeydew.gemspec
189
139
  - honeydew.iml
190
140
  - lib/honeydew.rb
@@ -194,16 +144,62 @@ files:
194
144
  - lib/honeydew/device_commands.rb
195
145
  - lib/honeydew/device_matchers.rb
196
146
  - lib/honeydew/dsl.rb
197
- - lib/honeydew/hooks.rb
198
- - lib/honeydew/step_definitions.rb
147
+ - lib/honeydew/honeydew.rb
199
148
  - lib/honeydew/version.rb
200
- - lib/tasks/honeydew.rake
149
+ - server/.gitignore
150
+ - server/pom.xml
151
+ - server/repo/com/google/android/uiautomator/4.1.1.4/_maven.repositories
152
+ - server/repo/com/google/android/uiautomator/4.1.1.4/uiautomator-4.1.1.4.jar
153
+ - server/repo/com/google/android/uiautomator/4.1.1.4/uiautomator-4.1.1.4.pom
154
+ - server/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/android-maven-plugin-3.5.2-SNAPSHOT.jar
155
+ - server/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/android-maven-plugin-3.5.2-SNAPSHOT.pom
156
+ - server/repo/com/jayway/maven/plugins/android/generation2/android-maven-plugin/3.5.2-SNAPSHOT/maven-metadata-local.xml
157
+ - server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/_maven.repositories
158
+ - server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/commons-jxpath-1.4-SNAPSHOT.jar
159
+ - server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/commons-jxpath-1.4-SNAPSHOT.pom
160
+ - server/repo/commons-jxpath/commons-jxpath/1.4-SNAPSHOT/maven-metadata-local.xml
161
+ - server/repo/commons-jxpath/commons-jxpath/maven-metadata-local.xml
162
+ - server/repo/fi/iki/elonen/nanohttpd-webserver/2.0.3/nanohttpd-webserver-2.0.3.jar
163
+ - server/repo/fi/iki/elonen/nanohttpd-webserver/2.0.3/nanohttpd-webserver-2.0.3.pom
164
+ - server/repo/fi/iki/elonen/nanohttpd-webserver/maven-metadata-local.xml
165
+ - server/repo/fi/iki/elonen/nanohttpd/2.0.3/nanohttpd-2.0.3.jar
166
+ - server/repo/fi/iki/elonen/nanohttpd/2.0.3/nanohttpd-2.0.3.pom
167
+ - server/repo/fi/iki/elonen/nanohttpd/maven-metadata-local.xml
168
+ - server/src/main/java/com/amplify/honeydew_server/Action.java
169
+ - server/src/main/java/com/amplify/honeydew_server/ActionsExecutor.java
170
+ - server/src/main/java/com/amplify/honeydew_server/Command.java
171
+ - server/src/main/java/com/amplify/honeydew_server/Result.java
172
+ - server/src/main/java/com/amplify/honeydew_server/TestRunner.java
173
+ - server/src/main/java/com/amplify/honeydew_server/actions/Click.java
174
+ - server/src/main/java/com/amplify/honeydew_server/actions/ClickAndWaitForNewWindow.java
175
+ - server/src/main/java/com/amplify/honeydew_server/actions/DumpWindowHierarchy.java
176
+ - server/src/main/java/com/amplify/honeydew_server/actions/HasSettingsMenuItem.java
177
+ - server/src/main/java/com/amplify/honeydew_server/actions/InspectOptionInSettingsMenu.java
178
+ - server/src/main/java/com/amplify/honeydew_server/actions/IsButtonPresent.java
179
+ - server/src/main/java/com/amplify/honeydew_server/actions/IsChildCountEqualTo.java
180
+ - server/src/main/java/com/amplify/honeydew_server/actions/IsElementWithNestedTextPresent.java
181
+ - server/src/main/java/com/amplify/honeydew_server/actions/IsOptionInSettingsMenuDisabled.java
182
+ - server/src/main/java/com/amplify/honeydew_server/actions/IsOptionInSettingsMenuEnabled.java
183
+ - server/src/main/java/com/amplify/honeydew_server/actions/IsTextPresent.java
184
+ - server/src/main/java/com/amplify/honeydew_server/actions/LaunchApp.java
185
+ - server/src/main/java/com/amplify/honeydew_server/actions/LaunchHome.java
186
+ - server/src/main/java/com/amplify/honeydew_server/actions/LongClick.java
187
+ - server/src/main/java/com/amplify/honeydew_server/actions/PressBack.java
188
+ - server/src/main/java/com/amplify/honeydew_server/actions/PressEnter.java
189
+ - server/src/main/java/com/amplify/honeydew_server/actions/ScrollToTextByIndex.java
190
+ - server/src/main/java/com/amplify/honeydew_server/actions/SelectFromAppsList.java
191
+ - server/src/main/java/com/amplify/honeydew_server/actions/SelectMenuInSettings.java
192
+ - server/src/main/java/com/amplify/honeydew_server/actions/SetText.java
193
+ - server/src/main/java/com/amplify/honeydew_server/actions/SetTextByIndex.java
194
+ - server/src/main/java/com/amplify/honeydew_server/actions/SetTextByLabel.java
195
+ - server/src/main/java/com/amplify/honeydew_server/actions/Unlock.java
196
+ - server/src/main/java/com/amplify/honeydew_server/actions/WakeUp.java
197
+ - server/src/main/java/com/amplify/honeydew_server/httpd/RemoteCommandReceiver.java
201
198
  - spec/honeydew/device_matchers_spec.rb
202
199
  - spec/honeydew/device_spec.rb
203
200
  - spec/spec_helper.rb
204
- - todo.md
205
- - android-server/target/android-server-0.0.1-SNAPSHOT.jar
206
- homepage: ''
201
+ - server/target/honeydew-server-0.14.0.jar
202
+ homepage:
207
203
  licenses: []
208
204
  post_install_message:
209
205
  rdoc_options: []
@@ -223,11 +219,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
223
219
  version: '0'
224
220
  requirements: []
225
221
  rubyforge_project:
226
- rubygems_version: 1.8.25
222
+ rubygems_version: 1.8.23
227
223
  signing_key:
228
224
  specification_version: 3
229
- summary: Automated functional testing on Android with uiautomator and cucumber
225
+ summary: Ruby DSL for controlling Android devices
230
226
  test_files:
227
+ - .rspec
228
+ - examples/rspec/spec/browse_spec.rb
229
+ - examples/rspec/spec/spec_helper.rb
230
+ - honeydew.gemspec
231
+ - server/src/main/java/com/amplify/honeydew_server/actions/InspectOptionInSettingsMenu.java
231
232
  - spec/honeydew/device_matchers_spec.rb
232
233
  - spec/honeydew/device_spec.rb
233
234
  - spec/spec_helper.rb
@@ -1,1100 +0,0 @@
1
- package com.uiautomator_cucumber.android_server.httpd;
2
-
3
- import java.io.*;
4
- import java.net.ServerSocket;
5
- import java.net.Socket;
6
- import java.net.URLEncoder;
7
- import java.util.*;
8
-
9
- /**
10
- * A simple, tiny, nicely embeddable HTTP 1.0 (partially 1.1) server in Java
11
- *
12
- * <p> NanoHTTPD version 1.25,
13
- * Copyright &copy; 2001,2005-2012 Jarno Elonen (elonen@iki.fi, http://iki.fi/elonen/)
14
- * and Copyright &copy; 2010 Konstantinos Togias (info@ktogias.gr, http://ktogias.gr)
15
- *
16
- * <p><b>Features + limitations: </b><ul>
17
- *
18
- * <li> Only one Java file </li>
19
- * <li> Java 1.1 compatible </li>
20
- * <li> Released as open source, Modified BSD licence </li>
21
- * <li> No fixed config files, logging, authorization etc. (Implement yourself if you need them.) </li>
22
- * <li> Supports parameter parsing of GET and POST methods (+ rudimentary PUT support in 1.25) </li>
23
- * <li> Supports both dynamic content and file serving </li>
24
- * <li> Supports file upload (since version 1.2, 2010) </li>
25
- * <li> Supports partial content (streaming)</li>
26
- * <li> Supports ETags</li>
27
- * <li> Never caches anything </li>
28
- * <li> Doesn't limit bandwidth, request time or simultaneous connections </li>
29
- * <li> Default code serves files and shows all HTTP parameters and headers</li>
30
- * <li> File server supports directory listing, index.html and index.htm</li>
31
- * <li> File server supports partial content (streaming)</li>
32
- * <li> File server supports ETags</li>
33
- * <li> File server does the 301 redirection trick for directories without '/'</li>
34
- * <li> File server supports simple skipping for files (continue download) </li>
35
- * <li> File server serves also very long files without memory overhead </li>
36
- * <li> Contains a built-in list of most common mime types </li>
37
- * <li> All header names are converted lowercase so they don't vary between browsers/clients </li>
38
- *
39
- * </ul>
40
- *
41
- * <p><b>Ways to use: </b><ul>
42
- *
43
- * <li> Run as a standalone app, serves files and shows requests</li>
44
- * <li> Subclass serve() and embed to your own program </li>
45
- * <li> Call serveFile() from serve() with your own base directory </li>
46
- *
47
- * </ul>
48
- *
49
- * See the end of the source file for distribution license
50
- * (Modified BSD licence)
51
- */
52
- public class NanoHTTPD
53
- {
54
- // ==================================================
55
- // API parts
56
- // ==================================================
57
-
58
- /**
59
- * Override this to customize the server.<p>
60
- *
61
- * (By default, this delegates to serveFile() and allows directory listing.)
62
- *
63
- * @param uri Percent-decoded URI without parameters, for example "/index.cgi"
64
- * @param method "GET", "POST" etc.
65
- * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data.
66
- * @param header Header entries, percent decoded
67
- * @return HTTP response, see class Response for details
68
- */
69
- public Response serve( String uri, String method, Properties header, Properties parms, Properties files )
70
- {
71
- myOut.println( method + " '" + uri + "' " );
72
-
73
- Enumeration e = header.propertyNames();
74
- while ( e.hasMoreElements())
75
- {
76
- String value = (String)e.nextElement();
77
- myOut.println( " HDR: '" + value + "' = '" +
78
- header.getProperty( value ) + "'" );
79
- }
80
- e = parms.propertyNames();
81
- while ( e.hasMoreElements())
82
- {
83
- String value = (String)e.nextElement();
84
- myOut.println( " PRM: '" + value + "' = '" +
85
- parms.getProperty( value ) + "'" );
86
- }
87
- e = files.propertyNames();
88
- while ( e.hasMoreElements())
89
- {
90
- String value = (String)e.nextElement();
91
- myOut.println( " UPLOADED: '" + value + "' = '" +
92
- files.getProperty( value ) + "'" );
93
- }
94
-
95
- return serveFile( uri, header, myRootDir, true );
96
- }
97
-
98
- /**
99
- * HTTP response.
100
- * Return one of these from serve().
101
- */
102
- public class Response
103
- {
104
- /**
105
- * Default constructor: response = HTTP_OK, data = mime = 'null'
106
- */
107
- public Response()
108
- {
109
- this.status = HTTP_OK;
110
- }
111
-
112
- /**
113
- * Basic constructor.
114
- */
115
- public Response( String status, String mimeType, InputStream data )
116
- {
117
- this.status = status;
118
- this.mimeType = mimeType;
119
- this.data = data;
120
- }
121
-
122
- /**
123
- * Convenience method that makes an InputStream out of
124
- * given text.
125
- */
126
- public Response( String status, String mimeType, String txt )
127
- {
128
- this.status = status;
129
- this.mimeType = mimeType;
130
- try
131
- {
132
- this.data = new ByteArrayInputStream( txt.getBytes("UTF-8"));
133
- }
134
- catch ( java.io.UnsupportedEncodingException uee )
135
- {
136
- uee.printStackTrace();
137
- }
138
- }
139
-
140
- /**
141
- * Adds given line to the header.
142
- */
143
- public void addHeader( String name, String value )
144
- {
145
- header.put( name, value );
146
- }
147
-
148
- /**
149
- * HTTP status code after processing, e.g. "200 OK", HTTP_OK
150
- */
151
- public String status;
152
-
153
- /**
154
- * MIME type of content, e.g. "text/html"
155
- */
156
- public String mimeType;
157
-
158
- /**
159
- * Data of the response, may be null.
160
- */
161
- public InputStream data;
162
-
163
- /**
164
- * Headers for the HTTP response. Use addHeader()
165
- * to add lines.
166
- */
167
- public Properties header = new Properties();
168
- }
169
-
170
- /**
171
- * Some HTTP response status codes
172
- */
173
- public static final String
174
- HTTP_OK = "200 OK",
175
- HTTP_PARTIALCONTENT = "206 Partial Content",
176
- HTTP_RANGE_NOT_SATISFIABLE = "416 Requested Range Not Satisfiable",
177
- HTTP_REDIRECT = "301 Moved Permanently",
178
- HTTP_NOTMODIFIED = "304 Not Modified",
179
- HTTP_FORBIDDEN = "403 Forbidden",
180
- HTTP_NOTFOUND = "404 Not Found",
181
- HTTP_BADREQUEST = "400 Bad Request",
182
- HTTP_INTERNALERROR = "500 Internal Server Error",
183
- HTTP_NOTIMPLEMENTED = "501 Not Implemented";
184
-
185
- /**
186
- * Common mime types for dynamic content
187
- */
188
- public static final String
189
- MIME_PLAINTEXT = "text/plain",
190
- MIME_HTML = "text/html",
191
- MIME_DEFAULT_BINARY = "application/octet-stream",
192
- MIME_XML = "text/xml";
193
-
194
- // ==================================================
195
- // Socket & server code
196
- // ==================================================
197
-
198
- /**
199
- * Starts a HTTP server to given port.<p>
200
- * Throws an IOException if the socket is already in use
201
- */
202
- public NanoHTTPD(int port, File wwwroot) throws IOException
203
- {
204
- myTcpPort = port;
205
- this.myRootDir = wwwroot;
206
- myServerSocket = new ServerSocket( myTcpPort );
207
- myThread = new Thread( new Runnable()
208
- {
209
- public void run()
210
- {
211
- try
212
- {
213
- while( true )
214
- new HTTPSession( myServerSocket.accept());
215
- }
216
- catch ( IOException ioe )
217
- {}
218
- }
219
- });
220
- myThread.setDaemon( true );
221
- myThread.start();
222
- }
223
-
224
- /**
225
- * Stops the server.
226
- */
227
- public void stop()
228
- {
229
- try
230
- {
231
- myServerSocket.close();
232
- myThread.join();
233
- }
234
- catch ( IOException ioe ) {}
235
- catch ( InterruptedException e ) {}
236
- }
237
-
238
-
239
- /**
240
- * Starts as a standalone file server and waits for Enter.
241
- */
242
- public static void main( String[] args )
243
- {
244
- myOut.println( "NanoHTTPD 1.25 (C) 2001,2005-2011 Jarno Elonen and (C) 2010 Konstantinos Togias\n" +
245
- "(Command line options: [-p port] [-d root-dir] [--licence])\n" );
246
-
247
- // Defaults
248
- int port = 80;
249
- File wwwroot = new File(".").getAbsoluteFile();
250
-
251
- // Show licence if requested
252
- for ( int i=0; i<args.length; ++i )
253
- if(args[i].equalsIgnoreCase("-p"))
254
- port = Integer.parseInt( args[i+1] );
255
- else if(args[i].equalsIgnoreCase("-d"))
256
- wwwroot = new File( args[i+1] ).getAbsoluteFile();
257
- else if ( args[i].toLowerCase().endsWith( "licence" ))
258
- {
259
- myOut.println( LICENCE + "\n" );
260
- break;
261
- }
262
-
263
- try
264
- {
265
- new NanoHTTPD( port, wwwroot );
266
- }
267
- catch( IOException ioe )
268
- {
269
- System.err.println( "Couldn't start server:\n" + ioe );
270
- System.exit( -1 );
271
- }
272
-
273
- myOut.println( "Now serving files in port " + port + " from \"" + wwwroot + "\"" );
274
- myOut.println( "Hit Enter to stop.\n" );
275
-
276
- try { System.in.read(); } catch( Throwable t ) {}
277
- }
278
-
279
- /**
280
- * Handles one session, i.e. parses the HTTP request
281
- * and returns the response.
282
- */
283
- private class HTTPSession implements Runnable
284
- {
285
- public HTTPSession( Socket s )
286
- {
287
- mySocket = s;
288
- Thread t = new Thread( this );
289
- t.setDaemon( true );
290
- t.start();
291
- }
292
-
293
- public void run()
294
- {
295
- try
296
- {
297
- InputStream is = mySocket.getInputStream();
298
- if ( is == null) return;
299
-
300
- // Read the first 8192 bytes.
301
- // The full header should fit in here.
302
- // Apache's default header limit is 8KB.
303
- int bufsize = 8192;
304
- byte[] buf = new byte[bufsize];
305
- int rlen = is.read(buf, 0, bufsize);
306
- if (rlen <= 0) return;
307
-
308
- // Create a BufferedReader for parsing the header.
309
- ByteArrayInputStream hbis = new ByteArrayInputStream(buf, 0, rlen);
310
- BufferedReader hin = new BufferedReader( new InputStreamReader( hbis ));
311
- Properties pre = new Properties();
312
- Properties parms = new Properties();
313
- Properties header = new Properties();
314
- Properties files = new Properties();
315
-
316
- // Decode the header into parms and header java properties
317
- decodeHeader(hin, pre, parms, header);
318
- String method = pre.getProperty("method");
319
- String uri = pre.getProperty("uri");
320
-
321
- long size = 0x7FFFFFFFFFFFFFFFl;
322
- String contentLength = header.getProperty("content-length");
323
- if (contentLength != null)
324
- {
325
- try { size = Integer.parseInt(contentLength); }
326
- catch (NumberFormatException ex) {}
327
- }
328
-
329
- // We are looking for the byte separating header from body.
330
- // It must be the last byte of the first two sequential new lines.
331
- int splitbyte = 0;
332
- boolean sbfound = false;
333
- while (splitbyte < rlen)
334
- {
335
- if (buf[splitbyte] == '\r' && buf[++splitbyte] == '\n' && buf[++splitbyte] == '\r' && buf[++splitbyte] == '\n') {
336
- sbfound = true;
337
- break;
338
- }
339
- splitbyte++;
340
- }
341
- splitbyte++;
342
-
343
- // Write the part of body already read to ByteArrayOutputStream f
344
- ByteArrayOutputStream f = new ByteArrayOutputStream();
345
- if (splitbyte < rlen) f.write(buf, splitbyte, rlen-splitbyte);
346
-
347
- // While Firefox sends on the first read all the data fitting
348
- // our buffer, Chrome and Opera sends only the headers even if
349
- // there is data for the body. So we do some magic here to find
350
- // out whether we have already consumed part of body, if we
351
- // have reached the end of the data to be sent or we should
352
- // expect the first byte of the body at the next read.
353
- if (splitbyte < rlen)
354
- size -= rlen - splitbyte +1;
355
- else if (!sbfound || size == 0x7FFFFFFFFFFFFFFFl)
356
- size = 0;
357
-
358
- // Now read all the body and write it to f
359
- buf = new byte[512];
360
- while ( rlen >= 0 && size > 0 )
361
- {
362
- rlen = is.read(buf, 0, 512);
363
- size -= rlen;
364
- if (rlen > 0)
365
- f.write(buf, 0, rlen);
366
- }
367
-
368
- // Get the raw body as a byte []
369
- byte [] fbuf = f.toByteArray();
370
-
371
- // Create a BufferedReader for easily reading it as string.
372
- ByteArrayInputStream bin = new ByteArrayInputStream(fbuf);
373
- BufferedReader in = new BufferedReader( new InputStreamReader(bin));
374
-
375
- // If the method is POST, there may be parameters
376
- // in data section, too, read it:
377
- if ( method.equalsIgnoreCase( "POST" ))
378
- {
379
- String contentType = "";
380
- String contentTypeHeader = header.getProperty("content-type");
381
- StringTokenizer st = new StringTokenizer( contentTypeHeader , "; " );
382
- if ( st.hasMoreTokens()) {
383
- contentType = st.nextToken();
384
- }
385
-
386
- if (contentType.equalsIgnoreCase("multipart/form-data"))
387
- {
388
- // Handle multipart/form-data
389
- if ( !st.hasMoreTokens())
390
- sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html" );
391
- String boundaryExp = st.nextToken();
392
- st = new StringTokenizer( boundaryExp , "=" );
393
- if (st.countTokens() != 2)
394
- sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary syntax error. Usage: GET /example/file.html" );
395
- st.nextToken();
396
- String boundary = st.nextToken();
397
-
398
- decodeMultipartData(boundary, fbuf, in, parms, files);
399
- }
400
- else
401
- {
402
- // Handle application/x-www-form-urlencoded
403
- String postLine = "";
404
- char pbuf[] = new char[512];
405
- int read = in.read(pbuf);
406
- while ( read >= 0 && !postLine.endsWith("\r\n") )
407
- {
408
- postLine += String.valueOf(pbuf, 0, read);
409
- read = in.read(pbuf);
410
- }
411
- postLine = postLine.trim();
412
- decodeParms( postLine, parms );
413
- }
414
- }
415
-
416
- if ( method.equalsIgnoreCase( "PUT" ))
417
- files.put("content", saveTmpFile( fbuf, 0, f.size()));
418
-
419
- // Ok, now do the serve()
420
- Response r = serve( uri, method, header, parms, files );
421
- if ( r == null )
422
- sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: Serve() returned a null response." );
423
- else
424
- sendResponse( r.status, r.mimeType, r.header, r.data );
425
-
426
- in.close();
427
- is.close();
428
- }
429
- catch ( IOException ioe )
430
- {
431
- try
432
- {
433
- sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
434
- }
435
- catch ( Throwable t ) {}
436
- }
437
- catch ( InterruptedException ie )
438
- {
439
- // Thrown by sendError, ignore and exit the thread.
440
- }
441
- }
442
-
443
- /**
444
- * Decodes the sent headers and loads the data into
445
- * java Properties' key - value pairs
446
- **/
447
- private void decodeHeader(BufferedReader in, Properties pre, Properties parms, Properties header)
448
- throws InterruptedException
449
- {
450
- try {
451
- // Read the request line
452
- String inLine = in.readLine();
453
- if (inLine == null) return;
454
- StringTokenizer st = new StringTokenizer( inLine );
455
- if ( !st.hasMoreTokens())
456
- sendError( HTTP_BADREQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html" );
457
-
458
- String method = st.nextToken();
459
- pre.put("method", method);
460
-
461
- if ( !st.hasMoreTokens())
462
- sendError( HTTP_BADREQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html" );
463
-
464
- String uri = st.nextToken();
465
-
466
- // Decode parameters from the URI
467
- int qmi = uri.indexOf( '?' );
468
- if ( qmi >= 0 )
469
- {
470
- decodeParms( uri.substring( qmi+1 ), parms );
471
- uri = decodePercent( uri.substring( 0, qmi ));
472
- }
473
- else uri = decodePercent(uri);
474
-
475
- // If there's another token, it's protocol version,
476
- // followed by HTTP headers. Ignore version but parse headers.
477
- // NOTE: this now forces header names lowercase since they are
478
- // case insensitive and vary by client.
479
- if ( st.hasMoreTokens())
480
- {
481
- String line = in.readLine();
482
- while ( line != null && line.trim().length() > 0 )
483
- {
484
- int p = line.indexOf( ':' );
485
- if ( p >= 0 )
486
- header.put( line.substring(0,p).trim().toLowerCase(), line.substring(p+1).trim());
487
- line = in.readLine();
488
- }
489
- }
490
-
491
- pre.put("uri", uri);
492
- }
493
- catch ( IOException ioe )
494
- {
495
- sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
496
- }
497
- }
498
-
499
- /**
500
- * Decodes the Multipart Body data and put it
501
- * into java Properties' key - value pairs.
502
- **/
503
- private void decodeMultipartData(String boundary, byte[] fbuf, BufferedReader in, Properties parms, Properties files)
504
- throws InterruptedException
505
- {
506
- try
507
- {
508
- int[] bpositions = getBoundaryPositions(fbuf,boundary.getBytes());
509
- int boundarycount = 1;
510
- String mpline = in.readLine();
511
- while ( mpline != null )
512
- {
513
- if (mpline.indexOf(boundary) == -1)
514
- sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html" );
515
- boundarycount++;
516
- Properties item = new Properties();
517
- mpline = in.readLine();
518
- while (mpline != null && mpline.trim().length() > 0)
519
- {
520
- int p = mpline.indexOf( ':' );
521
- if (p != -1)
522
- item.put( mpline.substring(0,p).trim().toLowerCase(), mpline.substring(p+1).trim());
523
- mpline = in.readLine();
524
- }
525
- if (mpline != null)
526
- {
527
- String contentDisposition = item.getProperty("content-disposition");
528
- if (contentDisposition == null)
529
- {
530
- sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html" );
531
- }
532
- StringTokenizer st = new StringTokenizer( contentDisposition , "; " );
533
- Properties disposition = new Properties();
534
- while ( st.hasMoreTokens())
535
- {
536
- String token = st.nextToken();
537
- int p = token.indexOf( '=' );
538
- if (p!=-1)
539
- disposition.put( token.substring(0,p).trim().toLowerCase(), token.substring(p+1).trim());
540
- }
541
- String pname = disposition.getProperty("name");
542
- pname = pname.substring(1,pname.length()-1);
543
-
544
- String value = "";
545
- if (item.getProperty("content-type") == null) {
546
- while (mpline != null && mpline.indexOf(boundary) == -1)
547
- {
548
- mpline = in.readLine();
549
- if ( mpline != null)
550
- {
551
- int d = mpline.indexOf(boundary);
552
- if (d == -1)
553
- value+=mpline;
554
- else
555
- value+=mpline.substring(0,d-2);
556
- }
557
- }
558
- }
559
- else
560
- {
561
- if (boundarycount> bpositions.length)
562
- sendError( HTTP_INTERNALERROR, "Error processing request" );
563
- int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount-2]);
564
- String path = saveTmpFile(fbuf, offset, bpositions[boundarycount-1]-offset-4);
565
- files.put(pname, path);
566
- value = disposition.getProperty("filename");
567
- value = value.substring(1,value.length()-1);
568
- do {
569
- mpline = in.readLine();
570
- } while (mpline != null && mpline.indexOf(boundary) == -1);
571
- }
572
- parms.put(pname, value);
573
- }
574
- }
575
- }
576
- catch ( IOException ioe )
577
- {
578
- sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
579
- }
580
- }
581
-
582
- /**
583
- * Find the byte positions where multipart boundaries start.
584
- **/
585
- public int[] getBoundaryPositions(byte[] b, byte[] boundary)
586
- {
587
- int matchcount = 0;
588
- int matchbyte = -1;
589
- Vector matchbytes = new Vector();
590
- for (int i=0; i<b.length; i++)
591
- {
592
- if (b[i] == boundary[matchcount])
593
- {
594
- if (matchcount == 0)
595
- matchbyte = i;
596
- matchcount++;
597
- if (matchcount==boundary.length)
598
- {
599
- matchbytes.addElement(new Integer(matchbyte));
600
- matchcount = 0;
601
- matchbyte = -1;
602
- }
603
- }
604
- else
605
- {
606
- i -= matchcount;
607
- matchcount = 0;
608
- matchbyte = -1;
609
- }
610
- }
611
- int[] ret = new int[matchbytes.size()];
612
- for (int i=0; i < ret.length; i++)
613
- {
614
- ret[i] = ((Integer)matchbytes.elementAt(i)).intValue();
615
- }
616
- return ret;
617
- }
618
-
619
- /**
620
- * Retrieves the content of a sent file and saves it
621
- * to a temporary file.
622
- * The full path to the saved file is returned.
623
- **/
624
- private String saveTmpFile(byte[] b, int offset, int len)
625
- {
626
- String path = "";
627
- if (len > 0)
628
- {
629
- String tmpdir = System.getProperty("java.io.tmpdir");
630
- try {
631
- File temp = File.createTempFile("NanoHTTPD", "", new File(tmpdir));
632
- OutputStream fstream = new FileOutputStream(temp);
633
- fstream.write(b, offset, len);
634
- fstream.close();
635
- path = temp.getAbsolutePath();
636
- } catch (Exception e) { // Catch exception if any
637
- System.err.println("Error: " + e.getMessage());
638
- }
639
- }
640
- return path;
641
- }
642
-
643
-
644
- /**
645
- * It returns the offset separating multipart file headers
646
- * from the file's data.
647
- **/
648
- private int stripMultipartHeaders(byte[] b, int offset)
649
- {
650
- int i = 0;
651
- for (i=offset; i<b.length; i++)
652
- {
653
- if (b[i] == '\r' && b[++i] == '\n' && b[++i] == '\r' && b[++i] == '\n')
654
- break;
655
- }
656
- return i+1;
657
- }
658
-
659
- /**
660
- * Decodes the percent encoding scheme. <br/>
661
- * For example: "an+example%20string" -> "an example string"
662
- */
663
- private String decodePercent( String str ) throws InterruptedException
664
- {
665
- try
666
- {
667
- StringBuffer sb = new StringBuffer();
668
- for( int i=0; i<str.length(); i++ )
669
- {
670
- char c = str.charAt( i );
671
- switch ( c )
672
- {
673
- case '+':
674
- sb.append( ' ' );
675
- break;
676
- case '%':
677
- sb.append((char)Integer.parseInt( str.substring(i+1,i+3), 16 ));
678
- i += 2;
679
- break;
680
- default:
681
- sb.append( c );
682
- break;
683
- }
684
- }
685
- return sb.toString();
686
- }
687
- catch( Exception e )
688
- {
689
- sendError( HTTP_BADREQUEST, "BAD REQUEST: Bad percent-encoding." );
690
- return null;
691
- }
692
- }
693
-
694
- /**
695
- * Decodes parameters in percent-encoded URI-format
696
- * ( e.g. "name=Jack%20Daniels&pass=Single%20Malt" ) and
697
- * adds them to given Properties. NOTE: this doesn't support multiple
698
- * identical keys due to the simplicity of Properties -- if you need multiples,
699
- * you might want to replace the Properties with a Hashtable of Vectors or such.
700
- */
701
- private void decodeParms( String parms, Properties p )
702
- throws InterruptedException
703
- {
704
- if ( parms == null )
705
- return;
706
-
707
- StringTokenizer st = new StringTokenizer( parms, "&" );
708
- while ( st.hasMoreTokens())
709
- {
710
- String e = st.nextToken();
711
- int sep = e.indexOf( '=' );
712
- if ( sep >= 0 )
713
- p.put( decodePercent( e.substring( 0, sep )).trim(),
714
- decodePercent( e.substring( sep+1 )));
715
- }
716
- }
717
-
718
- /**
719
- * Returns an error message as a HTTP response and
720
- * throws InterruptedException to stop further request processing.
721
- */
722
- private void sendError( String status, String msg ) throws InterruptedException
723
- {
724
- sendResponse( status, MIME_PLAINTEXT, null, new ByteArrayInputStream( msg.getBytes()));
725
- throw new InterruptedException();
726
- }
727
-
728
- /**
729
- * Sends given response to the socket.
730
- */
731
- private void sendResponse( String status, String mime, Properties header, InputStream data )
732
- {
733
- try
734
- {
735
- if ( status == null )
736
- throw new Error( "sendResponse(): Status can't be null." );
737
-
738
- OutputStream out = mySocket.getOutputStream();
739
- PrintWriter pw = new PrintWriter( out );
740
- pw.print("HTTP/1.0 " + status + " \r\n");
741
-
742
- if ( mime != null )
743
- pw.print("Content-Type: " + mime + "\r\n");
744
-
745
- if ( header == null || header.getProperty( "Date" ) == null )
746
- pw.print( "Date: " + gmtFrmt.format( new Date()) + "\r\n");
747
-
748
- if ( header != null )
749
- {
750
- Enumeration e = header.keys();
751
- while ( e.hasMoreElements())
752
- {
753
- String key = (String)e.nextElement();
754
- String value = header.getProperty( key );
755
- pw.print( key + ": " + value + "\r\n");
756
- }
757
- }
758
-
759
- pw.print("\r\n");
760
- pw.flush();
761
-
762
- if ( data != null )
763
- {
764
- int pending = data.available(); // This is to support partial sends, see serveFile()
765
- byte[] buff = new byte[theBufferSize];
766
- while (pending>0)
767
- {
768
- int read = data.read( buff, 0, ( (pending>theBufferSize) ? theBufferSize : pending ));
769
- if (read <= 0) break;
770
- out.write( buff, 0, read );
771
- pending -= read;
772
- }
773
- }
774
- out.flush();
775
- out.close();
776
- if ( data != null )
777
- data.close();
778
- }
779
- catch( IOException ioe )
780
- {
781
- // Couldn't write? No can do.
782
- try { mySocket.close(); } catch( Throwable t ) {}
783
- }
784
- }
785
-
786
- private Socket mySocket;
787
- }
788
-
789
- /**
790
- * URL-encodes everything between "/"-characters.
791
- * Encodes spaces as '%20' instead of '+'.
792
- */
793
- private String encodeUri( String uri )
794
- {
795
- String newUri = "";
796
- StringTokenizer st = new StringTokenizer( uri, "/ ", true );
797
- while ( st.hasMoreTokens())
798
- {
799
- String tok = st.nextToken();
800
- if ( tok.equals( "/" ))
801
- newUri += "/";
802
- else if ( tok.equals( " " ))
803
- newUri += "%20";
804
- else
805
- {
806
- newUri += URLEncoder.encode( tok );
807
- // For Java 1.4 you'll want to use this instead:
808
- // try { newUri += URLEncoder.encode( tok, "UTF-8" ); } catch ( java.io.UnsupportedEncodingException uee ) {}
809
- }
810
- }
811
- return newUri;
812
- }
813
-
814
- private int myTcpPort;
815
- private final ServerSocket myServerSocket;
816
- private Thread myThread;
817
- private File myRootDir;
818
-
819
- // ==================================================
820
- // File server code
821
- // ==================================================
822
-
823
- /**
824
- * Serves file from homeDir and its' subdirectories (only).
825
- * Uses only URI, ignores all headers and HTTP parameters.
826
- */
827
- public Response serveFile( String uri, Properties header, File homeDir,
828
- boolean allowDirectoryListing )
829
- {
830
- Response res = null;
831
-
832
- // Make sure we won't die of an exception later
833
- if ( !homeDir.isDirectory())
834
- res = new Response( HTTP_INTERNALERROR, MIME_PLAINTEXT,
835
- "INTERNAL ERRROR: serveFile(): given homeDir is not a directory." );
836
-
837
- if ( res == null )
838
- {
839
- // Remove URL arguments
840
- uri = uri.trim().replace( File.separatorChar, '/' );
841
- if ( uri.indexOf( '?' ) >= 0 )
842
- uri = uri.substring(0, uri.indexOf( '?' ));
843
-
844
- // Prohibit getting out of current directory
845
- if ( uri.startsWith( ".." ) || uri.endsWith( ".." ) || uri.indexOf( "../" ) >= 0 )
846
- res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT,
847
- "FORBIDDEN: Won't serve ../ for security reasons." );
848
- }
849
-
850
- File f = new File( homeDir, uri );
851
- if ( res == null && !f.exists())
852
- res = new Response( HTTP_NOTFOUND, MIME_PLAINTEXT,
853
- "Error 404, file not found." );
854
-
855
- // List the directory, if necessary
856
- if ( res == null && f.isDirectory())
857
- {
858
- // Browsers get confused without '/' after the
859
- // directory, send a redirect.
860
- if ( !uri.endsWith( "/" ))
861
- {
862
- uri += "/";
863
- res = new Response( HTTP_REDIRECT, MIME_HTML,
864
- "<html><body>Redirected: <a href=\"" + uri + "\">" +
865
- uri + "</a></body></html>");
866
- res.addHeader( "Location", uri );
867
- }
868
-
869
- if ( res == null )
870
- {
871
- // First try index.html and index.htm
872
- if ( new File( f, "index.html" ).exists())
873
- f = new File( homeDir, uri + "/index.html" );
874
- else if ( new File( f, "index.htm" ).exists())
875
- f = new File( homeDir, uri + "/index.htm" );
876
- // No index file, list the directory if it is readable
877
- else if ( allowDirectoryListing && f.canRead() )
878
- {
879
- String[] files = f.list();
880
- String msg = "<html><body><h1>Directory " + uri + "</h1><br/>";
881
-
882
- if ( uri.length() > 1 )
883
- {
884
- String u = uri.substring( 0, uri.length()-1 );
885
- int slash = u.lastIndexOf( '/' );
886
- if ( slash >= 0 && slash < u.length())
887
- msg += "<b><a href=\"" + uri.substring(0, slash+1) + "\">..</a></b><br/>";
888
- }
889
-
890
- if (files!=null)
891
- {
892
- for ( int i=0; i<files.length; ++i )
893
- {
894
- File curFile = new File( f, files[i] );
895
- boolean dir = curFile.isDirectory();
896
- if ( dir )
897
- {
898
- msg += "<b>";
899
- files[i] += "/";
900
- }
901
-
902
- msg += "<a href=\"" + encodeUri( uri + files[i] ) + "\">" +
903
- files[i] + "</a>";
904
-
905
- // Show file size
906
- if ( curFile.isFile())
907
- {
908
- long len = curFile.length();
909
- msg += " &nbsp;<font size=2>(";
910
- if ( len < 1024 )
911
- msg += len + " bytes";
912
- else if ( len < 1024 * 1024 )
913
- msg += len/1024 + "." + (len%1024/10%100) + " KB";
914
- else
915
- msg += len/(1024*1024) + "." + len%(1024*1024)/10%100 + " MB";
916
-
917
- msg += ")</font>";
918
- }
919
- msg += "<br/>";
920
- if ( dir ) msg += "</b>";
921
- }
922
- }
923
- msg += "</body></html>";
924
- res = new Response( HTTP_OK, MIME_HTML, msg );
925
- }
926
- else
927
- {
928
- res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT,
929
- "FORBIDDEN: No directory listing." );
930
- }
931
- }
932
- }
933
-
934
- try
935
- {
936
- if ( res == null )
937
- {
938
- // Get MIME type from file name extension, if possible
939
- String mime = null;
940
- int dot = f.getCanonicalPath().lastIndexOf( '.' );
941
- if ( dot >= 0 )
942
- mime = (String)theMimeTypes.get( f.getCanonicalPath().substring( dot + 1 ).toLowerCase());
943
- if ( mime == null )
944
- mime = MIME_DEFAULT_BINARY;
945
-
946
- // Calculate etag
947
- String etag = Integer.toHexString((f.getAbsolutePath() + f.lastModified() + "" + f.length()).hashCode());
948
-
949
- // Support (simple) skipping:
950
- long startFrom = 0;
951
- long endAt = -1;
952
- String range = header.getProperty( "range" );
953
- if ( range != null )
954
- {
955
- if ( range.startsWith( "bytes=" ))
956
- {
957
- range = range.substring( "bytes=".length());
958
- int minus = range.indexOf( '-' );
959
- try {
960
- if ( minus > 0 )
961
- {
962
- startFrom = Long.parseLong( range.substring( 0, minus ));
963
- endAt = Long.parseLong( range.substring( minus+1 ));
964
- }
965
- }
966
- catch ( NumberFormatException nfe ) {}
967
- }
968
- }
969
-
970
- // Change return code and add Content-Range header when skipping is requested
971
- long fileLen = f.length();
972
- if (range != null && startFrom >= 0)
973
- {
974
- if ( startFrom >= fileLen)
975
- {
976
- res = new Response( HTTP_RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "" );
977
- res.addHeader( "Content-Range", "bytes 0-0/" + fileLen);
978
- res.addHeader( "ETag", etag);
979
- }
980
- else
981
- {
982
- if ( endAt < 0 )
983
- endAt = fileLen-1;
984
- long newLen = endAt - startFrom + 1;
985
- if ( newLen < 0 ) newLen = 0;
986
-
987
- final long dataLen = newLen;
988
- FileInputStream fis = new FileInputStream( f ) {
989
- public int available() throws IOException { return (int)dataLen; }
990
- };
991
- fis.skip( startFrom );
992
-
993
- res = new Response( HTTP_PARTIALCONTENT, mime, fis );
994
- res.addHeader( "Content-Length", "" + dataLen);
995
- res.addHeader( "Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen);
996
- res.addHeader( "ETag", etag);
997
- }
998
- }
999
- else
1000
- {
1001
- if (etag.equals(header.getProperty("if-none-match")))
1002
- res = new Response( HTTP_NOTMODIFIED, mime, "");
1003
- else
1004
- {
1005
- res = new Response( HTTP_OK, mime, new FileInputStream( f ));
1006
- res.addHeader( "Content-Length", "" + fileLen);
1007
- res.addHeader( "ETag", etag);
1008
- }
1009
- }
1010
- }
1011
- }
1012
- catch( IOException ioe )
1013
- {
1014
- res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Reading file failed." );
1015
- }
1016
-
1017
- res.addHeader( "Accept-Ranges", "bytes"); // Announce that the file server accepts partial content requestes
1018
- return res;
1019
- }
1020
-
1021
- /**
1022
- * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE
1023
- */
1024
- private static Hashtable theMimeTypes = new Hashtable();
1025
- static
1026
- {
1027
- StringTokenizer st = new StringTokenizer(
1028
- "css text/css "+
1029
- "htm text/html "+
1030
- "html text/html "+
1031
- "xml text/xml "+
1032
- "txt text/plain "+
1033
- "asc text/plain "+
1034
- "gif image/gif "+
1035
- "jpg image/jpeg "+
1036
- "jpeg image/jpeg "+
1037
- "png image/png "+
1038
- "mp3 audio/mpeg "+
1039
- "m3u audio/mpeg-url " +
1040
- "mp4 video/mp4 " +
1041
- "ogv video/ogg " +
1042
- "flv video/x-flv " +
1043
- "mov video/quicktime " +
1044
- "swf application/x-shockwave-flash " +
1045
- "js application/javascript "+
1046
- "pdf application/pdf "+
1047
- "doc application/msword "+
1048
- "ogg application/x-ogg "+
1049
- "zip application/octet-stream "+
1050
- "exe application/octet-stream "+
1051
- "class application/octet-stream " );
1052
- while ( st.hasMoreTokens())
1053
- theMimeTypes.put( st.nextToken(), st.nextToken());
1054
- }
1055
-
1056
- private static int theBufferSize = 16 * 1024;
1057
-
1058
- // Change this if you want to log to somewhere else than stdout
1059
- protected static PrintStream myOut = System.out;
1060
-
1061
- /**
1062
- * GMT date formatter
1063
- */
1064
- private static java.text.SimpleDateFormat gmtFrmt;
1065
- static
1066
- {
1067
- gmtFrmt = new java.text.SimpleDateFormat( "E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
1068
- gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
1069
- }
1070
-
1071
- /**
1072
- * The distribution licence
1073
- */
1074
- private static final String LICENCE =
1075
- "Copyright (C) 2001,2005-2011 by Jarno Elonen <elonen@iki.fi>\n"+
1076
- "and Copyright (C) 2010 by Konstantinos Togias <info@ktogias.gr>\n"+
1077
- "\n"+
1078
- "Redistribution and use in source and binary forms, with or without\n"+
1079
- "modification, are permitted provided that the following conditions\n"+
1080
- "are met:\n"+
1081
- "\n"+
1082
- "Redistributions of source code must retain the above copyright notice,\n"+
1083
- "this list of conditions and the following disclaimer. Redistributions in\n"+
1084
- "binary form must reproduce the above copyright notice, this list of\n"+
1085
- "conditions and the following disclaimer in the documentation and/or other\n"+
1086
- "materials provided with the distribution. The name of the author may not\n"+
1087
- "be used to endorse or promote products derived from this software without\n"+
1088
- "specific prior written permission. \n"+
1089
- " \n"+
1090
- "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"+
1091
- "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"+
1092
- "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"+
1093
- "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"+
1094
- "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"+
1095
- "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"+
1096
- "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"+
1097
- "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"+
1098
- "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"+
1099
- "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.";
1100
- }