sproutcore 1.6.0.1 → 1.7.1.beta

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 (143) hide show
  1. data/CHANGELOG +21 -0
  2. data/Gemfile +5 -0
  3. data/Rakefile +26 -13
  4. data/VERSION.yml +2 -2
  5. data/lib/Buildfile +43 -4
  6. data/lib/buildtasks/build.rake +10 -0
  7. data/lib/buildtasks/helpers/file_rule.rb +22 -0
  8. data/lib/buildtasks/helpers/file_rule_list.rb +137 -0
  9. data/lib/buildtasks/manifest.rake +133 -122
  10. data/lib/frameworks/sproutcore/CHANGELOG.md +69 -2
  11. data/lib/frameworks/sproutcore/apps/tests/english.lproj/strings.js +1 -0
  12. data/lib/frameworks/sproutcore/frameworks/bootstrap/system/browser.js +28 -22
  13. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/array.js +9 -5
  14. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/controller.js +1 -1
  15. data/lib/frameworks/sproutcore/frameworks/core_foundation/controls/button.js +18 -13
  16. data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/bind.js +5 -3
  17. data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/collection.js +2 -0
  18. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/action_support.js +80 -0
  19. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/template_helpers/text_field_support.js +84 -116
  20. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane.js +8 -5
  21. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/event.js +157 -157
  22. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/platform.js +5 -3
  23. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +6 -6
  24. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/sparse_array.js +10 -7
  25. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/mixins/action_support.js +106 -0
  26. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/collection.js +18 -0
  27. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/handlebars.js +71 -1
  28. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/attribute_bindings_test.js +38 -0
  29. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/class_name_bindings_test.js +47 -0
  30. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutChildViews.js +18 -18
  31. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutStyle.js +42 -10
  32. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/keyboard.js +26 -1
  33. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +14 -8
  34. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +158 -1
  35. data/lib/frameworks/sproutcore/frameworks/datastore/models/record.js +15 -2
  36. data/lib/frameworks/sproutcore/frameworks/datastore/models/record_attribute.js +108 -108
  37. data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +1 -1
  38. data/lib/frameworks/sproutcore/frameworks/datastore/system/record_array.js +2 -4
  39. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/error_methods.js +2 -2
  40. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/single_attribute.js +26 -0
  41. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/query/builders.js +7 -0
  42. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/record_array/error_methods.js +1 -1
  43. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +4 -1
  44. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/tests/system/datetime.js +6 -0
  45. data/lib/frameworks/sproutcore/frameworks/desktop/panes/menu.js +26 -5
  46. data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +97 -96
  47. data/lib/frameworks/sproutcore/frameworks/desktop/system/drag.js +4 -3
  48. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/menu/ui.js +17 -4
  49. data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +7 -7
  50. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_item.js +7 -5
  51. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll.js +12 -3
  52. data/lib/frameworks/sproutcore/frameworks/desktop/views/web.js +23 -14
  53. data/lib/frameworks/sproutcore/frameworks/experimental/Buildfile +5 -1
  54. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/render_delegates/menu_scroller.js +28 -0
  55. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/tests/menu/scroll.js +235 -0
  56. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroll.js +363 -0
  57. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroller.js +250 -0
  58. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/desktop_scroller.js +92 -0
  59. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/native_scroll.js +25 -0
  60. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/scroll.js +33 -0
  61. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/touch_scroller.js +76 -0
  62. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/integration.js +50 -0
  63. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/methods.js +143 -0
  64. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/ui.js +258 -0
  65. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroll.js +1164 -0
  66. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroller.js +332 -0
  67. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroll.js +236 -0
  68. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroller.js +347 -0
  69. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroll.js +15 -0
  70. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroller.js +10 -0
  71. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroll.js +804 -0
  72. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroller.js +133 -0
  73. data/lib/frameworks/sproutcore/frameworks/foundation/resources/text_field.css +3 -3
  74. data/lib/frameworks/sproutcore/frameworks/foundation/validators/number.js +3 -1
  75. data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +3 -3
  76. data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +2 -1
  77. data/lib/frameworks/sproutcore/frameworks/media/views/controls.js +2 -1
  78. data/lib/frameworks/sproutcore/frameworks/media/views/media_slider.js +2 -4
  79. data/lib/frameworks/sproutcore/frameworks/media/views/mini_controls.js +2 -4
  80. data/lib/frameworks/sproutcore/frameworks/media/views/simple_controls.js +2 -4
  81. data/lib/frameworks/sproutcore/frameworks/media/views/video.js +2 -2
  82. data/lib/frameworks/sproutcore/frameworks/routing/system/routes.js +29 -3
  83. data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
  84. data/lib/frameworks/sproutcore/frameworks/runtime/debug/test_suites/array/replace.js +1 -1
  85. data/lib/frameworks/sproutcore/frameworks/runtime/private/property_chain.js +2 -1
  86. data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +3 -3
  87. data/lib/frameworks/sproutcore/frameworks/runtime/system/index_set.js +2 -2
  88. data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +1 -1
  89. data/lib/frameworks/sproutcore/themes/ace/resources/collection/normal/list_item.css +2 -2
  90. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/segmented.css +1 -1
  91. data/lib/gen/app/templates/apps/@target_name@/Buildfile +3 -5
  92. data/lib/gen/app/templates/apps/@target_name@/resources/_theme.css +18 -0
  93. data/lib/gen/project/templates/@filename@/Buildfile +2 -2
  94. data/lib/sproutcore/builders/chance_file.rb +9 -16
  95. data/lib/sproutcore/builders/html.rb +2 -1
  96. data/lib/sproutcore/builders/minify.rb +4 -35
  97. data/lib/sproutcore/builders/module.rb +38 -1
  98. data/lib/sproutcore/builders/split.rb +63 -0
  99. data/lib/sproutcore/builders/strings.rb +7 -1
  100. data/lib/sproutcore/builders.rb +1 -0
  101. data/lib/sproutcore/helpers/css_split.rb +190 -0
  102. data/lib/sproutcore/helpers/entry_sorter.rb +2 -0
  103. data/lib/sproutcore/helpers/minifier.rb +40 -16
  104. data/lib/sproutcore/helpers/static_helper.rb +35 -17
  105. data/lib/sproutcore/helpers.rb +1 -1
  106. data/lib/sproutcore/models/manifest.rb +26 -0
  107. data/lib/sproutcore/models/target.rb +12 -1
  108. data/lib/sproutcore/rack/proxy.rb +244 -225
  109. data/lib/sproutcore/rack/restrict_ip.rb +67 -0
  110. data/lib/sproutcore/rack/service.rb +8 -2
  111. data/lib/sproutcore/rack.rb +1 -0
  112. data/lib/sproutcore/tools/build.rb +91 -43
  113. data/lib/sproutcore/tools/gen.rb +2 -3
  114. data/lib/sproutcore/tools/manifest.rb +22 -16
  115. data/lib/sproutcore/tools/server.rb +21 -0
  116. data/lib/sproutcore/tools.rb +102 -46
  117. data/lib/sproutcore.rb +30 -5
  118. data/spec/buildtasks/helpers/accept_list +22 -0
  119. data/spec/buildtasks/helpers/accept_list.rb +128 -0
  120. data/spec/buildtasks/helpers/list.json +11 -0
  121. data/spec/buildtasks/manifest/prepare_build_tasks/chance_2x_spec.rb +1 -39
  122. data/spec/buildtasks/manifest/prepare_build_tasks/chance_spec.rb +0 -38
  123. data/spec/buildtasks/manifest/prepare_build_tasks/combine_spec.rb +4 -4
  124. data/spec/buildtasks/manifest/prepare_build_tasks/module_spec.rb +2 -2
  125. data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_indirect_spec.rb +7 -16
  126. data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_spec.rb +7 -17
  127. data/spec/buildtasks/manifest/prepare_build_tasks/packed_spec.rb +11 -6
  128. data/spec/fixtures/builder_tests/Buildfile +2 -1
  129. data/spec/fixtures/builder_tests/apps/module_test/modules/required_module/core.js +0 -0
  130. data/spec/lib/builders/module_spec.rb +1 -1
  131. data/spec/spec_helper.rb +1 -0
  132. data/sproutcore.gemspec +4 -9
  133. data/vendor/chance/lib/chance/factory.rb +45 -0
  134. data/vendor/chance/lib/chance/instance/data_url.rb +0 -29
  135. data/vendor/chance/lib/chance/instance/slicing.rb +57 -4
  136. data/vendor/chance/lib/chance/instance/spriting.rb +112 -21
  137. data/vendor/chance/lib/chance/instance.rb +173 -28
  138. data/vendor/chance/lib/chance/parser.rb +80 -52
  139. data/vendor/chance/lib/chance.rb +25 -6
  140. data/vendor/sproutcore/SCCompiler.jar +0 -0
  141. data/vendor/sproutcore/lib/args4j-2.0.12.jar +0 -0
  142. data/vendor/sproutcore/lib/yuicompressor-2.4.2.jar +0 -0
  143. metadata +97 -38
@@ -14,299 +14,318 @@ rescue LoadError => e
14
14
  SC::HTTPS_ENABLED = false
15
15
  end
16
16
 
17
- require 'eventmachine'
18
- require 'em-http'
19
- require 'thin'
20
-
17
+ begin
18
+ require 'eventmachine'
19
+ require 'em-http'
20
+ require 'thin'
21
+
22
+ SC::PROXY_ENABLED = true
23
+ rescue LoadError => e
24
+ SC::PROXY_ENABLED = false
25
+ end
21
26
 
22
- module SC
27
+ if SC::PROXY_ENABLED
28
+ # There are cases where we cannot load the proxy and don't need to (build environment)
29
+
30
+ module SC
23
31
 
24
- module Rack
32
+ module Rack
25
33
 
26
- class DeferrableBody
27
- include EM::Deferrable
34
+ class DeferrableBody
35
+ include EM::Deferrable
28
36
 
29
- def initialize(options = {})
30
- @options = options
31
- end
37
+ def initialize(options = {})
38
+ @options = options
39
+ end
32
40
 
33
- def call(body)
34
- body.each do |chunk|
35
- @body_callback.call(prepare_chunk(chunk))
41
+ def call(body)
42
+ body.each do |chunk|
43
+ @body_callback.call(prepare_chunk(chunk))
44
+ end
36
45
  end
37
- end
38
46
 
39
- def prepare_chunk(chunk)
40
- if chunked?
41
- size = chunk.respond_to?(:bytesize) ? chunk.bytesize : chunk.length
42
- "#{size.to_s(16)}\r\n#{chunk}\r\n"
43
- else
44
- # Thin doesn't like null bodies
45
- chunk || ''
47
+ def prepare_chunk(chunk)
48
+ if chunked?
49
+ size = chunk.respond_to?(:bytesize) ? chunk.bytesize : chunk.length
50
+ "#{size.to_s(16)}\r\n#{chunk}\r\n"
51
+ else
52
+ # Thin doesn't like null bodies
53
+ chunk || ''
54
+ end
46
55
  end
47
- end
48
56
 
49
- def each(&blk)
50
- @body_callback = blk
51
- end
57
+ def each(&blk)
58
+ @body_callback = blk
59
+ end
52
60
 
53
- private
61
+ private
54
62
 
55
- def chunked?
56
- @options[:chunked]
63
+ def chunked?
64
+ @options[:chunked]
65
+ end
57
66
  end
58
- end
59
67
 
60
68
 
61
69
 
62
- # Rack application proxies requests as needed for the given project.
63
- class Proxy
64
-
65
- def initialize(project)
66
- @project = project
67
- @proxies = project.buildfile.proxies
68
- end
69
-
70
- def call(env)
71
- path = env['PATH_INFO']
72
-
73
- @proxies.each do |proxy, value|
74
- # If the url matches a proxied url, handle it
75
- if path.match(/^#{Regexp.escape(proxy.to_s)}/)
76
- handle_proxy(value, proxy.to_s, env)
70
+ # Rack application proxies requests as needed for the given project.
71
+ class Proxy
77
72
 
78
- # Don't block waiting for a response
79
- throw :async
80
- end
73
+ def initialize(project)
74
+ @project = project
75
+ @proxies = project.buildfile.proxies
81
76
  end
82
77
 
83
- return [404, {}, "not found"]
84
- end
78
+ def call(env)
79
+ path = env['PATH_INFO']
85
80
 
86
- def chunked?(headers)
87
- headers['Transfer-Encoding'] == "chunked"
88
- end
81
+ @proxies.each do |proxy, value|
82
+ # If the url matches a proxied url, handle it
83
+ if path.match(/^#{Regexp.escape(proxy.to_s)}/)
84
+ handle_proxy(value, proxy.to_s, env)
89
85
 
90
- def handle_proxy(proxy, proxy_url, env)
86
+ # Don't block waiting for a response
87
+ throw :async
88
+ end
89
+ end
91
90
 
92
- if proxy[:secure] && !SC::HTTPS_ENABLED
93
- SC.logger << "~ WARNING: HTTPS is not supported on your system, using HTTP instead.\n"
94
- SC.logger << " If you are using Ubuntu, you can run `apt-get install libopenssl-ruby`\n"
95
- proxy[:secure] = false
91
+ return [404, {}, "not found"]
96
92
  end
97
93
 
98
- headers = request_headers(env, proxy) # ex. {"Host"=>"localhost:4020", "Connection"=>"...
99
- path = env['PATH_INFO'] # ex. /contacts
100
- params = env['QUERY_STRING'] # ex. since=yesterday&unread=true
94
+ def chunked?(headers)
95
+ headers['Transfer-Encoding'] == "chunked"
96
+ end
101
97
 
102
- # Switch to https if proxy[:secure] configured
103
- protocol = proxy[:secure] ? 'https' : 'http'
98
+ def handle_proxy(proxy, proxy_url, env)
104
99
 
105
- # Adjust the path if proxy[:url] configured
106
- if proxy[:url]
107
- path = path.sub(/^#{Regexp.escape proxy_url}/, proxy[:url])
108
- end
100
+ if proxy[:secure] && !SC::HTTPS_ENABLED
101
+ SC.logger << "~ WARNING: HTTPS is not supported on your system, using HTTP instead.\n"
102
+ SC.logger << " If you are using Ubuntu, you can run `apt-get install libopenssl-ruby`\n"
103
+ proxy[:secure] = false
104
+ end
109
105
 
110
- # The endpoint URL
111
- url = protocol + '://' + proxy[:to]
112
- url += path unless path.empty?
113
- url += '?' + params unless params.empty?
106
+ headers = request_headers(env, proxy) # ex. {"Host"=>"localhost:4020", "Connection"=>"...
107
+ path = env['PATH_INFO'] # ex. /contacts
108
+ params = env['QUERY_STRING'] # ex. since=yesterday&unread=true
114
109
 
115
- if env['CONTENT_LENGTH'] || env['HTTP_TRANSFER_ENCODING']
116
- req_body = env['rack.input']
117
- req_body.rewind # May not be necessary but can't hurt
110
+ # Switch to https if proxy[:secure] configured
111
+ protocol = proxy[:secure] ? 'https' : 'http'
118
112
 
119
- req_body = req_body.read
120
- end
113
+ # Adjust the path if proxy[:url] configured
114
+ if proxy[:url]
115
+ path = path.sub(/^#{Regexp.escape proxy_url}/, proxy[:url])
116
+ end
121
117
 
122
- # Options for the request
123
- request_options = {}
124
- request_options[:head] = headers
125
- request_options[:body] = req_body if !!req_body
126
- request_options[:timeout] = proxy[:timeout] if proxy[:timeout]
127
- request_options[:redirects] = 10 if proxy[:redirect] != false
128
-
129
- EventMachine.run {
130
- body = nil
131
- conn = EM::HttpRequest.new(url)
132
- chunked = false
133
- headers = {}
134
- method = env['REQUEST_METHOD'].upcase
135
- status = 0
136
-
137
- case method
138
- when 'GET'
139
- http = conn.get request_options
140
- when 'POST'
141
- http = conn.post request_options
142
- when 'PUT'
143
- http = conn.put request_options
144
- when 'DELETE'
145
- http = conn.delete request_options
146
- else
147
- http = conn.head request_options
148
- end
118
+ # The endpoint URL
119
+ url = protocol + '://' + proxy[:to]
120
+ url += path unless path.empty?
121
+ url += '?' + params unless params.empty?
149
122
 
150
- # Received error
151
- http.errback {
152
- status = http.response_header.status
153
- path = env['PATH_INFO']
154
- url = http.last_effective_url
155
- SC.logger << "~ PROXY FAILED: #{method} #{path} -> #{status} #{url}\n"
156
-
157
- # If a body has been sent use it, otherwise respond with generic message
158
- if !body
159
- body = "Unable to proxy to #{url}. Received status: #{status}"
160
- size = body.respond_to?(:bytesize) ? body.bytesize : body.length
161
- headers = { 'Content-Length' => size.to_s }
162
- body = [body]
163
- end
123
+ if env['CONTENT_LENGTH'] || env['HTTP_TRANSFER_ENCODING']
124
+ req_body = env['rack.input']
125
+ req_body.rewind # May not be necessary but can't hurt
164
126
 
165
- env['async.callback'].call [502, headers, body]
166
- }
127
+ req_body = req_body.read
128
+ end
167
129
 
168
- # Received response
169
- http.callback {
170
-
171
- # Too many redirects
172
- if redirect? status
173
- body = "Unable to proxy to #{url}. Too many redirects."
174
- size = body.respond_to?(:bytesize) ? body.bytesize : body.length
175
- headers = { 'Content-Length' => size.to_s }
176
-
177
- env['async.callback'].call [502, headers, [body]]
178
- else
179
- # Terminate the deferred body (which may have been chunked)
180
- if body
181
- body.call ['']
182
- body.succeed
130
+ # Options for the request
131
+ request_options = {}
132
+ request_options[:head] = headers
133
+ request_options[:body] = req_body if !!req_body
134
+ request_options[:timeout] = proxy[:timeout] if proxy[:timeout]
135
+ request_options[:redirects] = 10 if proxy[:redirect] != false
136
+ request_options[:decoding] = false # don't decode gzipped content
137
+
138
+ EventMachine.run {
139
+ body = nil
140
+ conn = EM::HttpRequest.new(url)
141
+ chunked = false
142
+ headers = {}
143
+ method = env['REQUEST_METHOD'].upcase
144
+ status = 0
145
+
146
+ case method
147
+ when 'GET'
148
+ http = conn.get request_options
149
+ when 'POST'
150
+ http = conn.post request_options
151
+ when 'PUT'
152
+ http = conn.put request_options
153
+ when 'DELETE'
154
+ http = conn.delete request_options
155
+ else
156
+ http = conn.head request_options
183
157
  end
184
158
 
185
- # Log the initial path and the final url
159
+ # Received error
160
+ http.errback {
161
+ status = http.response_header.status
186
162
  path = env['PATH_INFO']
187
163
  url = http.last_effective_url
188
- SC.logger << "~ PROXY: #{method} #{path} -> #{status} #{url}\n"
189
- end
190
- }
164
+ SC.logger << "~ PROXY FAILED: #{method} #{path} -> #{status} #{url}\n"
191
165
 
192
- # Received headers
193
- http.headers { |hash|
194
- status = http.response_header.status
166
+ # If a body has been sent use it, otherwise respond with generic message
167
+ if !body
168
+ body = "Unable to proxy to #{url}. Received status: #{status}"
169
+ size = body.respond_to?(:bytesize) ? body.bytesize : body.length
170
+ headers = { 'Content-Length' => size.to_s }
171
+ body = [body]
172
+ end
195
173
 
196
- headers = response_headers(hash)
174
+ env['async.callback'].call [502, headers, body]
175
+ }
197
176
 
198
- # Don't respond on redirection, but fail out on bad redirects
199
- if redirect? status
177
+ # Received response
178
+ http.callback {
200
179
 
201
- if status == 304
202
- env["async.callback"].call [status, headers, ['']]
203
- SC.logger << "~ PROXY: #{method} #{path} -> #{status} #{url}\n"
204
- elsif !headers['Location']
205
- body = "Unable to proxy to #{url}. Received redirect with no Location."
180
+ # Too many redirects
181
+ if redirect? status
182
+ body = "Unable to proxy to #{url}. Too many redirects."
206
183
  size = body.respond_to?(:bytesize) ? body.bytesize : body.length
207
184
  headers = { 'Content-Length' => size.to_s }
208
185
 
209
- http.close
186
+ env['async.callback'].call [502, headers, [body]]
187
+ else
188
+ # Terminate the deferred body (which may have been chunked)
189
+ if body
190
+ body.call ['']
191
+ body.succeed
192
+ end
193
+
194
+ # Log the initial path and the final url
195
+ path = env['PATH_INFO']
196
+ url = http.last_effective_url
197
+ SC.logger << "~ PROXY: #{method} #{path} -> #{status} #{url}\n"
198
+ end
199
+ }
200
+
201
+ # Received headers
202
+ http.headers { |hash|
203
+ status = http.response_header.status
204
+
205
+ headers = response_headers(hash)
206
+
207
+ # Don't respond on redirection, but fail out on bad redirects
208
+ if redirect? status
209
+
210
+ if status == 304
211
+ env["async.callback"].call [status, headers, ['']]
212
+ SC.logger << "~ PROXY: #{method} #{path} -> #{status} #{url}\n"
213
+ elsif !headers['Location']
214
+ body = "Unable to proxy to #{url}. Received redirect with no Location."
215
+ size = body.respond_to?(:bytesize) ? body.bytesize : body.length
216
+ headers = { 'Content-Length' => size.to_s }
217
+
218
+ http.close
219
+ end
220
+
221
+ else
222
+ # Stream the body right across in the format it was sent
223
+ chunked = chunked?(headers)
224
+ body = DeferrableBody.new({ :chunked => chunked })
225
+
226
+ # Start responding to the client immediately
227
+ env["async.callback"].call [status, headers, body]
210
228
  end
229
+ }
211
230
 
212
- else
213
- # Stream the body right across in the format it was sent
214
- chunked = chunked?(headers)
215
- body = DeferrableBody.new({ :chunked => chunked })
231
+ # Received chunk of data
232
+ http.stream { |chunk|
233
+ # Ignore body of redirects
234
+ if !redirect? status
235
+ body.call [chunk]
236
+ end
237
+ }
216
238
 
217
- # Start responding to the client immediately
218
- env["async.callback"].call [status, headers, body]
219
- end
220
- }
239
+ # If the client disconnects early, make sure we close our other connection too
240
+ # TODO: this is waiting for changes not yet available in em-http
241
+ # Test with: curl http://0.0.0.0:4020/stream.twitter.com/1/statuses/sample.json -uTWITTER_USERNAME:TWITTER_PASSWORD
242
+ # env["async.close"].callback {
243
+ # conn.close
244
+ # }
221
245
 
222
- # Received chunk of data
223
- http.stream { |chunk|
224
- # Ignore body of redirects
225
- if !redirect? status
226
- body.call [chunk]
227
- end
228
246
  }
229
- }
230
- end
247
+ end
231
248
 
232
- def redirect?(status)
233
- status >= 300 && status < 400
234
- end
249
+ def redirect?(status)
250
+ status >= 300 && status < 400
251
+ end
235
252
 
236
- # collect headers...
237
- def request_headers(env, proxy)
238
- result = {}
239
- env.each do |key, value|
240
- next unless key =~ /^HTTP_/
253
+ # collect headers...
254
+ def request_headers(env, proxy)
255
+ result = {}
256
+ env.each do |key, value|
257
+ next unless key =~ /^HTTP_/
241
258
 
242
- key = headerize(key)
243
- if !key.eql? "Version"
244
- result[key] = value
259
+ key = headerize(key)
260
+ if !key.eql? "Version"
261
+ result[key] = value
262
+ end
245
263
  end
246
- end
247
264
 
248
- # Rack documentation says CONTENT_TYPE and CONTENT_LENGTH aren't prefixed by HTTP_
249
- result['Content-Type'] = env['CONTENT_TYPE'] if env['CONTENT_TYPE']
265
+ # Rack documentation says CONTENT_TYPE and CONTENT_LENGTH aren't prefixed by HTTP_
266
+ result['Content-Type'] = env['CONTENT_TYPE'] if env['CONTENT_TYPE']
250
267
 
251
- length = env['CONTENT_LENGTH']
252
- result['Content-Length'] = length if length
268
+ length = env['CONTENT_LENGTH']
269
+ result['Content-Length'] = length if length
253
270
 
254
- # added 4/23/09 per Charles Jolley, corrects problem
255
- # when making requests to virtual hosts
256
- result['Host'] = proxy[:to]
271
+ # added 4/23/09 per Charles Jolley, corrects problem
272
+ # when making requests to virtual hosts
273
+ result['Host'] = proxy[:to]
257
274
 
258
- result
259
- end
275
+ result
276
+ end
260
277
 
261
- # construct and display specific response headers
262
- def response_headers(hash)
263
- result = {}
278
+ # construct and display specific response headers
279
+ def response_headers(hash)
280
+ result = {}
264
281
 
265
- hash.each do |key, value|
266
- key = headerize(key)
282
+ hash.each do |key, value|
283
+ key = headerize(key)
267
284
 
268
- # Because Set-Cookie header can appear more the once in the response body,
269
- # but Rack only accepts a hash of headers, we store it in a line break separated string
270
- # for Ruby 1.9 and as an Array for Ruby 1.8
271
- # See http://groups.google.com/group/rack-devel/browse_thread/thread/e8759b91a82c5a10/a8dbd4574fe97d69?#a8dbd4574fe97d69
272
- if key.downcase == 'set-cookie'
273
- cookies = []
285
+ # Because Set-Cookie header can appear more the once in the response body,
286
+ # but Rack only accepts a hash of headers, we store it in a line break separated string
287
+ # for Ruby 1.9 and as an Array for Ruby 1.8
288
+ # See http://groups.google.com/group/rack-devel/browse_thread/thread/e8759b91a82c5a10/a8dbd4574fe97d69?#a8dbd4574fe97d69
289
+ if key.downcase == 'set-cookie'
290
+ cookies = []
274
291
 
275
- case value
276
- when Array then value.each { |c| cookies << strip_domain(c) }
277
- when Hash then value.each { |_, c| cookies << strip_domain(c) }
278
- else cookies << strip_domain(value)
279
- end
292
+ case value
293
+ when Array then value.each { |c| cookies << strip_domain(c) }
294
+ when Hash then value.each { |_, c| cookies << strip_domain(c) }
295
+ else cookies << strip_domain(value)
296
+ end
280
297
 
281
- # Remove nil values
282
- result['Set-Cookie'] = [result['Set-Cookie'], cookies].compact
298
+ # Remove nil values
299
+ result['Set-Cookie'] = [result['Set-Cookie'], cookies].compact
283
300
 
284
- if Thin.ruby_18?
285
- result['Set-Cookie'].flatten!
286
- else
287
- result['Set-Cookie'] = result['Set-Cookie'].join("\n")
301
+ if Thin.ruby_18?
302
+ result['Set-Cookie'].flatten!
303
+ else
304
+ result['Set-Cookie'] = result['Set-Cookie'].join("\n")
305
+ end
288
306
  end
307
+
308
+ SC.logger << " #{key}: #{value}\n"
309
+ result[key] = value
289
310
  end
290
311
 
291
- SC.logger << " #{key}: #{value}\n"
292
- result[key] = value
312
+ ::Rack::Utils::HeaderHash.new(result)
293
313
  end
294
314
 
295
- ::Rack::Utils::HeaderHash.new(result)
296
- end
297
-
298
- # remove HTTP_, dasherize and titleize
299
- def headerize(str)
300
- parts = str.gsub(/^HTTP_/, '').split('_')
301
- parts.map! { |p| p.capitalize }.join('-')
302
- end
315
+ # remove HTTP_, dasherize and titleize
316
+ def headerize(str)
317
+ parts = str.gsub(/^HTTP_/, '').split('_')
318
+ parts.map! { |p| p.capitalize }.join('-')
319
+ end
303
320
 
304
- # Strip out the domain of passed in cookie. This technically may
305
- # break certain scenarios where services try to set cross-domain
306
- # cookies, but those services should not be doing that anyway...
307
- def strip_domain(cookie)
308
- cookie.to_s.gsub!(/domain=[^\;]+\;? ?/,'')
321
+ # Strip out the domain of passed in cookie. This technically may
322
+ # break certain scenarios where services try to set cross-domain
323
+ # cookies, but those services should not be doing that anyway...
324
+ def strip_domain(cookie)
325
+ cookie.to_s.gsub!(/domain=[^\;]+\;? ?/,'')
326
+ end
309
327
  end
310
328
  end
311
329
  end
312
- end
330
+
331
+ end
@@ -0,0 +1,67 @@
1
+ # ===========================================================================
2
+ # Project: Abbot - SproutCore Build Tools
3
+ # Copyright: ©2009-2011 Apple Inc.
4
+ # portions copyright @2006-2011 Strobe Inc.
5
+ # and contributors
6
+ # ===========================================================================
7
+
8
+ # For all those working in internet cafes...
9
+ # We feel for you. Go to a real cafe instead. They have internet, too.
10
+ module SC
11
+ module Rack
12
+ class RestrictIP
13
+ def initialize(app, allow_ips=[])
14
+ @app = app
15
+ @allow = allow_ips
16
+ end
17
+
18
+ # checks if an IP, such as 127.0.0.1, matches a mask, such as 127.*.*.*
19
+ def ip_is_valid(ip, mask)
20
+ ip_parts = ip.split('.')
21
+ mask_parts = mask.split('.')
22
+
23
+ if mask_parts.length != 4
24
+ SC.logger.fatal "Invalid IP mask: #{mask}\n"
25
+ exit
26
+ end
27
+
28
+ ip_idx = 0
29
+ mask_parts.each {|mask_part|
30
+ ip_part = ip_parts[ip_idx]
31
+
32
+ # * means anything matches
33
+ if mask_part == '*'
34
+ next
35
+ end
36
+
37
+ if ip_part != mask_part
38
+ return false
39
+ end
40
+
41
+ ip_idx = ip_idx + 1
42
+ }
43
+
44
+ return true
45
+ end
46
+
47
+ def call(env)
48
+ ip = env['REMOTE_ADDR']
49
+
50
+ is_valid = false
51
+ @allow.each {|mask|
52
+ if ip_is_valid(ip, mask)
53
+ is_valid = true
54
+ break
55
+ end
56
+ }
57
+
58
+ if is_valid
59
+ return @app.call(env)
60
+ else
61
+ SC.logger << "Blocked connection attempt by ip: #{ip}\n"
62
+ return [403, { 'Content-Type' => 'text/plain' }, "YOU CANNOT BEEZ HERE."]
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -59,7 +59,7 @@ module SC
59
59
  self.filesystem = opts[:Filesystem]
60
60
 
61
61
  projects = opts.delete(:projects) || [opts.delete(:project)].compact
62
- app = self.new(*projects)
62
+ app = self.new(*projects, opts)
63
63
 
64
64
  opts[:Host] ||= opts[:host] # allow either case.
65
65
  opts[:Port] ||= opts[:port] || '4020'
@@ -93,7 +93,7 @@ module SC
93
93
  server.run app, opts
94
94
  end
95
95
 
96
- def initialize(*projects)
96
+ def initialize(*projects, opts)
97
97
  @projects = projects.flatten
98
98
 
99
99
  # Get apps for each project & cascade if needed
@@ -105,6 +105,12 @@ module SC
105
105
  @app = ::Rack::ConditionalGet.new(@app)
106
106
  #@app = ::Rack::Deflater.new(@app)
107
107
 
108
+ # preprocess IPs so we can restrict properly
109
+ ips = opts[:allow_from_ips] || '127.0.0.1'
110
+ SC.logger << "Allowing access only from IPs: #{ips}. Use --allow-from-ips='*.*.*.*' to allow all\n"
111
+ ips = ips.split(',')
112
+
113
+ @app = SC::Rack::RestrictIP.new(@app, ips)
108
114
  end
109
115
 
110
116
  def call(env); @app.call(env); end
@@ -2,5 +2,6 @@ require "sproutcore/rack/builder"
2
2
  require "sproutcore/rack/dev"
3
3
  require "sproutcore/rack/filesystem"
4
4
  require "sproutcore/rack/proxy"
5
+ require 'sproutcore/rack/restrict_ip'
5
6
  require "sproutcore/rack/service"
6
7
  require "sproutcore/rack/test_runner"