sinatra 1.4.8 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sinatra might be problematic. Click here for more details.

Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -47
  3. data/CONTRIBUTING.md +1 -1
  4. data/Gemfile +37 -49
  5. data/MAINTENANCE.md +42 -0
  6. data/README.de.md +5 -5
  7. data/README.es.md +5 -5
  8. data/README.fr.md +9 -9
  9. data/README.hu.md +3 -3
  10. data/README.ja.md +19 -8
  11. data/README.ko.md +8 -8
  12. data/README.md +90 -61
  13. data/README.pt-br.md +3 -3
  14. data/README.pt-pt.md +2 -2
  15. data/README.ru.md +42 -26
  16. data/README.zh.md +8 -8
  17. data/Rakefile +0 -6
  18. data/SECURITY.md +35 -0
  19. data/lib/sinatra/base.rb +113 -161
  20. data/lib/sinatra/main.rb +1 -0
  21. data/lib/sinatra/show_exceptions.rb +8 -8
  22. data/lib/sinatra/version.rb +1 -1
  23. data/sinatra.gemspec +7 -4
  24. metadata +34 -168
  25. data/lib/sinatra/ext.rb +0 -17
  26. data/test/asciidoctor_test.rb +0 -72
  27. data/test/base_test.rb +0 -167
  28. data/test/builder_test.rb +0 -91
  29. data/test/coffee_test.rb +0 -96
  30. data/test/compile_test.rb +0 -183
  31. data/test/contest.rb +0 -91
  32. data/test/creole_test.rb +0 -65
  33. data/test/delegator_test.rb +0 -160
  34. data/test/encoding_test.rb +0 -20
  35. data/test/erb_test.rb +0 -116
  36. data/test/extensions_test.rb +0 -98
  37. data/test/filter_test.rb +0 -487
  38. data/test/haml_test.rb +0 -109
  39. data/test/helper.rb +0 -132
  40. data/test/helpers_test.rb +0 -1917
  41. data/test/integration/app.rb +0 -79
  42. data/test/integration_helper.rb +0 -236
  43. data/test/integration_test.rb +0 -104
  44. data/test/less_test.rb +0 -69
  45. data/test/liquid_test.rb +0 -77
  46. data/test/mapped_error_test.rb +0 -285
  47. data/test/markaby_test.rb +0 -80
  48. data/test/markdown_test.rb +0 -85
  49. data/test/mediawiki_test.rb +0 -68
  50. data/test/middleware_test.rb +0 -68
  51. data/test/nokogiri_test.rb +0 -67
  52. data/test/public/favicon.ico +0 -0
  53. data/test/public/hello+world.txt +0 -1
  54. data/test/rabl_test.rb +0 -89
  55. data/test/rack_test.rb +0 -45
  56. data/test/radius_test.rb +0 -59
  57. data/test/rdoc_test.rb +0 -66
  58. data/test/readme_test.rb +0 -130
  59. data/test/request_test.rb +0 -100
  60. data/test/response_test.rb +0 -63
  61. data/test/result_test.rb +0 -76
  62. data/test/route_added_hook_test.rb +0 -59
  63. data/test/routing_test.rb +0 -1456
  64. data/test/sass_test.rb +0 -115
  65. data/test/scss_test.rb +0 -88
  66. data/test/server_test.rb +0 -56
  67. data/test/settings_test.rb +0 -582
  68. data/test/sinatra_test.rb +0 -12
  69. data/test/slim_test.rb +0 -102
  70. data/test/static_test.rb +0 -266
  71. data/test/streaming_test.rb +0 -149
  72. data/test/stylus_test.rb +0 -90
  73. data/test/templates_test.rb +0 -382
  74. data/test/textile_test.rb +0 -65
  75. data/test/views/a/in_a.str +0 -1
  76. data/test/views/ascii.erb +0 -2
  77. data/test/views/b/in_b.str +0 -1
  78. data/test/views/calc.html.erb +0 -1
  79. data/test/views/error.builder +0 -3
  80. data/test/views/error.erb +0 -3
  81. data/test/views/error.haml +0 -3
  82. data/test/views/error.sass +0 -2
  83. data/test/views/explicitly_nested.str +0 -1
  84. data/test/views/foo/hello.test +0 -1
  85. data/test/views/hello.asciidoc +0 -1
  86. data/test/views/hello.builder +0 -1
  87. data/test/views/hello.coffee +0 -1
  88. data/test/views/hello.creole +0 -1
  89. data/test/views/hello.erb +0 -1
  90. data/test/views/hello.haml +0 -1
  91. data/test/views/hello.less +0 -5
  92. data/test/views/hello.liquid +0 -1
  93. data/test/views/hello.mab +0 -1
  94. data/test/views/hello.md +0 -1
  95. data/test/views/hello.mediawiki +0 -1
  96. data/test/views/hello.nokogiri +0 -1
  97. data/test/views/hello.rabl +0 -2
  98. data/test/views/hello.radius +0 -1
  99. data/test/views/hello.rdoc +0 -1
  100. data/test/views/hello.sass +0 -2
  101. data/test/views/hello.scss +0 -3
  102. data/test/views/hello.slim +0 -1
  103. data/test/views/hello.str +0 -1
  104. data/test/views/hello.styl +0 -2
  105. data/test/views/hello.test +0 -1
  106. data/test/views/hello.textile +0 -1
  107. data/test/views/hello.wlang +0 -1
  108. data/test/views/hello.yajl +0 -1
  109. data/test/views/layout2.builder +0 -3
  110. data/test/views/layout2.erb +0 -2
  111. data/test/views/layout2.haml +0 -2
  112. data/test/views/layout2.liquid +0 -2
  113. data/test/views/layout2.mab +0 -2
  114. data/test/views/layout2.nokogiri +0 -3
  115. data/test/views/layout2.rabl +0 -3
  116. data/test/views/layout2.radius +0 -2
  117. data/test/views/layout2.slim +0 -3
  118. data/test/views/layout2.str +0 -2
  119. data/test/views/layout2.test +0 -1
  120. data/test/views/layout2.wlang +0 -2
  121. data/test/views/nested.str +0 -1
  122. data/test/views/utf8.erb +0 -2
  123. data/test/wlang_test.rb +0 -87
  124. data/test/yajl_test.rb +0 -86
@@ -180,7 +180,7 @@ end
180
180
  Rotas podem casar com expressões regulares:
181
181
 
182
182
  ```ruby
183
- get /\A\/ola\/([\w]+)\z/ do
183
+ get /\/ola\/([\w]+)/ do
184
184
  "Olá, #{params['captures'].first}!"
185
185
  end
186
186
  ```
@@ -362,7 +362,7 @@ end
362
362
  Ou, usando algo mais denso à frente:
363
363
 
364
364
  ```ruby
365
- get %r{^(?!/index$)} do
365
+ get %r{(?!/index)} do
366
366
  # ...
367
367
  end
368
368
  ```
@@ -1417,7 +1417,7 @@ configure do
1417
1417
  end
1418
1418
  ```
1419
1419
 
1420
- Rodando somente quando o ambiente (`RACK_ENV` environment variável) é
1420
+ Rodando somente quando o ambiente (`APP_ENV` environment variável) é
1421
1421
  setado para `:production`:
1422
1422
 
1423
1423
  ```ruby
@@ -88,7 +88,7 @@ end
88
88
  Rotas correspondem-se com expressões regulares:
89
89
 
90
90
  ```ruby
91
- get /\A\/ola\/([\w]+)\z/ do
91
+ get /\/ola\/([\w]+)/ do
92
92
  "Olá, #{params['captures'].first}!"
93
93
  end
94
94
  ```
@@ -477,7 +477,7 @@ configure do
477
477
  end
478
478
  ```
479
479
 
480
- Correndo somente quando o ambiente (`RACK_ENV` environment variável) é
480
+ Correndo somente quando o ambiente (`APP_ENV` environment variável) é
481
481
  definido para `:production`:
482
482
 
483
483
  ```ruby
@@ -42,6 +42,7 @@
42
42
  * [Фильтры](#Фильтры)
43
43
  * [Методы-помощники](#Методы-помощники)
44
44
  * [Использование сессий](#Использование-сессий)
45
+ * [Выбор вашей собственной "прослойки" сессии](#Выбор-вашей-собственной-прослойки-сессий)
45
46
  * [Прерывание](#Прерывание)
46
47
  * [Передача](#Передача)
47
48
  * [Вызов другого маршрута](#Вызов-другого-маршрута)
@@ -208,7 +209,7 @@ end
208
209
  Регулярные выражения в качестве шаблонов маршрутов:
209
210
 
210
211
  ```ruby
211
- get /\A\/hello\/([\w]+)\z/ do
212
+ get /\/hello\/([\w]+)/ do
212
213
  "Hello, #{params['captures'].first}!"
213
214
  end
214
215
  ```
@@ -377,7 +378,7 @@ end
377
378
  Или с использованием негативного просмотра вперед:
378
379
 
379
380
  ```ruby
380
- get %r{^(?!/index$)} do
381
+ get %r{(?!/index)} do
381
382
  # ...
382
383
  end
383
384
  ```
@@ -1341,25 +1342,6 @@ get '/:value' do
1341
1342
  end
1342
1343
  ```
1343
1344
 
1344
- Заметьте, что при использовании `enable :sessions` все данные сохраняются в
1345
- куках (cookies). Это может быть не совсем то, что вы хотите (например,
1346
- сохранение больших объемов данных увеличит ваш трафик). В таком случае вы
1347
- можете использовать альтернативную Rack "прослойку" (middleware), реализующую
1348
- механизм сессий. Для этого *не надо* вызывать `enable :sessions`, вместо этого
1349
- следует подключить ее так же, как и любую другую "прослойку":
1350
-
1351
- ```ruby
1352
- use Rack::Session::Pool, :expire_after => 2592000
1353
-
1354
- get '/' do
1355
- "value = " << session['value'].inspect
1356
- end
1357
-
1358
- get '/:value' do
1359
- session['value'] = params['value']
1360
- end
1361
- ```
1362
-
1363
1345
  Для повышения безопасности данные сессии в куках подписываются секретным
1364
1346
  ключом. Секретный ключ генерируется Sinatra. Тем не менее, так как этот ключ
1365
1347
  будет меняться с каждым запуском приложения, вы, возможно, захотите установить
@@ -1384,6 +1366,40 @@ foo.com, добавьте *.* перед доменом:
1384
1366
  set :sessions, :domain => '.foo.com'
1385
1367
  ```
1386
1368
 
1369
+ #### Выбор вашей собственной "прослойки" сессии
1370
+
1371
+ Заметьте, что при использовании `enable :sessions` все данные сохраняются в
1372
+ куках (cookies). Это может быть не совсем то, что вы хотите (например,
1373
+ сохранение больших объемов данных увеличит ваш трафик). В таком случае вы
1374
+ можете использовать альтернативную Rack "прослойку" (middleware), реализующую
1375
+ механизм сессий. Для этого используете один из способов ниже:
1376
+
1377
+ ```ruby
1378
+ enable :sessions
1379
+ set :session_store, Rack::Session::Pool
1380
+ ```
1381
+
1382
+ Или установите параметры сессии с помощью хеша опций:
1383
+
1384
+ ```ruby
1385
+ set :sessions, :expire_after => 2592000
1386
+ set :session_store, Rack::Session::Pool
1387
+ ```
1388
+
1389
+ Вы так же можете не вызывать `enable :sessions`, а вместо этого вызывать
1390
+ необходимую вам прослойку так же, как вы это обычно делаете. Очень важно
1391
+ обратить внимание на то, что когда вы используете этот метод, основной способ
1392
+ защиты сессии **не будет включен по умолчанию**. Если вы хотите включить защиту,
1393
+ вам нужно добавить следующие строчки:
1394
+
1395
+ ```ruby
1396
+ use Rack::Session::Pool, :expire_after => 2592000
1397
+ use Rack::Protection::RemoteToken
1398
+ use Rack::Protection::SessionHijacking
1399
+ ```
1400
+
1401
+ Смотрите секцию "Настройка защиты от атак" для более подробной информации.
1402
+
1387
1403
  ### Прерывание
1388
1404
 
1389
1405
  Чтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута,
@@ -2022,7 +2038,7 @@ configure do
2022
2038
  end
2023
2039
  ```
2024
2040
 
2025
- Будет запущено, когда окружение (RACK_ENV переменная) `:production`:
2041
+ Будет запущено, когда окружение (APP_ENV переменная) `:production`:
2026
2042
 
2027
2043
  ```ruby
2028
2044
  configure :production do
@@ -2121,8 +2137,8 @@ set :protection, :except => [:path_traversal, :session_hijacking]
2121
2137
 
2122
2138
  <dt>environment</dt>
2123
2139
  <dd>
2124
- текущее окружение, по умолчанию, значение <tt>ENV['RACK_ENV']</tt> или
2125
- <tt>"development"</tt>, если <tt>ENV['RACK_ENV']</tt> недоступна.
2140
+ текущее окружение, по умолчанию, значение <tt>ENV['APP_ENV']</tt> или
2141
+ <tt>"development"</tt>, если <tt>ENV['APP_ENV']</tt> недоступна.
2126
2142
  </dd>
2127
2143
 
2128
2144
  <dt>logging</dt>
@@ -2249,7 +2265,7 @@ set :protection, :except => [:path_traversal, :session_hijacking]
2249
2265
  ## Режим, окружение
2250
2266
 
2251
2267
  Есть 3 предопределенных режима, окружения: `"development"`, `"production"` и
2252
- `"test"`. Режим может быть задан через переменную окружения `RACK_ENV`.
2268
+ `"test"`. Режим может быть задан через переменную окружения `APP_ENV`.
2253
2269
  Значение по умолчанию — `"development"`. В этом режиме работы все шаблоны
2254
2270
  перезагружаются между запросами. А также задаются специальные обработчики
2255
2271
  `not_found` и `error`, чтобы вы могли увидеть стек вызовов. В окружениях
@@ -2412,7 +2428,7 @@ class MyAppTest < Minitest::Test
2412
2428
  assert_equal 'Hello Frank!', last_response.body
2413
2429
  end
2414
2430
 
2415
- def test_with_rack_env
2431
+ def test_with_user_agent
2416
2432
  get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
2417
2433
  assert_equal "You're using Songbird!", last_response.body
2418
2434
  end
@@ -205,7 +205,7 @@ end
205
205
  通过正则表达式匹配路由:
206
206
 
207
207
  ```ruby
208
- get /\A\/hello\/([\w]+)\z/ do
208
+ get /\/hello\/([\w]+)/ do
209
209
  "Hello, #{params['captures'].first}!"
210
210
  end
211
211
  ```
@@ -372,7 +372,7 @@ end
372
372
  或者,使用消极向前查找:
373
373
 
374
374
  ```ruby
375
- get %r{^(?!/index$)} do
375
+ get %r{(?!/index)} do
376
376
  # ...
377
377
  end
378
378
  ```
@@ -1933,7 +1933,7 @@ configure do
1933
1933
  end
1934
1934
  ```
1935
1935
 
1936
- 只有当环境 (`RACK_ENV` 环境变量) 被设定为 `:production` 时才运行:
1936
+ 只有当环境 (`APP_ENV` 环境变量) 被设定为 `:production` 时才运行:
1937
1937
 
1938
1938
  ```ruby
1939
1939
  configure :production do
@@ -2033,8 +2033,8 @@ set :protection, :session => true
2033
2033
 
2034
2034
  <dt>environment</dt>
2035
2035
  <dd>
2036
- 当前环境,默认是 <tt>ENV['RACK_ENV']</tt>,
2037
- 或者 <tt>"development"</tt> (如果 ENV['RACK_ENV'] 不可用)。
2036
+ 当前环境,默认是 <tt>ENV['APP_ENV']</tt>,
2037
+ 或者 <tt>"development"</tt> (如果 ENV['APP_ENV'] 不可用)。
2038
2038
  </dd>
2039
2039
 
2040
2040
  <dt>logging</dt>
@@ -2145,15 +2145,15 @@ set :protection, :session => true
2145
2145
  ## 环境
2146
2146
 
2147
2147
  Sinatra 中有三种预先定义的环境:"development"、"production" 和 "test"。
2148
- 环境可以通过 `RACK_ENV` 环境变量设置。默认值为 "development"。
2148
+ 环境可以通过 `APP_ENV` 环境变量设置。默认值为 "development"。
2149
2149
  在开发环境下,每次请求都会重新加载所有模板,
2150
2150
  特殊的 `not_found` 和 `error` 错误处理器会在浏览器中显示 stack trace。
2151
2151
  在测试和生产环境下,模板默认会缓存。
2152
2152
 
2153
- 在不同的环境下运行,设置 `RACK_ENV` 环境变量:
2153
+ 在不同的环境下运行,设置 `APP_ENV` 环境变量:
2154
2154
 
2155
2155
  ```shell
2156
- RACK_ENV=production ruby my_app.rb
2156
+ APP_ENV=production ruby my_app.rb
2157
2157
  ```
2158
2158
 
2159
2159
  可以使用预定义的三种方法: `development?`、`test?` 和 `production?` 来检查当前环境:
data/Rakefile CHANGED
@@ -3,12 +3,6 @@ require 'rake/testtask'
3
3
  require 'fileutils'
4
4
  require 'date'
5
5
 
6
- # CI Reporter is only needed for the CI
7
- begin
8
- require 'ci/reporter/rake/test_unit'
9
- rescue LoadError
10
- end
11
-
12
6
  task :default => :test
13
7
  task :spec => :test
14
8
 
@@ -0,0 +1,35 @@
1
+ # Reporting a security bug
2
+
3
+ All security bugs in Sinatra should be reported to the core team through our private mailing list [sinatra-security@googlegroups.com](https://groups.google.com/group/sinatra-security). Your report will be acknowledged within 24 hours, and you’ll receive a more detailed response to your email within 48 hours indicating the next steps in handling your report.
4
+
5
+ After the initial reply to your report the security team will endeavor to keep you informed of the progress being made towards a fix and full announcement. These updates will be sent at least every five days, in reality this is more likely to be every 24-48 hours.
6
+
7
+ If you have not received a reply to your email within 48 hours, or have not heard from the security team for the past five days there are a few steps you can take:
8
+
9
+ * Contact the current security coordinator [Zachary Scott](mailto:zzak@ruby-lang.org) directly
10
+
11
+ ## Disclosure Policy
12
+
13
+ Sinatra has a 5 step disclosure policy, that is upheld to the best of our ability.
14
+
15
+ 1. Security report received and is assigned a primary handler. This person will coordinate the fix and release process.
16
+ 2. Problem is confirmed and, a list of all affected versions is determined. Code is audited to find any potential similar problems.
17
+ 3. Fixes are prepared for all releases which are still supported. These fixes are not committed to the public repository but rather held locally pending the announcement.
18
+ 4. A suggested embargo date for this vulnerability is chosen and distros@openwall is notified. This notification will include patches for all versions still under support and a contact address for packagers who need advice back-porting patches to older versions.
19
+ 5. On the embargo date, the [mailing list][mailing-list] and [security list][security-list] are sent a copy of the announcement. The changes are pushed to the public repository and new gems released to rubygems.
20
+
21
+ Typically the embargo date will be set 72 hours from the time vendor-sec is first notified, however this may vary depending on the severity of the bug or difficulty in applying a fix.
22
+
23
+ This process can take some time, especially when coordination is required with maintainers of other projects. Every effort will be made to handle the bug in as timely a manner as possible, however it’s important that we follow the release process above to ensure that the disclosure is handled in a consistent manner.
24
+
25
+ ## Security Updates
26
+
27
+ Security updates will be posted on the [mailing list][mailing-list] and [security list][security-list].
28
+
29
+ ## Comments on this Policy
30
+
31
+ If you have any suggestions to improve this policy, please send an email the core team at [sinatrarb@googlegroups.com](https://groups.google.com/group/sinatrarb).
32
+
33
+
34
+ [mailing-list]: http://groups.google.com/group/sinatrarb/topics
35
+ [security-list]: http://groups.google.com/group/sinatra-security/topics
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # external dependencies
2
4
  require 'rack'
3
5
  require 'tilt'
4
6
  require 'rack/protection'
7
+ require 'mustermann'
8
+ require 'mustermann/sinatra'
9
+ require 'mustermann/regular'
5
10
 
6
11
  # stdlib dependencies
7
12
  require 'thread'
@@ -10,7 +15,6 @@ require 'uri'
10
15
 
11
16
  # other files we need
12
17
  require 'sinatra/show_exceptions'
13
- require 'sinatra/ext'
14
18
  require 'sinatra/version'
15
19
 
16
20
  module Sinatra
@@ -69,6 +73,12 @@ module Sinatra
69
73
  request_method == "UNLINK"
70
74
  end
71
75
 
76
+ def params
77
+ super
78
+ rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
79
+ raise BadRequest, "Invalid query parameters: #{e.message}"
80
+ end
81
+
72
82
  private
73
83
 
74
84
  class AcceptEntry
@@ -151,7 +161,7 @@ module Sinatra
151
161
  if calculate_content_length?
152
162
  # if some other code has already set Content-Length, don't muck with it
153
163
  # currently, this would be the static file-handler
154
- headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
164
+ headers["Content-Length"] = body.inject(0) { |l, p| l + p.bytesize }.to_s
155
165
  end
156
166
 
157
167
  [status.to_i, headers, result]
@@ -221,6 +231,10 @@ module Sinatra
221
231
  end
222
232
  end
223
233
 
234
+ class BadRequest < TypeError #:nodoc:
235
+ def http_status; 400 end
236
+ end
237
+
224
238
  class NotFound < NameError #:nodoc:
225
239
  def http_status; 404 end
226
240
  end
@@ -229,7 +243,7 @@ module Sinatra
229
243
  module Helpers
230
244
  # Set or retrieve the response status code.
231
245
  def status(value = nil)
232
- response.status = value if value
246
+ response.status = Rack::Utils.status_code(value) if value
233
247
  response.status
234
248
  end
235
249
 
@@ -240,7 +254,11 @@ module Sinatra
240
254
  def block.each; yield(call) end
241
255
  response.body = block
242
256
  elsif value
243
- headers.delete 'Content-Length' unless request.head? || value.is_a?(Rack::File) || value.is_a?(Stream)
257
+ # Rack 2.0 returns a Rack::File::Iterator here instead of
258
+ # Rack::File as it was in the previous API.
259
+ unless request.head? || value.is_a?(Rack::File::Iterator) || value.is_a?(Stream)
260
+ headers.delete 'Content-Length'
261
+ end
244
262
  response.body = value
245
263
  else
246
264
  response.body
@@ -264,8 +282,8 @@ module Sinatra
264
282
  # Generates the absolute URI for a given path in the app.
265
283
  # Takes Rack routers and reverse proxies into account.
266
284
  def uri(addr = nil, absolute = true, add_script_name = true)
267
- return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/
268
- uri = [host = ""]
285
+ return addr if addr =~ /\A[a-z][a-z0-9\+\.\-]*:/i
286
+ uri = [host = String.new]
269
287
  if absolute
270
288
  host << "http#{'s' if request.secure?}://"
271
289
  if request.forwarded? or request.port != (request.secure? ? 443 : 80)
@@ -339,7 +357,7 @@ module Sinatra
339
357
 
340
358
  # Set the Content-Disposition to "attachment" with the specified filename,
341
359
  # instructing the user agents to prompt to save.
342
- def attachment(filename = nil, disposition = 'attachment')
360
+ def attachment(filename = nil, disposition = :attachment)
343
361
  response['Content-Disposition'] = disposition.to_s
344
362
  if filename
345
363
  params = '; filename="%s"' % File.basename(filename)
@@ -357,19 +375,19 @@ module Sinatra
357
375
 
358
376
  disposition = opts[:disposition]
359
377
  filename = opts[:filename]
360
- disposition = 'attachment' if disposition.nil? and filename
361
- filename = path if filename.nil?
378
+ disposition = :attachment if disposition.nil? and filename
379
+ filename = path if filename.nil?
362
380
  attachment(filename, disposition) if disposition
363
381
 
364
382
  last_modified opts[:last_modified] if opts[:last_modified]
365
383
 
366
- file = Rack::File.new nil
367
- file.path = path
368
- result = file.serving env
384
+ file = Rack::File.new(File.dirname(settings.app_file))
385
+ result = file.serving(request, path)
386
+
369
387
  result[1].each { |k,v| headers[k] ||= v }
370
388
  headers['Content-Length'] = result[1]['Content-Length']
371
389
  opts[:status] &&= Integer(opts[:status])
372
- halt opts[:status] || result[0], result[2]
390
+ halt (opts[:status] || result[0]), result[2]
373
391
  rescue Errno::ENOENT
374
392
  not_found
375
393
  end
@@ -441,7 +459,7 @@ module Sinatra
441
459
  # Specify response freshness policy for HTTP caches (Cache-Control header).
442
460
  # Any number of non-value directives (:public, :private, :no_cache,
443
461
  # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
444
- # a Hash of value directives (:max_age, :min_stale, :s_max_age).
462
+ # a Hash of value directives (:max_age, :min_stale, :s_maxage).
445
463
  #
446
464
  # cache_control :public, :must_revalidate, :max_age => 60
447
465
  # => Cache-Control: public, must-revalidate, max-age=60
@@ -460,7 +478,7 @@ module Sinatra
460
478
  values.map! { |value| value.to_s.tr('_','-') }
461
479
  hash.each do |key, value|
462
480
  key = key.to_s.tr('_', '-')
463
- value = value.to_i if key == "max-age"
481
+ value = value.to_i if ['max-age', 's-maxage'].include? key
464
482
  values << "#{key}=#{value}"
465
483
  end
466
484
 
@@ -473,7 +491,7 @@ module Sinatra
473
491
  # "values" arguments are passed to the #cache_control helper:
474
492
  #
475
493
  # expires 500, :public, :must_revalidate
476
- # => Cache-Control: public, must-revalidate, max-age=60
494
+ # => Cache-Control: public, must-revalidate, max-age=500
477
495
  # => Expires: Mon, 08 Jun 2009 08:50:17 GMT
478
496
  #
479
497
  def expires(amount, *values)
@@ -585,6 +603,11 @@ module Sinatra
585
603
  status.between? 500, 599
586
604
  end
587
605
 
606
+ # whether or not the status is set to 400
607
+ def bad_request?
608
+ status == 400
609
+ end
610
+
588
611
  # whether or not the status is set to 404
589
612
  def not_found?
590
613
  status == 404
@@ -593,22 +616,12 @@ module Sinatra
593
616
  # Generates a Time object from the given value.
594
617
  # Used by #expires and #last_modified.
595
618
  def time_for(value)
596
- if value.respond_to? :to_time
597
- value.to_time
598
- elsif value.is_a? Time
599
- value
600
- elsif value.respond_to? :new_offset
601
- # DateTime#to_time does the same on 1.9
602
- d = value.new_offset 0
603
- t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
604
- t.getlocal
605
- elsif value.respond_to? :mday
606
- # Date#to_time does the same on 1.9
607
- Time.local(value.year, value.mon, value.mday)
608
- elsif value.is_a? Numeric
619
+ if value.is_a? Numeric
609
620
  Time.at value
610
- else
621
+ elsif value.respond_to? :to_s
611
622
  Time.parse value.to_s
623
+ else
624
+ value.to_time
612
625
  end
613
626
  rescue ArgumentError => boom
614
627
  raise boom
@@ -806,7 +819,8 @@ module Sinatra
806
819
  layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false)
807
820
  layout = @default_layout if layout.nil? or layout == true
808
821
  layout_options = options.delete(:layout_options) || {}
809
- content_type = options.delete(:content_type) || options.delete(:default_content_type)
822
+ content_type = options.delete(:default_content_type)
823
+ content_type = options.delete(:content_type) || content_type
810
824
  layout_engine = options.delete(:layout_engine) || engine
811
825
  scope = options.delete(:scope) || self
812
826
  options.delete(:layout)
@@ -863,7 +877,9 @@ module Sinatra
863
877
  end
864
878
  when Proc, String
865
879
  body = data.is_a?(String) ? Proc.new { data } : data
866
- path, line = settings.caller_locations.first
880
+ caller = settings.caller_locations.first
881
+ path = options[:path] || caller[0]
882
+ line = options[:line] || caller[1]
867
883
  template.new(path, line.to_i, options, &body)
868
884
  else
869
885
  raise ArgumentError, "Sorry, don't know how to render #{data.inspect}."
@@ -878,7 +894,7 @@ module Sinatra
878
894
  include Helpers
879
895
  include Templates
880
896
 
881
- URI_INSTANCE = URI.const_defined?(:Parser) ? URI::Parser.new : URI
897
+ URI_INSTANCE = URI::Parser.new
882
898
 
883
899
  attr_accessor :app, :env, :request, :response, :params
884
900
  attr_reader :template_cache
@@ -899,9 +915,7 @@ module Sinatra
899
915
  @env = env
900
916
  @request = Request.new(env)
901
917
  @response = Response.new
902
- @params = indifferent_params(@request.params)
903
918
  template_cache.clear if settings.reload_templates
904
- force_encoding(@params)
905
919
 
906
920
  @response['Content-Type'] = nil
907
921
  invoke { dispatch! }
@@ -969,9 +983,9 @@ module Sinatra
969
983
  # Run routes defined on the class and all superclasses.
970
984
  def route!(base = settings, pass_block = nil)
971
985
  if routes = base.routes[@request.request_method]
972
- routes.each do |pattern, keys, conditions, block|
973
- returned_pass_block = process_route(pattern, keys, conditions) do |*args|
974
- env['sinatra.route'] = block.instance_variable_get(:@route_name)
986
+ routes.each do |pattern, conditions, block|
987
+ returned_pass_block = process_route(pattern, conditions) do |*args|
988
+ env['sinatra.route'] = "#{@request.request_method} #{pattern}"
975
989
  route_eval { block[*args] }
976
990
  end
977
991
 
@@ -999,21 +1013,29 @@ module Sinatra
999
1013
  # Revert params afterwards.
1000
1014
  #
1001
1015
  # Returns pass block.
1002
- def process_route(pattern, keys, conditions, block = nil, values = [])
1016
+ def process_route(pattern, conditions, block = nil, values = [])
1003
1017
  route = @request.path_info
1004
1018
  route = '/' if route.empty? and not settings.empty_path_info?
1005
- return unless match = pattern.match(route)
1006
- values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
1019
+ return unless params = pattern.params(route)
1007
1020
 
1008
- if values.any?
1009
- original, @params = params, params.merge('splat' => [], 'captures' => values)
1010
- keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
1021
+ params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
1022
+ original, @params = @params, @params.merge(params) if params.any?
1023
+
1024
+ if pattern.is_a? Mustermann::Regular
1025
+ captures = pattern.match(route).captures
1026
+ values += captures
1027
+ @params[:captures] = captures
1028
+ else
1029
+ values += params.values.flatten
1011
1030
  end
1012
1031
 
1013
1032
  catch(:pass) do
1014
1033
  conditions.each { |c| throw :pass if c.bind(self).call == false }
1015
1034
  block ? block[self, values] : yield(self, values)
1016
1035
  end
1036
+ rescue
1037
+ @env['sinatra.error.params'] = @params
1038
+ raise
1017
1039
  ensure
1018
1040
  @params = original if original
1019
1041
  end
@@ -1065,8 +1087,9 @@ module Sinatra
1065
1087
  # Run the block with 'throw :halt' support and apply result to the response.
1066
1088
  def invoke
1067
1089
  res = catch(:halt) { yield }
1068
- res = [res] if Integer === res or String === res
1069
- if Array === res and Integer === res.first
1090
+
1091
+ res = [res] if Fixnum === res or String === res
1092
+ if Array === res and Fixnum === res.first
1070
1093
  res = res.dup
1071
1094
  status(res.shift)
1072
1095
  body(res.pop)
@@ -1079,6 +1102,9 @@ module Sinatra
1079
1102
 
1080
1103
  # Dispatch a request with error handling.
1081
1104
  def dispatch!
1105
+ @params = indifferent_params(@request.params)
1106
+ force_encoding(@params)
1107
+
1082
1108
  invoke do
1083
1109
  static! if settings.static? && (request.get? || request.head?)
1084
1110
  filter! :before
@@ -1096,6 +1122,9 @@ module Sinatra
1096
1122
 
1097
1123
  # Error handling during requests.
1098
1124
  def handle_exception!(boom)
1125
+ if error_params = @env['sinatra.error.params']
1126
+ @params = @params.merge(error_params)
1127
+ end
1099
1128
  @env['sinatra.error'] = boom
1100
1129
 
1101
1130
  if boom.respond_to? :http_status
@@ -1111,11 +1140,12 @@ module Sinatra
1111
1140
  if server_error?
1112
1141
  dump_errors! boom if settings.dump_errors?
1113
1142
  raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
1114
- end
1115
-
1116
- if not_found?
1143
+ elsif not_found?
1117
1144
  headers['X-Cascade'] = 'pass' if settings.x_cascade?
1118
1145
  body '<h1>Not Found</h1>'
1146
+ elsif bad_request?
1147
+ dump_errors! boom if settings.dump_errors?
1148
+ halt status
1119
1149
  end
1120
1150
 
1121
1151
  res = error_block!(boom.class, boom) || error_block!(status, boom)
@@ -1223,7 +1253,7 @@ module Sinatra
1223
1253
  case value
1224
1254
  when Proc
1225
1255
  getter = value
1226
- when Symbol, Integer, FalseClass, TrueClass, NilClass
1256
+ when Symbol, Fixnum, FalseClass, TrueClass, NilClass
1227
1257
  getter = value.inspect
1228
1258
  when Hash
1229
1259
  setter = proc do |val|
@@ -1252,16 +1282,16 @@ module Sinatra
1252
1282
  # class, or an HTTP status code to specify which errors should be
1253
1283
  # handled.
1254
1284
  def error(*codes, &block)
1255
- args = compile! "ERROR", //, block
1285
+ args = compile! "ERROR", /.*/, block
1256
1286
  codes = codes.map { |c| Array(c) }.flatten
1257
1287
  codes << Exception if codes.empty?
1288
+ codes << Sinatra::NotFound if codes.include?(404)
1258
1289
  codes.each { |c| (@errors[c] ||= []) << args }
1259
1290
  end
1260
1291
 
1261
1292
  # Sugar for `error(404) { ... }`
1262
1293
  def not_found(&block)
1263
1294
  error(404, &block)
1264
- error(Sinatra::NotFound, &block)
1265
1295
  end
1266
1296
 
1267
1297
  # Define a named template. The block must return the template source.
@@ -1299,7 +1329,7 @@ module Sinatra
1299
1329
  data.each_line do |line|
1300
1330
  lines += 1
1301
1331
  if line =~ /^@@\s*(.*\S)\s*$/
1302
- template = force_encoding('', encoding)
1332
+ template = force_encoding(String.new, encoding)
1303
1333
  templates[$1.to_sym] = [template, file, lines]
1304
1334
  elsif template
1305
1335
  template << line
@@ -1328,21 +1358,20 @@ module Sinatra
1328
1358
  # Define a before filter; runs before all requests within the same
1329
1359
  # context as route handlers and may access/modify the request and
1330
1360
  # response.
1331
- def before(path = nil, options = {}, &block)
1361
+ def before(path = /.*/, **options, &block)
1332
1362
  add_filter(:before, path, options, &block)
1333
1363
  end
1334
1364
 
1335
1365
  # Define an after filter; runs after all requests within the same
1336
1366
  # context as route handlers and may access/modify the request and
1337
1367
  # response.
1338
- def after(path = nil, options = {}, &block)
1368
+ def after(path = /.*/, **options, &block)
1339
1369
  add_filter(:after, path, options, &block)
1340
1370
  end
1341
1371
 
1342
1372
  # add a filter
1343
- def add_filter(type, path = nil, options = {}, &block)
1344
- path, options = //, path if path.respond_to?(:each_pair)
1345
- filters[type] << compile!(type, path || //, block, options)
1373
+ def add_filter(type, path = /.*/, **options, &block)
1374
+ filters[type] << compile!(type, path, block, options)
1346
1375
  end
1347
1376
 
1348
1377
  # Add a route condition. The route is considered non-matching when the
@@ -1422,7 +1451,7 @@ module Sinatra
1422
1451
  return unless running?
1423
1452
  # Use Thin's hard #stop! if available, otherwise just #stop.
1424
1453
  running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
1425
- $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
1454
+ $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless supress_messages?
1426
1455
  set :running_server, nil
1427
1456
  set :handler_name, nil
1428
1457
  end
@@ -1504,7 +1533,7 @@ module Sinatra
1504
1533
  # Starts the server by running the Rack Handler.
1505
1534
  def start_server(handler, server_settings, handler_name)
1506
1535
  handler.run(self, server_settings) do |server|
1507
- unless handler_name =~ /cgi/i
1536
+ unless supress_messages?
1508
1537
  $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
1509
1538
  end
1510
1539
 
@@ -1517,6 +1546,10 @@ module Sinatra
1517
1546
  end
1518
1547
  end
1519
1548
 
1549
+ def supress_messages?
1550
+ handler_name =~ /cgi/i || quiet
1551
+ end
1552
+
1520
1553
  def setup_traps
1521
1554
  if traps?
1522
1555
  at_exit { quit! }
@@ -1534,8 +1567,7 @@ module Sinatra
1534
1567
 
1535
1568
  # Dynamically defines a method on settings.
1536
1569
  def define_singleton(name, content = Proc.new)
1537
- # replace with call to singleton_class once we're 1.9 only
1538
- (class << self; self; end).class_eval do
1570
+ singleton_class.class_eval do
1539
1571
  undef_method(name) if method_defined? name
1540
1572
  String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
1541
1573
  end
@@ -1599,108 +1631,22 @@ module Sinatra
1599
1631
  method
1600
1632
  end
1601
1633
 
1602
- def compile!(verb, path, block, options = {})
1634
+ def compile!(verb, path, block, **options)
1603
1635
  options.each_pair { |option, args| send(option, *args) }
1636
+
1637
+ pattern = compile(path)
1604
1638
  method_name = "#{verb} #{path}"
1605
1639
  unbound_method = generate_method(method_name, &block)
1606
- pattern, keys = compile path
1607
1640
  conditions, @conditions = @conditions, []
1608
-
1609
1641
  wrapper = block.arity != 0 ?
1610
1642
  proc { |a,p| unbound_method.bind(a).call(*p) } :
1611
1643
  proc { |a,p| unbound_method.bind(a).call }
1612
- wrapper.instance_variable_set(:@route_name, method_name)
1613
1644
 
1614
- [ pattern, keys, conditions, wrapper ]
1645
+ [ pattern, conditions, wrapper ]
1615
1646
  end
1616
1647
 
1617
1648
  def compile(path)
1618
- if path.respond_to? :to_str
1619
- keys = []
1620
-
1621
- # Split the path into pieces in between forward slashes.
1622
- # A negative number is given as the second argument of path.split
1623
- # because with this number, the method does not ignore / at the end
1624
- # and appends an empty string at the end of the return value.
1625
- #
1626
- segments = path.split('/', -1).map! do |segment|
1627
- ignore = []
1628
-
1629
- # Special character handling.
1630
- #
1631
- pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]|:(?!\w)/) do |c|
1632
- ignore << escaped(c).join if c.match(/[\.@]/)
1633
- patt = encoded(c)
1634
- patt.gsub(/%[\da-fA-F]{2}/) do |match|
1635
- match.split(//).map! { |char| char == char.downcase ? char : "[#{char}#{char.downcase}]" }.join
1636
- end
1637
- end
1638
-
1639
- ignore = ignore.uniq.join
1640
-
1641
- # Key handling.
1642
- #
1643
- pattern.gsub(/((:\w+)|\*)/) do |match|
1644
- if match == "*"
1645
- keys << 'splat'
1646
- "(.*?)"
1647
- else
1648
- keys << $2[1..-1]
1649
- ignore_pattern = safe_ignore(ignore)
1650
-
1651
- ignore_pattern
1652
- end
1653
- end
1654
- end
1655
-
1656
- # Special case handling.
1657
- #
1658
- if last_segment = segments[-1] and last_segment.match(/\[\^\\\./)
1659
- parts = last_segment.rpartition(/\[\^\\\./)
1660
- parts[1] = '[^'
1661
- segments[-1] = parts.join
1662
- end
1663
- [/\A#{segments.join('/')}\z/, keys]
1664
- elsif path.respond_to?(:keys) && path.respond_to?(:match)
1665
- [path, path.keys]
1666
- elsif path.respond_to?(:names) && path.respond_to?(:match)
1667
- [path, path.names]
1668
- elsif path.respond_to? :match
1669
- [path, []]
1670
- else
1671
- raise TypeError, path
1672
- end
1673
- end
1674
-
1675
- def encoded(char)
1676
- enc = URI_INSTANCE.escape(char)
1677
- enc = "(?:#{escaped(char, enc).join('|')})" if enc == char
1678
- enc = "(?:#{enc}|#{encoded('+')})" if char == " "
1679
- enc
1680
- end
1681
-
1682
- def escaped(char, enc = URI_INSTANCE.escape(char))
1683
- [Regexp.escape(enc), URI_INSTANCE.escape(char, /./)]
1684
- end
1685
-
1686
- def safe_ignore(ignore)
1687
- unsafe_ignore = []
1688
- ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
1689
- unsafe_ignore << hex[1..2]
1690
- ''
1691
- end
1692
- unsafe_patterns = unsafe_ignore.map! do |unsafe|
1693
- chars = unsafe.split(//).map! do |char|
1694
- char == char.downcase ? char : char + char.downcase
1695
- end
1696
-
1697
- "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
1698
- end
1699
- if unsafe_patterns.length > 0
1700
- "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
1701
- else
1702
- "([^#{ignore}/?#]+)"
1703
- end
1649
+ Mustermann.new(path)
1704
1650
  end
1705
1651
 
1706
1652
  def setup_default_middleware(builder)
@@ -1745,10 +1691,16 @@ module Sinatra
1745
1691
  def setup_protection(builder)
1746
1692
  return unless protection?
1747
1693
  options = Hash === protection ? protection.dup : {}
1748
- protect_session = options.fetch(:session) { sessions? }
1749
- options[:except] = Array options[:except]
1750
- options[:except] += [:session_hijacking, :remote_token] unless protect_session
1694
+ options = {
1695
+ img_src: "'self' data:",
1696
+ font_src: "'self'"
1697
+ }.merge options
1698
+
1699
+ protect_session = options.fetch(:session) { sessions? }
1700
+ options[:without_session] = !protect_session
1701
+
1751
1702
  options[:reaction] ||= :drop_session
1703
+
1752
1704
  builder.use Rack::Protection, options
1753
1705
  end
1754
1706
 
@@ -1757,7 +1709,7 @@ module Sinatra
1757
1709
  options = {}
1758
1710
  options[:secret] = session_secret if session_secret?
1759
1711
  options.merge! sessions.to_hash if sessions.respond_to? :to_hash
1760
- builder.use Rack::Session::Cookie, options
1712
+ builder.use session_store, options
1761
1713
  end
1762
1714
 
1763
1715
  def detect_rack_handler
@@ -1766,8 +1718,6 @@ module Sinatra
1766
1718
  begin
1767
1719
  return Rack::Handler.get(server_name.to_s)
1768
1720
  rescue LoadError, NameError
1769
- rescue ArgumentError
1770
- Sinatra::Ext.get_handler(server_name.to_s)
1771
1721
  end
1772
1722
  end
1773
1723
  fail "Server handler (#{servers.join(',')}) not found."
@@ -1826,11 +1776,12 @@ module Sinatra
1826
1776
 
1827
1777
  reset!
1828
1778
 
1829
- set :environment, (ENV['RACK_ENV'] || :development).to_sym
1779
+ set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
1830
1780
  set :raise_errors, Proc.new { test? }
1831
1781
  set :dump_errors, Proc.new { !test? }
1832
1782
  set :show_exceptions, Proc.new { development? }
1833
1783
  set :sessions, false
1784
+ set :session_store, Rack::Session::Cookie
1834
1785
  set :logging, false
1835
1786
  set :protection, true
1836
1787
  set :method_override, false
@@ -1861,6 +1812,7 @@ module Sinatra
1861
1812
  set :server, %w[HTTP webrick]
1862
1813
  set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1863
1814
  set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
1815
+ set :quiet, false
1864
1816
 
1865
1817
  ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1866
1818
 
@@ -1936,7 +1888,7 @@ module Sinatra
1936
1888
  </style>
1937
1889
  </head>
1938
1890
  <body>
1939
- <h2>Sinatra doesn&rsquo;t know this ditty.</h2>
1891
+ <h2>Sinatra doesnt know this ditty.</h2>
1940
1892
  <img src='#{uri "/__sinatra__/404.png"}'>
1941
1893
  <div id="c">
1942
1894
  Try this: