sproutcore 1.6.0.1 → 1.7.1.beta

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