rack 3.2.1 → 3.2.6
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +124 -14
- data/README.md +8 -0
- data/lib/rack/directory.rb +6 -3
- data/lib/rack/files.rb +1 -1
- data/lib/rack/mock_response.rb +11 -2
- data/lib/rack/multipart/parser.rb +95 -4
- data/lib/rack/query_parser.rb +3 -1
- data/lib/rack/request.rb +6 -3
- data/lib/rack/sendfile.rb +51 -21
- data/lib/rack/static.rb +7 -3
- data/lib/rack/utils.rb +107 -15
- data/lib/rack/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 37024c5110b365f1dd0b5627dd903edf42344fa9ad98a99af672e220d2370288
|
|
4
|
+
data.tar.gz: d0bd60323a75ff2828b33713963dcb5769898abc82a7ab3c2b9ac4ad36550b88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 825e73fe75136333217b5f395f25245fe295fa4749a8068711011ba5c47350043941e4a4319837015e211f6cbd29952970fdc79f5deb0364c76d995c8d9de457
|
|
7
|
+
data.tar.gz: 57b8333f46704c6aead2d24ab214ab99ddb8296f93cc62298385d20bf4bffad829fae3b5e3a658e399d1628ab355a9abd69e4e1f2d08971740547b6ff1425f5b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference [Keep A Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## [3.2.6] - 2026-04-01
|
|
6
|
+
|
|
7
|
+
### Security
|
|
8
|
+
|
|
9
|
+
- [CVE-2026-34763](https://github.com/advisories/GHSA-7mqq-6cf9-v2qp) Root directory disclosure via unescaped regex interpolation in `Rack::Directory`.
|
|
10
|
+
- [CVE-2026-34230](https://github.com/advisories/GHSA-v569-hp3g-36wr) Avoid O(n^2) algorithm in `Rack::Utils.select_best_encoding` which could lead to denial of service.
|
|
11
|
+
- [CVE-2026-32762](https://github.com/advisories/GHSA-qfgr-crr9-7r49) Forwarded header semicolon injection enables Host and Scheme spoofing.
|
|
12
|
+
- [CVE-2026-26961](https://github.com/advisories/GHSA-vgpv-f759-9wx3) Raise error for multipart requests with multiple boundary parameters.
|
|
13
|
+
- [CVE-2026-34786](https://github.com/advisories/GHSA-q4qf-9j86-f5mh) `Rack::Static` `header_rules` bypass via URL-encoded path mismatch.
|
|
14
|
+
- [CVE-2026-34831](https://github.com/advisories/GHSA-q2ww-5357-x388) `Content-Length` mismatch in `Rack::Files` error responses.
|
|
15
|
+
- [CVE-2026-34826](https://github.com/advisories/GHSA-x8cg-fq8g-mxfx) Multipart byte range processing allows denial of service via excessive overlapping ranges.
|
|
16
|
+
- [CVE-2026-34835](https://github.com/advisories/GHSA-g2pf-xv49-m2h5) `Rack::Request` accepts invalid Host characters, enabling host allowlist bypass.
|
|
17
|
+
- [CVE-2026-34830](https://github.com/advisories/GHSA-qv7j-4883-hwh7) `Rack::Sendfile` header-based `X-Accel-Mapping` regex injection enables unauthorized `X-Accel-Redirect`.
|
|
18
|
+
- [CVE-2026-34785](https://github.com/advisories/GHSA-h2jq-g4cq-5ppq) `Rack::Static` prefix matching can expose unintended files under the static root.
|
|
19
|
+
- [CVE-2026-34829](https://github.com/advisories/GHSA-8vqr-qjwx-82mw) Multipart parsing without `Content-Length` header allows unbounded chunked file uploads.
|
|
20
|
+
- [CVE-2026-34827](https://github.com/advisories/GHSA-v6x5-cg8r-vv6x) Quadratic-time multipart header parsing allows denial of service via escape-heavy quoted parameters.
|
|
21
|
+
- [CVE-2026-26962](https://github.com/advisories/GHSA-rx22-g9mx-qrhv) Improper unfolding of folded multipart headers preserves CRLF in parsed parameter values.
|
|
22
|
+
|
|
23
|
+
## [3.2.5] - 2026-02-16
|
|
24
|
+
|
|
25
|
+
### Security
|
|
26
|
+
|
|
27
|
+
- [CVE-2026-25500](https://github.com/advisories/GHSA-whrj-4476-wvmp) XSS injection via malicious filename in `Rack::Directory`.
|
|
28
|
+
- [CVE-2026-22860](https://github.com/advisories/GHSA-mxw3-3hh2-x2mh) Directory traversal via root prefix bypass in `Rack::Directory`.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Fix `Rack::MockResponse#body` when the body is a Proc. ([#2420](https://github.com/rack/rack/pull/2420), [#2423](https://github.com/rack/rack/pull/2423), [@tavianator](https://github.com/tavianator), [@ioquatix])
|
|
33
|
+
|
|
34
|
+
## [3.2.4] - 2025-11-03
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Multipart parser: limit MIME header size check to the unread buffer region to avoid false `multipart mime part header too large` errors when previously read data accumulates in the scan buffer. ([#2392](https://github.com/rack/rack/pull/2392), [@alpaca-tc](https://github.com/alpaca-tc), [@willnet](https://github.com/willnet), [@krororo](https://github.com/krororo))
|
|
39
|
+
|
|
40
|
+
## [3.2.3] - 2025-10-10
|
|
41
|
+
|
|
42
|
+
### Security
|
|
43
|
+
|
|
44
|
+
- [CVE-2025-61780](https://github.com/advisories/GHSA-r657-rxjc-j557) Improper handling of headers in `Rack::Sendfile` may allow proxy bypass.
|
|
45
|
+
- [CVE-2025-61919](https://github.com/advisories/GHSA-6xw4-3v39-52mm) Unbounded read in `Rack::Request` form parsing can lead to memory exhaustion.
|
|
46
|
+
|
|
47
|
+
## [3.2.2] - 2025-10-07
|
|
48
|
+
|
|
49
|
+
### Security
|
|
50
|
+
|
|
51
|
+
- [CVE-2025-61772](https://github.com/advisories/GHSA-wpv5-97wm-hp9c) Multipart parser buffers unbounded per-part headers, enabling DoS (memory exhaustion)
|
|
52
|
+
- [CVE-2025-61771](https://github.com/advisories/GHSA-w9pc-fmgc-vxvw) Multipart parser buffers large non‑file fields entirely in memory, enabling DoS (memory exhaustion)
|
|
53
|
+
- [CVE-2025-61770](https://github.com/advisories/GHSA-p543-xpfm-54cp) Unbounded multipart preamble buffering enables DoS (memory exhaustion)
|
|
6
54
|
|
|
7
55
|
## [3.2.1] -- 2025-09-02
|
|
8
56
|
|
|
@@ -61,6 +109,34 @@ This release continues Rack's evolution toward a cleaner, more efficient foundat
|
|
|
61
109
|
- `SERVER_NAME` and `HTTP_HOST` are now more strictly validated according to the relevant specifications. ([#2298](https://github.com/rack/rack/pull/2298), [@ioquatix])
|
|
62
110
|
- `Rack::Lint` now disallows `PATH_INFO="" SCRIPT_NAME=""`. ([#2298](https://github.com/rack/rack/issues/2307), [@jeremyevans])
|
|
63
111
|
|
|
112
|
+
## [3.1.20] - 2026-02-16
|
|
113
|
+
|
|
114
|
+
### Security
|
|
115
|
+
|
|
116
|
+
- [CVE-2026-25500](https://github.com/advisories/GHSA-whrj-4476-wvmp) XSS injection via malicious filename in `Rack::Directory`.
|
|
117
|
+
- [CVE-2026-22860](https://github.com/advisories/GHSA-mxw3-3hh2-x2mh) Directory traversal via root prefix bypass in `Rack::Directory`.
|
|
118
|
+
|
|
119
|
+
## [3.1.19] - 2025-11-03
|
|
120
|
+
|
|
121
|
+
### Fixed
|
|
122
|
+
|
|
123
|
+
- Multipart parser: limit MIME header size check to the unread buffer region to avoid false `multipart mime part header too large` errors when previously read data accumulates in the scan buffer. ([#2392](https://github.com/rack/rack/pull/2392), [@alpaca-tc](https://github.com/alpaca-tc), [@willnet](https://github.com/willnet), [@krororo](https://github.com/krororo))
|
|
124
|
+
|
|
125
|
+
## [3.1.18] - 2025-10-10
|
|
126
|
+
|
|
127
|
+
### Security
|
|
128
|
+
|
|
129
|
+
- [CVE-2025-61780](https://github.com/advisories/GHSA-r657-rxjc-j557) Improper handling of headers in `Rack::Sendfile` may allow proxy bypass.
|
|
130
|
+
- [CVE-2025-61919](https://github.com/advisories/GHSA-6xw4-3v39-52mm) Unbounded read in `Rack::Request` form parsing can lead to memory exhaustion.
|
|
131
|
+
|
|
132
|
+
## [3.1.17] - 2025-10-07
|
|
133
|
+
|
|
134
|
+
### Security
|
|
135
|
+
|
|
136
|
+
- [CVE-2025-61772](https://github.com/advisories/GHSA-wpv5-97wm-hp9c) Multipart parser buffers unbounded per-part headers, enabling DoS (memory exhaustion)
|
|
137
|
+
- [CVE-2025-61771](https://github.com/advisories/GHSA-w9pc-fmgc-vxvw) Multipart parser buffers large non‑file fields entirely in memory, enabling DoS (memory exhaustion)
|
|
138
|
+
- [CVE-2025-61770](https://github.com/advisories/GHSA-p543-xpfm-54cp) Unbounded multipart preamble buffering enables DoS (memory exhaustion)
|
|
139
|
+
|
|
64
140
|
## [3.1.16] - 2025-06-04
|
|
65
141
|
|
|
66
142
|
### Security
|
|
@@ -77,7 +153,7 @@ This release continues Rack's evolution toward a cleaner, more efficient foundat
|
|
|
77
153
|
|
|
78
154
|
### Security
|
|
79
155
|
|
|
80
|
-
- [CVE-2025-46727](https://github.com/
|
|
156
|
+
- [CVE-2025-46727](https://github.com/advisories/GHSA-gjh7-p2fx-99vx) Unbounded parameter parsing in `Rack::QueryParser` can lead to memory exhaustion.
|
|
81
157
|
|
|
82
158
|
## [3.1.13] - 2025-04-13
|
|
83
159
|
|
|
@@ -87,19 +163,19 @@ This release continues Rack's evolution toward a cleaner, more efficient foundat
|
|
|
87
163
|
|
|
88
164
|
### Security
|
|
89
165
|
|
|
90
|
-
- [CVE-2025-27610](https://github.com/
|
|
166
|
+
- [CVE-2025-27610](https://github.com/advisories/GHSA-7wqh-767x-r66v) Local file inclusion in `Rack::Static`.
|
|
91
167
|
|
|
92
168
|
## [3.1.11] - 2025-03-04
|
|
93
169
|
|
|
94
170
|
### Security
|
|
95
171
|
|
|
96
|
-
- [CVE-2025-27111](https://github.com/
|
|
172
|
+
- [CVE-2025-27111](https://github.com/advisories/GHSA-8cgq-6mh2-7j6v) Possible Log Injection in `Rack::Sendfile`.
|
|
97
173
|
|
|
98
174
|
## [3.1.10] - 2025-02-12
|
|
99
175
|
|
|
100
176
|
### Security
|
|
101
177
|
|
|
102
|
-
- [CVE-2025-25184](https://github.com/
|
|
178
|
+
- [CVE-2025-25184](https://github.com/advisories/GHSA-7g2v-jj9q-g3rg) Possible Log Injection in `Rack::CommonLogger`.
|
|
103
179
|
|
|
104
180
|
## [3.1.9] - 2025-01-31
|
|
105
181
|
|
|
@@ -132,7 +208,7 @@ This release continues Rack's evolution toward a cleaner, more efficient foundat
|
|
|
132
208
|
|
|
133
209
|
### Security
|
|
134
210
|
|
|
135
|
-
- Fix potential ReDoS attack in `Rack::Request#parse_http_accept_header`. ([GHSA-cj83-2ww7-mvq7](https://github.com/
|
|
211
|
+
- Fix potential ReDoS attack in `Rack::Request#parse_http_accept_header`. ([GHSA-cj83-2ww7-mvq7](https://github.com/advisories/GHSA-cj83-2ww7-mvq7), [@dwisiswant0](https://github.com/dwisiswant0))
|
|
136
212
|
|
|
137
213
|
## [3.1.4] - 2024-06-22
|
|
138
214
|
|
|
@@ -224,7 +300,7 @@ This release is primarily a maintenance release that removes features deprecated
|
|
|
224
300
|
|
|
225
301
|
### Security
|
|
226
302
|
|
|
227
|
-
- [CVE-2025-46727](https://github.com/
|
|
303
|
+
- [CVE-2025-46727](https://github.com/advisories/GHSA-gjh7-p2fx-99vx) Unbounded parameter parsing in `Rack::QueryParser` can lead to memory exhaustion.
|
|
228
304
|
|
|
229
305
|
## [3.0.15] - 2025-04-13
|
|
230
306
|
|
|
@@ -234,13 +310,13 @@ This release is primarily a maintenance release that removes features deprecated
|
|
|
234
310
|
|
|
235
311
|
### Security
|
|
236
312
|
|
|
237
|
-
- [CVE-2025-27610](https://github.com/
|
|
313
|
+
- [CVE-2025-27610](https://github.com/advisories/GHSA-7wqh-767x-r66v) Local file inclusion in `Rack::Static`.
|
|
238
314
|
|
|
239
315
|
## [3.0.13] - 2025-03-04
|
|
240
316
|
|
|
241
317
|
### Security
|
|
242
318
|
|
|
243
|
-
- [CVE-2025-27111](https://github.com/
|
|
319
|
+
- [CVE-2025-27111](https://github.com/advisories/GHSA-8cgq-6mh2-7j6v) Possible Log Injection in `Rack::Sendfile`.
|
|
244
320
|
|
|
245
321
|
### Fixed
|
|
246
322
|
|
|
@@ -250,7 +326,7 @@ This release is primarily a maintenance release that removes features deprecated
|
|
|
250
326
|
|
|
251
327
|
### Security
|
|
252
328
|
|
|
253
|
-
- [CVE-2025-25184](https://github.com/
|
|
329
|
+
- [CVE-2025-25184](https://github.com/advisories/GHSA-7g2v-jj9q-g3rg) Possible Log Injection in `Rack::CommonLogger`.
|
|
254
330
|
|
|
255
331
|
## [3.0.11] - 2024-05-10
|
|
256
332
|
|
|
@@ -430,6 +506,40 @@ This release introduces major improvements to Rack, including enhanced support f
|
|
|
430
506
|
- Fix multipart filename generation for filenames that contain spaces. Encode spaces as "%20" instead of "+" which will be decoded properly by the multipart parser. ([#1736](https://github.com/rack/rack/pull/1645), [@muirdm](https://github.com/muirdm))
|
|
431
507
|
- `Rack::Request#scheme` returns `ws` or `wss` when one of the `X-Forwarded-Scheme` / `X-Forwarded-Proto` headers is set to `ws` or `wss`, respectively. ([#1730](https://github.com/rack/rack/issues/1730), [@erwanst](https://github.com/erwanst))
|
|
432
508
|
|
|
509
|
+
## [2.2.22] - 2026-02-16
|
|
510
|
+
|
|
511
|
+
### Security
|
|
512
|
+
|
|
513
|
+
- [CVE-2026-25500](https://github.com/advisories/GHSA-whrj-4476-wvmp) XSS injection via malicious filename in `Rack::Directory`.
|
|
514
|
+
- [CVE-2026-22860](https://github.com/advisories/GHSA-mxw3-3hh2-x2mh) Directory traversal via root prefix bypass in `Rack::Directory`.
|
|
515
|
+
|
|
516
|
+
## [2.2.21] - 2025-11-03
|
|
517
|
+
|
|
518
|
+
### Fixed
|
|
519
|
+
|
|
520
|
+
- Multipart parser: limit MIME header size check to the unread buffer region to avoid false `multipart mime part header too large` errors when previously read data accumulates in the scan buffer. ([#2392](https://github.com/rack/rack/pull/2392), [@alpaca-tc](https://github.com/alpaca-tc), [@willnet](https://github.com/willnet), [@krororo](https://github.com/krororo))
|
|
521
|
+
|
|
522
|
+
## [2.2.20] - 2025-10-10
|
|
523
|
+
|
|
524
|
+
### Security
|
|
525
|
+
|
|
526
|
+
- [CVE-2025-61780](https://github.com/advisories/GHSA-r657-rxjc-j557) Improper handling of headers in `Rack::Sendfile` may allow proxy bypass.
|
|
527
|
+
- [CVE-2025-61919](https://github.com/advisories/GHSA-6xw4-3v39-52mm) Unbounded read in `Rack::Request` form parsing can lead to memory exhaustion.
|
|
528
|
+
|
|
529
|
+
## [2.2.19] - 2025-10-07
|
|
530
|
+
|
|
531
|
+
### Security
|
|
532
|
+
|
|
533
|
+
- [CVE-2025-61772](https://github.com/advisories/GHSA-wpv5-97wm-hp9c) Multipart parser buffers unbounded per-part headers, enabling DoS (memory exhaustion)
|
|
534
|
+
- [CVE-2025-61771](https://github.com/advisories/GHSA-w9pc-fmgc-vxvw) Multipart parser buffers large non‑file fields entirely in memory, enabling DoS (memory exhaustion)
|
|
535
|
+
- [CVE-2025-61770](https://github.com/advisories/GHSA-p543-xpfm-54cp) Unbounded multipart preamble buffering enables DoS (memory exhaustion)
|
|
536
|
+
|
|
537
|
+
## [2.2.18] - 2025-09-25
|
|
538
|
+
|
|
539
|
+
### Security
|
|
540
|
+
|
|
541
|
+
- [CVE-2025-59830](https://github.com/advisories/GHSA-625h-95r8-8xpm) Unbounded parameter parsing in `Rack::QueryParser` can lead to memory exhaustion via semicolon-separated parameters.
|
|
542
|
+
|
|
433
543
|
## [2.2.17] - 2025-06-03
|
|
434
544
|
|
|
435
545
|
- Backport `Rack::MediaType#params` now handles parameters without values. ([#2263](https://github.com/rack/rack/pull/2263), [@AllyMarthaJ](https://github.com/AllyMarthaJ))
|
|
@@ -448,25 +558,25 @@ This release introduces major improvements to Rack, including enhanced support f
|
|
|
448
558
|
|
|
449
559
|
### Security
|
|
450
560
|
|
|
451
|
-
- [CVE-2025-46727](https://github.com/
|
|
561
|
+
- [CVE-2025-46727](https://github.com/advisories/GHSA-gjh7-p2fx-99vx) Unbounded parameter parsing in `Rack::QueryParser` can lead to memory exhaustion.
|
|
452
562
|
|
|
453
563
|
## [2.2.13] - 2025-03-11
|
|
454
564
|
|
|
455
565
|
### Security
|
|
456
566
|
|
|
457
|
-
- [CVE-2025-27610](https://github.com/
|
|
567
|
+
- [CVE-2025-27610](https://github.com/advisories/GHSA-7wqh-767x-r66v) Local file inclusion in `Rack::Static`.
|
|
458
568
|
|
|
459
569
|
## [2.2.12] - 2025-03-04
|
|
460
570
|
|
|
461
571
|
### Security
|
|
462
572
|
|
|
463
|
-
- [CVE-2025-27111](https://github.com/
|
|
573
|
+
- [CVE-2025-27111](https://github.com/advisories/GHSA-8cgq-6mh2-7j6v) Possible Log Injection in `Rack::Sendfile`.
|
|
464
574
|
|
|
465
575
|
## [2.2.11] - 2025-02-12
|
|
466
576
|
|
|
467
577
|
### Security
|
|
468
578
|
|
|
469
|
-
- [CVE-2025-25184](https://github.com/
|
|
579
|
+
- [CVE-2025-25184](https://github.com/advisories/GHSA-7g2v-jj9q-g3rg) Possible Log Injection in `Rack::CommonLogger`.
|
|
470
580
|
|
|
471
581
|
## [2.2.10] - 2024-10-14
|
|
472
582
|
|
data/README.md
CHANGED
|
@@ -230,6 +230,14 @@ query string, before attempting parsing, so if the same parameter key is
|
|
|
230
230
|
used multiple times in the query, each counts as a separate parameter for
|
|
231
231
|
this check.
|
|
232
232
|
|
|
233
|
+
### `RACK_MULTIPART_BUFFERED_UPLOAD_BYTESIZE_LIMIT`
|
|
234
|
+
|
|
235
|
+
This environment variable sets the maximum amount of memory Rack will use
|
|
236
|
+
to buffer multipart parameters when parsing a request body. This considers
|
|
237
|
+
the size of the multipart mime headers and the body part for multipart
|
|
238
|
+
parameters that are buffered in memory and do not use tempfiles. This
|
|
239
|
+
defaults to 16MB if not provided.
|
|
240
|
+
|
|
233
241
|
### `param_depth_limit`
|
|
234
242
|
|
|
235
243
|
```ruby
|
data/lib/rack/directory.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Rack
|
|
|
17
17
|
# If +app+ is not specified, a Rack::Files of the same +root+ will be used.
|
|
18
18
|
|
|
19
19
|
class Directory
|
|
20
|
-
DIR_FILE = "<tr><td class='name'><a href='
|
|
20
|
+
DIR_FILE = "<tr><td class='name'><a href='./%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>\n"
|
|
21
21
|
DIR_PAGE_HEADER = <<-PAGE
|
|
22
22
|
<html><head>
|
|
23
23
|
<title>%s</title>
|
|
@@ -51,7 +51,7 @@ table { width:100%%; }
|
|
|
51
51
|
class DirectoryBody < Struct.new(:root, :path, :files)
|
|
52
52
|
# Yield strings for each part of the directory entry
|
|
53
53
|
def each
|
|
54
|
-
show_path = Utils.escape_html(path.sub(
|
|
54
|
+
show_path = Utils.escape_html(path.sub(/\A#{Regexp.escape(root)}/, ''))
|
|
55
55
|
yield(DIR_PAGE_HEADER % [ show_path, show_path ])
|
|
56
56
|
|
|
57
57
|
unless path.chomp('/') == root
|
|
@@ -82,6 +82,7 @@ table { width:100%%; }
|
|
|
82
82
|
# Set the root directory and application for serving files.
|
|
83
83
|
def initialize(root, app = nil)
|
|
84
84
|
@root = ::File.expand_path(root)
|
|
85
|
+
@root_with_separator = @root.end_with?(::File::SEPARATOR) ? @root : "#{@root}#{::File::SEPARATOR}"
|
|
85
86
|
@app = app || Files.new(@root)
|
|
86
87
|
@head = Head.new(method(:get))
|
|
87
88
|
end
|
|
@@ -118,7 +119,9 @@ table { width:100%%; }
|
|
|
118
119
|
# Rack response to use for requests with paths outside the root, or nil if path is inside the root.
|
|
119
120
|
def check_forbidden(path_info)
|
|
120
121
|
return unless path_info.include? ".."
|
|
121
|
-
|
|
122
|
+
|
|
123
|
+
expanded_path = ::File.expand_path(::File.join(@root, path_info))
|
|
124
|
+
return if expanded_path == @root || expanded_path.start_with?(@root_with_separator)
|
|
122
125
|
|
|
123
126
|
body = "Forbidden\n"
|
|
124
127
|
[403, { CONTENT_TYPE => "text/plain",
|
data/lib/rack/files.rb
CHANGED
data/lib/rack/mock_response.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'stringio'
|
|
3
4
|
require 'time'
|
|
4
5
|
|
|
5
6
|
require_relative 'response'
|
|
@@ -82,8 +83,16 @@ module Rack
|
|
|
82
83
|
# end
|
|
83
84
|
buffer = @buffered_body = String.new
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
begin
|
|
87
|
+
if @body.respond_to?(:each)
|
|
88
|
+
@body.each do |chunk|
|
|
89
|
+
buffer << chunk
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
@body.call(StringIO.new(buffer))
|
|
93
|
+
end
|
|
94
|
+
ensure
|
|
95
|
+
@body.close if @body.respond_to?(:close)
|
|
87
96
|
end
|
|
88
97
|
|
|
89
98
|
return buffer
|
|
@@ -33,7 +33,7 @@ module Rack
|
|
|
33
33
|
EOL = "\r\n"
|
|
34
34
|
FWS = /[ \t]+(?:\r\n[ \t]+)?/ # whitespace with optional folding
|
|
35
35
|
HEADER_VALUE = "(?:[^\r\n]|\r\n[ \t])*" # anything but a non-folding CRLF
|
|
36
|
-
MULTIPART = %r|\Amultipart
|
|
36
|
+
MULTIPART = %r|\Amultipart/.*?boundary(\s*)=\"?([^\";,]+)\"?|ni
|
|
37
37
|
MULTIPART_CONTENT_TYPE = /^Content-Type:#{FWS}?(#{HEADER_VALUE})/ni
|
|
38
38
|
MULTIPART_CONTENT_DISPOSITION = /^Content-Disposition:#{FWS}?(#{HEADER_VALUE})/ni
|
|
39
39
|
MULTIPART_CONTENT_ID = /^Content-ID:#{FWS}?(#{HEADER_VALUE})/ni
|
|
@@ -59,6 +59,34 @@ module Rack
|
|
|
59
59
|
Tempfile.new(["RackMultipart", extension])
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
BOUNDARY_START_LIMIT = 16 * 1024
|
|
63
|
+
private_constant :BOUNDARY_START_LIMIT
|
|
64
|
+
|
|
65
|
+
MIME_HEADER_BYTESIZE_LIMIT = 64 * 1024
|
|
66
|
+
private_constant :MIME_HEADER_BYTESIZE_LIMIT
|
|
67
|
+
|
|
68
|
+
env_int = lambda do |key, val|
|
|
69
|
+
if str_val = ENV[key]
|
|
70
|
+
begin
|
|
71
|
+
val = Integer(str_val, 10)
|
|
72
|
+
rescue ArgumentError
|
|
73
|
+
raise ArgumentError, "non-integer value provided for environment variable #{key}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
val
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
BUFFERED_UPLOAD_BYTESIZE_LIMIT = env_int.call("RACK_MULTIPART_BUFFERED_UPLOAD_BYTESIZE_LIMIT", 16 * 1024 * 1024)
|
|
81
|
+
private_constant :BUFFERED_UPLOAD_BYTESIZE_LIMIT
|
|
82
|
+
|
|
83
|
+
bytesize_limit = env_int.call("RACK_MULTIPART_PARSER_BYTESIZE_LIMIT", 10 * 1024 * 1024 * 1024)
|
|
84
|
+
PARSER_BYTESIZE_LIMIT = bytesize_limit > 0 ? bytesize_limit : nil
|
|
85
|
+
private_constant :PARSER_BYTESIZE_LIMIT
|
|
86
|
+
|
|
87
|
+
CONTENT_DISPOSITION_QUOTED_ESCAPES_LIMIT = env_int.call("RACK_MULTIPART_CONTENT_DISPOSITION_QUOTED_ESCAPES_LIMIT", 8 * 1024)
|
|
88
|
+
private_constant :CONTENT_DISPOSITION_QUOTED_ESCAPES_LIMIT
|
|
89
|
+
|
|
62
90
|
class BoundedIO # :nodoc:
|
|
63
91
|
def initialize(io, content_length)
|
|
64
92
|
@io = io
|
|
@@ -95,7 +123,15 @@ module Rack
|
|
|
95
123
|
return unless content_type
|
|
96
124
|
data = content_type.match(MULTIPART)
|
|
97
125
|
return unless data
|
|
98
|
-
|
|
126
|
+
|
|
127
|
+
unless data[1].empty?
|
|
128
|
+
raise Error, "whitespace between boundary parameter name and equal sign"
|
|
129
|
+
end
|
|
130
|
+
if data.post_match.match?(/boundary\s*=/i)
|
|
131
|
+
raise BoundaryTooLongError, "multiple boundary parameters found in multipart content type"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
data[2]
|
|
99
135
|
end
|
|
100
136
|
|
|
101
137
|
def self.parse(io, content_length, content_type, tmpfile, bufsize, qp)
|
|
@@ -104,6 +140,10 @@ module Rack
|
|
|
104
140
|
boundary = parse_boundary content_type
|
|
105
141
|
return EMPTY unless boundary
|
|
106
142
|
|
|
143
|
+
if PARSER_BYTESIZE_LIMIT && content_length && content_length > PARSER_BYTESIZE_LIMIT
|
|
144
|
+
raise Error, "multipart Content-Length #{content_length} exceeds limit of #{PARSER_BYTESIZE_LIMIT} bytes"
|
|
145
|
+
end
|
|
146
|
+
|
|
107
147
|
if boundary.length > 70
|
|
108
148
|
# RFC 1521 Section 7.2.1 imposes a 70 character maximum for the boundary.
|
|
109
149
|
# Most clients use no more than 55 characters.
|
|
@@ -218,6 +258,10 @@ module Rack
|
|
|
218
258
|
|
|
219
259
|
@state = :FAST_FORWARD
|
|
220
260
|
@mime_index = 0
|
|
261
|
+
@body_retained = nil
|
|
262
|
+
@retained_size = 0
|
|
263
|
+
@total_bytes_read = (0 if PARSER_BYTESIZE_LIMIT)
|
|
264
|
+
@content_disposition_quoted_escapes = 0
|
|
221
265
|
@collector = Collector.new tempfile
|
|
222
266
|
|
|
223
267
|
@sbuf = StringScanner.new("".dup)
|
|
@@ -229,6 +273,7 @@ module Rack
|
|
|
229
273
|
end
|
|
230
274
|
|
|
231
275
|
def parse(io)
|
|
276
|
+
@total_bytes_read &&= nil if io.is_a?(BoundedIO)
|
|
232
277
|
outbuf = String.new
|
|
233
278
|
read_data(io, outbuf)
|
|
234
279
|
|
|
@@ -267,6 +312,12 @@ module Rack
|
|
|
267
312
|
def read_data(io, outbuf)
|
|
268
313
|
content = io.read(@bufsize, outbuf)
|
|
269
314
|
handle_empty_content!(content)
|
|
315
|
+
if @total_bytes_read
|
|
316
|
+
@total_bytes_read += content.bytesize
|
|
317
|
+
if @total_bytes_read > PARSER_BYTESIZE_LIMIT
|
|
318
|
+
raise Error, "multipart upload exceeds limit of #{PARSER_BYTESIZE_LIMIT} bytes"
|
|
319
|
+
end
|
|
320
|
+
end
|
|
270
321
|
@sbuf.concat(content)
|
|
271
322
|
end
|
|
272
323
|
|
|
@@ -294,6 +345,10 @@ module Rack
|
|
|
294
345
|
|
|
295
346
|
# retry for opening boundary
|
|
296
347
|
else
|
|
348
|
+
# We raise if we don't find the multipart boundary, to avoid unbounded memory
|
|
349
|
+
# buffering. Note that the actual limit is the higher of 16KB and the buffer size (1MB by default)
|
|
350
|
+
raise Error, "multipart boundary not found within limit" if @sbuf.string.bytesize > BOUNDARY_START_LIMIT
|
|
351
|
+
|
|
297
352
|
# no boundary found, keep reading data
|
|
298
353
|
return :want_read
|
|
299
354
|
end
|
|
@@ -312,13 +367,21 @@ module Rack
|
|
|
312
367
|
|
|
313
368
|
CONTENT_DISPOSITION_MAX_PARAMS = 16
|
|
314
369
|
CONTENT_DISPOSITION_MAX_BYTES = 1536
|
|
370
|
+
OBS_UNFOLD = /\r\n([ \t])/
|
|
371
|
+
private_constant :OBS_UNFOLD
|
|
372
|
+
|
|
315
373
|
def handle_mime_head
|
|
316
374
|
if @sbuf.scan_until(@head_regex)
|
|
317
375
|
head = @sbuf[1]
|
|
318
376
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
|
377
|
+
content_type.gsub!(OBS_UNFOLD, '\1') if content_type
|
|
378
|
+
|
|
319
379
|
if (disposition = head[MULTIPART_CONTENT_DISPOSITION, 1]) &&
|
|
320
380
|
disposition.bytesize <= CONTENT_DISPOSITION_MAX_BYTES
|
|
321
381
|
|
|
382
|
+
# Implement OBS unfolding (RFC 5322 Section 2.2.3)
|
|
383
|
+
disposition.gsub!(OBS_UNFOLD, '\1')
|
|
384
|
+
|
|
322
385
|
# ignore actual content-disposition value (should always be form-data)
|
|
323
386
|
i = disposition.index(';')
|
|
324
387
|
disposition.slice!(0, i+1)
|
|
@@ -356,6 +419,11 @@ module Rack
|
|
|
356
419
|
# stop parsing parameter value if found ending quote
|
|
357
420
|
break if c == '"'
|
|
358
421
|
|
|
422
|
+
@content_disposition_quoted_escapes += 1
|
|
423
|
+
if @content_disposition_quoted_escapes > CONTENT_DISPOSITION_QUOTED_ESCAPES_LIMIT
|
|
424
|
+
raise Error, "number of quoted escapes during content disposition parsing exceeds limit"
|
|
425
|
+
end
|
|
426
|
+
|
|
359
427
|
escaped_char = disposition.slice!(0, 1)
|
|
360
428
|
if param == 'filename' && escaped_char != '"'
|
|
361
429
|
# Possible IE uploaded filename, append both escape backslash and value
|
|
@@ -410,16 +478,30 @@ module Rack
|
|
|
410
478
|
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
|
411
479
|
end
|
|
412
480
|
|
|
481
|
+
# Mime part head data is retained for both TempfilePart and BufferPart
|
|
482
|
+
# for the entireity of the parse, even though it isn't used for BufferPart.
|
|
483
|
+
update_retained_size(head.bytesize)
|
|
484
|
+
|
|
485
|
+
# If a filename is given, a TempfilePart will be used, so the body will
|
|
486
|
+
# not be buffered in memory. However, if a filename is not given, a BufferPart
|
|
487
|
+
# will be used, and the body will be buffered in memory.
|
|
488
|
+
@body_retained = !filename
|
|
489
|
+
|
|
413
490
|
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
|
414
491
|
@state = :MIME_BODY
|
|
415
492
|
else
|
|
416
|
-
|
|
493
|
+
# We raise if the mime part header is too large, to avoid unbounded memory
|
|
494
|
+
# buffering. Note that the actual limit is the higher of 64KB and the buffer size (1MB by default)
|
|
495
|
+
raise Error, "multipart mime part header too large" if @sbuf.rest.bytesize > MIME_HEADER_BYTESIZE_LIMIT
|
|
496
|
+
|
|
497
|
+
return :want_read
|
|
417
498
|
end
|
|
418
499
|
end
|
|
419
500
|
|
|
420
501
|
def handle_mime_body
|
|
421
502
|
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
|
422
503
|
body = body_with_boundary.sub(@body_regex_at_end, '') # remove the boundary from the string
|
|
504
|
+
update_retained_size(body.bytesize) if @body_retained
|
|
423
505
|
@collector.on_mime_body @mime_index, body
|
|
424
506
|
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
|
425
507
|
@state = :CONSUME_TOKEN
|
|
@@ -428,7 +510,9 @@ module Rack
|
|
|
428
510
|
# Save what we have so far
|
|
429
511
|
if @rx_max_size < @sbuf.rest_size
|
|
430
512
|
delta = @sbuf.rest_size - @rx_max_size
|
|
431
|
-
|
|
513
|
+
body = @sbuf.peek(delta)
|
|
514
|
+
update_retained_size(body.bytesize) if @body_retained
|
|
515
|
+
@collector.on_mime_body @mime_index, body
|
|
432
516
|
@sbuf.pos += delta
|
|
433
517
|
@sbuf.string = @sbuf.rest
|
|
434
518
|
end
|
|
@@ -436,6 +520,13 @@ module Rack
|
|
|
436
520
|
end
|
|
437
521
|
end
|
|
438
522
|
|
|
523
|
+
def update_retained_size(size)
|
|
524
|
+
@retained_size += size
|
|
525
|
+
if @retained_size > BUFFERED_UPLOAD_BYTESIZE_LIMIT
|
|
526
|
+
raise Error, "multipart data over retained size limit"
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
439
530
|
# Scan until the we find the start or end of the boundary.
|
|
440
531
|
# If we find it, return the appropriate symbol for the start or
|
|
441
532
|
# end of the boundary. If we don't find the start or end of the
|
data/lib/rack/query_parser.rb
CHANGED
|
@@ -57,6 +57,8 @@ module Rack
|
|
|
57
57
|
PARAMS_LIMIT = env_int.call("RACK_QUERY_PARSER_PARAMS_LIMIT", 4096)
|
|
58
58
|
private_constant :PARAMS_LIMIT
|
|
59
59
|
|
|
60
|
+
attr_reader :bytesize_limit
|
|
61
|
+
|
|
60
62
|
def initialize(params_class, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
|
|
61
63
|
@params_class = params_class
|
|
62
64
|
@param_depth_limit = param_depth_limit
|
|
@@ -221,7 +223,7 @@ module Rack
|
|
|
221
223
|
return if !qs || qs.empty?
|
|
222
224
|
|
|
223
225
|
if qs.bytesize > @bytesize_limit
|
|
224
|
-
raise QueryLimitError, "total query size
|
|
226
|
+
raise QueryLimitError, "total query size exceeds limit (#{@bytesize_limit})"
|
|
225
227
|
end
|
|
226
228
|
|
|
227
229
|
pairs = qs.split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP, @params_limit + 1)
|
data/lib/rack/request.rb
CHANGED
|
@@ -513,7 +513,10 @@ module Rack
|
|
|
513
513
|
if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList)
|
|
514
514
|
set_header RACK_REQUEST_FORM_PAIRS, pairs
|
|
515
515
|
else
|
|
516
|
-
|
|
516
|
+
# Add 2 bytes. One to check whether it is over the limit, and a second
|
|
517
|
+
# in case the slice! call below removes the last byte
|
|
518
|
+
# If read returns nil, use the empty string
|
|
519
|
+
form_vars = get_header(RACK_INPUT).read(query_parser.bytesize_limit + 2) || ''
|
|
517
520
|
|
|
518
521
|
# Fix for Safari Ajax postings that always append \0
|
|
519
522
|
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
|
@@ -720,8 +723,8 @@ module Rack
|
|
|
720
723
|
# Match IPv6 as a string of hex digits and colons in square brackets
|
|
721
724
|
\[(?<address>#{ipv6})\]
|
|
722
725
|
|
|
|
723
|
-
# Match
|
|
724
|
-
(?<address>[
|
|
726
|
+
# Match characters allowed by RFC 3986 Section 3.2.2
|
|
727
|
+
(?<address>[-a-zA-Z0-9._~%!$&'()*+,;=]*?)
|
|
725
728
|
)
|
|
726
729
|
(:(?<port>\d+))?
|
|
727
730
|
\z
|
data/lib/rack/sendfile.rb
CHANGED
|
@@ -16,21 +16,21 @@ module Rack
|
|
|
16
16
|
# delivery code.
|
|
17
17
|
#
|
|
18
18
|
# In order to take advantage of this middleware, the response body must
|
|
19
|
-
# respond to +to_path+ and the request must include an x-sendfile-type
|
|
19
|
+
# respond to +to_path+ and the request must include an `x-sendfile-type`
|
|
20
20
|
# header. Rack::Files and other components implement +to_path+ so there's
|
|
21
|
-
# rarely anything you need to do in your application. The x-sendfile-type
|
|
21
|
+
# rarely anything you need to do in your application. The `x-sendfile-type`
|
|
22
22
|
# header is typically set in your web servers configuration. The following
|
|
23
23
|
# sections attempt to document
|
|
24
24
|
#
|
|
25
25
|
# === Nginx
|
|
26
26
|
#
|
|
27
|
-
# Nginx supports the x-accel-redirect header. This is similar to x-sendfile
|
|
27
|
+
# Nginx supports the `x-accel-redirect` header. This is similar to `x-sendfile`
|
|
28
28
|
# but requires parts of the filesystem to be mapped into a private URL
|
|
29
29
|
# hierarchy.
|
|
30
30
|
#
|
|
31
31
|
# The following example shows the Nginx configuration required to create
|
|
32
|
-
# a private "/files/" area, enable x-accel-redirect
|
|
33
|
-
# x-
|
|
32
|
+
# a private "/files/" area, enable `x-accel-redirect`, and pass the special
|
|
33
|
+
# `x-accel-mapping` header to the backend:
|
|
34
34
|
#
|
|
35
35
|
# location ~ /files/(.*) {
|
|
36
36
|
# internal;
|
|
@@ -44,24 +44,29 @@ module Rack
|
|
|
44
44
|
# proxy_set_header X-Real-IP $remote_addr;
|
|
45
45
|
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
46
46
|
#
|
|
47
|
-
# proxy_set_header x-sendfile-type x-accel-redirect;
|
|
48
47
|
# proxy_set_header x-accel-mapping /var/www/=/files/;
|
|
49
48
|
#
|
|
50
49
|
# proxy_pass http://127.0.0.1:8080/;
|
|
51
50
|
# }
|
|
52
51
|
#
|
|
53
|
-
#
|
|
54
|
-
# The x-accel-mapping header should specify the location on the file system,
|
|
52
|
+
# The `x-accel-mapping` header should specify the location on the file system,
|
|
55
53
|
# followed by an equals sign (=), followed name of the private URL pattern
|
|
56
|
-
# that it maps to. The middleware performs a
|
|
54
|
+
# that it maps to. The middleware performs a case-insensitive substitution on the
|
|
57
55
|
# resulting path.
|
|
58
56
|
#
|
|
57
|
+
# To enable `x-accel-redirect`, you must configure the middleware explicitly:
|
|
58
|
+
#
|
|
59
|
+
# use Rack::Sendfile, "x-accel-redirect"
|
|
60
|
+
#
|
|
61
|
+
# For security reasons, the `x-sendfile-type` header from requests is ignored.
|
|
62
|
+
# The sendfile variation must be set via the middleware constructor.
|
|
63
|
+
#
|
|
59
64
|
# See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
|
|
60
65
|
#
|
|
61
66
|
# === lighttpd
|
|
62
67
|
#
|
|
63
|
-
# Lighttpd has supported some variation of the x-sendfile header for some
|
|
64
|
-
# time, although only recent version support x-sendfile in a reverse proxy
|
|
68
|
+
# Lighttpd has supported some variation of the `x-sendfile` header for some
|
|
69
|
+
# time, although only recent version support `x-sendfile` in a reverse proxy
|
|
65
70
|
# configuration.
|
|
66
71
|
#
|
|
67
72
|
# $HTTP["host"] == "example.com" {
|
|
@@ -83,7 +88,7 @@ module Rack
|
|
|
83
88
|
#
|
|
84
89
|
# === Apache
|
|
85
90
|
#
|
|
86
|
-
# x-sendfile is supported under Apache 2.x using a separate module:
|
|
91
|
+
# `x-sendfile` is supported under Apache 2.x using a separate module:
|
|
87
92
|
#
|
|
88
93
|
# https://tn123.org/mod_xsendfile/
|
|
89
94
|
#
|
|
@@ -97,16 +102,28 @@ module Rack
|
|
|
97
102
|
# === Mapping parameter
|
|
98
103
|
#
|
|
99
104
|
# The third parameter allows for an overriding extension of the
|
|
100
|
-
# x-accel-mapping header. Mappings should be provided in tuples of internal to
|
|
105
|
+
# `x-accel-mapping` header. Mappings should be provided in tuples of internal to
|
|
101
106
|
# external. The internal values may contain regular expression syntax, they
|
|
102
107
|
# will be matched with case indifference.
|
|
108
|
+
#
|
|
109
|
+
# When `x-accel-redirect` is explicitly enabled via the variation parameter,
|
|
110
|
+
# and no application-level mappings are provided, the middleware will read
|
|
111
|
+
# the `x-accel-mapping` header from the proxy. This allows nginx to control
|
|
112
|
+
# the path mapping without requiring application-level configuration.
|
|
113
|
+
#
|
|
114
|
+
# === Security
|
|
115
|
+
#
|
|
116
|
+
# For security reasons, the `x-sendfile-type` header from HTTP requests is
|
|
117
|
+
# ignored. The sendfile variation must be explicitly configured via the
|
|
118
|
+
# middleware constructor to prevent information disclosure vulnerabilities
|
|
119
|
+
# where attackers could bypass proxy restrictions.
|
|
103
120
|
|
|
104
121
|
class Sendfile
|
|
105
122
|
def initialize(app, variation = nil, mappings = [])
|
|
106
123
|
@app = app
|
|
107
124
|
@variation = variation
|
|
108
125
|
@mappings = mappings.map do |internal, external|
|
|
109
|
-
[
|
|
126
|
+
[/\A#{internal}/i, external]
|
|
110
127
|
end
|
|
111
128
|
end
|
|
112
129
|
|
|
@@ -145,22 +162,35 @@ module Rack
|
|
|
145
162
|
end
|
|
146
163
|
|
|
147
164
|
private
|
|
165
|
+
|
|
148
166
|
def variation(env)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
167
|
+
# Note: HTTP_X_SENDFILE_TYPE is intentionally NOT read for security reasons.
|
|
168
|
+
# Attackers could use this header to enable x-accel-redirect and bypass proxy restrictions.
|
|
169
|
+
@variation || env['sendfile.type']
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def x_accel_mapping(env)
|
|
173
|
+
# Only allow header when:
|
|
174
|
+
# 1. `x-accel-redirect` is explicitly enabled via constructor.
|
|
175
|
+
# 2. No application-level mappings are configured.
|
|
176
|
+
return nil unless @variation =~ /x-accel-redirect/i
|
|
177
|
+
return nil if @mappings.any?
|
|
178
|
+
|
|
179
|
+
env['HTTP_X_ACCEL_MAPPING']
|
|
152
180
|
end
|
|
153
181
|
|
|
154
182
|
def map_accel_path(env, path)
|
|
155
183
|
if mapping = @mappings.find { |internal, _| internal =~ path }
|
|
156
|
-
path.sub(*mapping)
|
|
157
|
-
elsif mapping = env
|
|
184
|
+
return path.sub(*mapping)
|
|
185
|
+
elsif mapping = x_accel_mapping(env)
|
|
186
|
+
# Safe to use header: explicit config + no app mappings:
|
|
158
187
|
mapping.split(',').map(&:strip).each do |m|
|
|
159
188
|
internal, external = m.split('=', 2).map(&:strip)
|
|
160
|
-
new_path = path.sub(
|
|
189
|
+
new_path = path.sub(/\A#{Regexp.escape(internal)}/i, external)
|
|
161
190
|
return new_path unless path == new_path
|
|
162
191
|
end
|
|
163
|
-
|
|
192
|
+
|
|
193
|
+
return path
|
|
164
194
|
end
|
|
165
195
|
end
|
|
166
196
|
end
|
data/lib/rack/static.rb
CHANGED
|
@@ -93,6 +93,9 @@ module Rack
|
|
|
93
93
|
def initialize(app, options = {})
|
|
94
94
|
@app = app
|
|
95
95
|
@urls = options[:urls] || ["/favicon.ico"]
|
|
96
|
+
if @urls.kind_of?(Array)
|
|
97
|
+
@urls = @urls.map { |url| [url, url.end_with?('/') ? url : "#{url}/".freeze].freeze }.freeze
|
|
98
|
+
end
|
|
96
99
|
@index = options[:index]
|
|
97
100
|
@gzip = options[:gzip]
|
|
98
101
|
@cascade = options[:cascade]
|
|
@@ -115,7 +118,7 @@ module Rack
|
|
|
115
118
|
end
|
|
116
119
|
|
|
117
120
|
def route_file(path)
|
|
118
|
-
@urls.kind_of?(Array) && @urls.any? { |url| path.
|
|
121
|
+
@urls.kind_of?(Array) && @urls.any? { |url, url_slash| path == url || path.start_with?(url_slash) }
|
|
119
122
|
end
|
|
120
123
|
|
|
121
124
|
def can_serve(path)
|
|
@@ -165,6 +168,8 @@ module Rack
|
|
|
165
168
|
|
|
166
169
|
# Convert HTTP header rules to HTTP headers
|
|
167
170
|
def applicable_rules(path)
|
|
171
|
+
path = ::Rack::Utils.unescape_path(path)
|
|
172
|
+
|
|
168
173
|
@header_rules.find_all do |rule, new_headers|
|
|
169
174
|
case rule
|
|
170
175
|
when :all
|
|
@@ -172,10 +177,9 @@ module Rack
|
|
|
172
177
|
when :fonts
|
|
173
178
|
/\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
|
|
174
179
|
when String
|
|
175
|
-
path = ::Rack::Utils.unescape(path)
|
|
176
180
|
path.start_with?(rule) || path.start_with?('/' + rule)
|
|
177
181
|
when Array
|
|
178
|
-
|
|
182
|
+
/\.#{Regexp.union(rule)}\z/.match?(path)
|
|
179
183
|
when Regexp
|
|
180
184
|
rule.match?(path)
|
|
181
185
|
else
|
data/lib/rack/utils.rb
CHANGED
|
@@ -146,17 +146,77 @@ module Rack
|
|
|
146
146
|
end
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
+
ALLOWED_FORWARED_PARAMS = %w[by for host proto].to_h { |name| [name, name.to_sym] }.freeze
|
|
150
|
+
private_constant :ALLOWED_FORWARED_PARAMS
|
|
151
|
+
|
|
149
152
|
def forwarded_values(forwarded_header)
|
|
150
|
-
return
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
return unless forwarded_header
|
|
154
|
+
header = forwarded_header.to_s.tr("\n", ";")
|
|
155
|
+
header.sub!(/\A[\s;,]+/, '')
|
|
156
|
+
num_params = num_escapes = 0
|
|
157
|
+
max_params = max_escapes = 1024
|
|
158
|
+
params = {}
|
|
159
|
+
|
|
160
|
+
# Parse parameter list
|
|
161
|
+
while i = header.index('=')
|
|
162
|
+
# Only parse up to max parameters, to avoid potential denial of service
|
|
163
|
+
num_params += 1
|
|
164
|
+
return if num_params > max_params
|
|
165
|
+
|
|
166
|
+
# Found end of parameter name, ensure forward progress in loop
|
|
167
|
+
param = header.slice!(0, i+1)
|
|
168
|
+
|
|
169
|
+
# Remove ending equals and preceding whitespace from parameter name
|
|
170
|
+
param.chomp!('=')
|
|
171
|
+
param.strip!
|
|
172
|
+
param.downcase!
|
|
173
|
+
return unless param = ALLOWED_FORWARED_PARAMS[param]
|
|
174
|
+
|
|
175
|
+
if header[0] == '"'
|
|
176
|
+
# Parameter value is quoted, parse it, handling backslash escapes
|
|
177
|
+
header.slice!(0, 1)
|
|
178
|
+
value = String.new
|
|
179
|
+
|
|
180
|
+
while i = header.index(/(["\\])/)
|
|
181
|
+
c = $1
|
|
182
|
+
|
|
183
|
+
# Append all content until ending quote or escape
|
|
184
|
+
value << header.slice!(0, i)
|
|
185
|
+
|
|
186
|
+
# Remove either backslash or ending quote,
|
|
187
|
+
# ensures forward progress in loop
|
|
188
|
+
header.slice!(0, 1)
|
|
189
|
+
|
|
190
|
+
# stop parsing parameter value if found ending quote
|
|
191
|
+
break if c == '"'
|
|
192
|
+
|
|
193
|
+
# Only allow up to max escapes, to avoid potential denial of service
|
|
194
|
+
num_escapes += 1
|
|
195
|
+
return if num_escapes > max_escapes
|
|
196
|
+
escaped_char = header.slice!(0, 1)
|
|
197
|
+
value << escaped_char
|
|
198
|
+
end
|
|
199
|
+
else
|
|
200
|
+
if i = header.index(/[;,]/)
|
|
201
|
+
# Parameter value unquoted (which may be invalid), value ends at comma or semicolon
|
|
202
|
+
value = header.slice!(0, i)
|
|
203
|
+
value.sub!(/[\s;,]+\z/, '')
|
|
204
|
+
else
|
|
205
|
+
# If no ending semicolon, assume remainder of line is value and stop parsing
|
|
206
|
+
header.strip!
|
|
207
|
+
value = header
|
|
208
|
+
header = ''
|
|
209
|
+
end
|
|
210
|
+
value.lstrip!
|
|
158
211
|
end
|
|
212
|
+
|
|
213
|
+
(params[param] ||= []) << value
|
|
214
|
+
|
|
215
|
+
# skip trailing semicolons/commas/whitespace, to proceed to next parameter
|
|
216
|
+
header.sub!(/\A[\s;,]+/, '') unless header.empty?
|
|
159
217
|
end
|
|
218
|
+
|
|
219
|
+
params
|
|
160
220
|
end
|
|
161
221
|
module_function :forwarded_values
|
|
162
222
|
|
|
@@ -193,17 +253,41 @@ module Rack
|
|
|
193
253
|
# :nocov:
|
|
194
254
|
end
|
|
195
255
|
|
|
256
|
+
# Given an array of available encoding strings, and an array of
|
|
257
|
+
# acceptable encodings for a request, where each element of the
|
|
258
|
+
# acceptable encodings array is an array where the first element
|
|
259
|
+
# is an encoding name and the second element is the numeric
|
|
260
|
+
# priority for the encoding, return the available encoding with
|
|
261
|
+
# the highest priority.
|
|
262
|
+
#
|
|
263
|
+
# The accept_encoding argument is typically generated by calling
|
|
264
|
+
# Request#accept_encoding.
|
|
265
|
+
#
|
|
266
|
+
# Example:
|
|
267
|
+
#
|
|
268
|
+
# select_best_encoding(%w(compress gzip identity),
|
|
269
|
+
# [["compress", 0.5], ["gzip", 1.0]])
|
|
270
|
+
# # => "gzip"
|
|
271
|
+
#
|
|
272
|
+
# To reduce denial of service potential, only the first 16
|
|
273
|
+
# acceptable encodings are considered.
|
|
196
274
|
def select_best_encoding(available_encodings, accept_encoding)
|
|
197
275
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
198
276
|
|
|
277
|
+
# Only process the first 16 encodings
|
|
278
|
+
accept_encoding = accept_encoding[0...16]
|
|
199
279
|
expanded_accept_encoding = []
|
|
280
|
+
wildcard_seen = false
|
|
200
281
|
|
|
201
282
|
accept_encoding.each do |m, q|
|
|
202
283
|
preference = available_encodings.index(m) || available_encodings.size
|
|
203
284
|
|
|
204
285
|
if m == "*"
|
|
205
|
-
|
|
206
|
-
|
|
286
|
+
unless wildcard_seen
|
|
287
|
+
(available_encodings - accept_encoding.map(&:first)).each do |m2|
|
|
288
|
+
expanded_accept_encoding << [m2, q, preference]
|
|
289
|
+
end
|
|
290
|
+
wildcard_seen = true
|
|
207
291
|
end
|
|
208
292
|
else
|
|
209
293
|
expanded_accept_encoding << [m, q, preference]
|
|
@@ -211,7 +295,13 @@ module Rack
|
|
|
211
295
|
end
|
|
212
296
|
|
|
213
297
|
encoding_candidates = expanded_accept_encoding
|
|
214
|
-
.
|
|
298
|
+
.sort do |(_, q1, p1), (_, q2, p2)|
|
|
299
|
+
if r = (q1 <=> q2).nonzero?
|
|
300
|
+
-r
|
|
301
|
+
else
|
|
302
|
+
(p1 <=> p2).nonzero? || 0
|
|
303
|
+
end
|
|
304
|
+
end
|
|
215
305
|
.map!(&:first)
|
|
216
306
|
|
|
217
307
|
unless encoding_candidates.include?("identity")
|
|
@@ -399,17 +489,19 @@ module Rack
|
|
|
399
489
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
|
400
490
|
# Returns nil if the header is missing or syntactically invalid.
|
|
401
491
|
# Returns an empty array if none of the ranges are satisfiable.
|
|
402
|
-
def byte_ranges(env, size)
|
|
403
|
-
get_byte_ranges env['HTTP_RANGE'], size
|
|
492
|
+
def byte_ranges(env, size, max_ranges: 100)
|
|
493
|
+
get_byte_ranges env['HTTP_RANGE'], size, max_ranges: max_ranges
|
|
404
494
|
end
|
|
405
495
|
|
|
406
|
-
def get_byte_ranges(http_range, size)
|
|
496
|
+
def get_byte_ranges(http_range, size, max_ranges: 100)
|
|
407
497
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
|
408
498
|
# Ignore Range when file size is 0 to avoid a 416 error.
|
|
409
499
|
return nil if size.zero?
|
|
410
500
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
|
501
|
+
byte_range = $1
|
|
502
|
+
return nil if byte_range.count(',') >= max_ranges
|
|
411
503
|
ranges = []
|
|
412
|
-
|
|
504
|
+
byte_range.split(/,[ \t]*/).each do |range_spec|
|
|
413
505
|
return nil unless range_spec.include?('-')
|
|
414
506
|
range = range_spec.split('-')
|
|
415
507
|
r0, r1 = range[0], range[1]
|
data/lib/rack/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.2.
|
|
4
|
+
version: 3.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leah Neukirchen
|
|
@@ -156,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
156
156
|
- !ruby/object:Gem::Version
|
|
157
157
|
version: '0'
|
|
158
158
|
requirements: []
|
|
159
|
-
rubygems_version:
|
|
159
|
+
rubygems_version: 4.0.6
|
|
160
160
|
specification_version: 4
|
|
161
161
|
summary: A modular Ruby webserver interface.
|
|
162
162
|
test_files: []
|