Warning: file_get_contents(https://raw.githubusercontent.com/Den1xxx/Filemanager/master/languages/ru.json): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 88

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 215

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 216

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 217

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 218

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 219

Warning: Cannot modify header information - headers already sent by (output started at /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php:88) in /home/afelisqd/cppseducation.sc.tz/admin/images/photos/17587263121019776732_admin-dbb.php on line 220
PK!t2 CHANGELOG.mdnu[# Changelog 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/). ## [3.0.7] - 2023-03-16 - Make query parameters without `=` have `nil` values. ([#2059](https://github.com/rack/rack/pull/2059), [@jeremyevans]) ## [3.0.6.1] - 2023-03-13 - [CVE-2023-27539] Avoid ReDoS in header parsing ## [3.0.6] - 2023-03-13 - Add `QueryParser#missing_value` for handling missing values + tests. ([#2052](https://github.com/rack/rack/pull/2052), [@ioquatix]) ## [3.0.5] - 2023-03-13 - Split form/query parsing into two steps. ([#2038](https://github.com/rack/rack/pull/2038), [@matthewd](https://github.com/matthewd)) ## [3.0.4.1] - 2023-03-02 - [CVE-2023-27530] Introduce multipart_total_part_limit to limit total parts ## [3.0.4.1] - 2023-01-17 - [CVE-2022-44571] Fix ReDoS vulnerability in multipart parser - [CVE-2022-44570] Fix ReDoS in Rack::Utils.get_byte_ranges - [CVE-2022-44572] Forbid control characters in attributes (also ReDoS) ## [3.0.4] - 2023-01-17 - `Rack::Request#POST` should consistently raise errors. Cache errors that occur when invoking `Rack::Request#POST` so they can be raised again later. ([#2010](https://github.com/rack/rack/pull/2010), [@ioquatix]) - Fix `Rack::Lint` error message for `HTTP_CONTENT_TYPE` and `HTTP_CONTENT_LENGTH`. ([#2007](https://github.com/rack/rack/pull/2007), [@byroot](https://github.com/byroot)) - Extend `Rack::MethodOverride` to handle `QueryParser::ParamsTooDeepError` error. ([#2006](https://github.com/rack/rack/pull/2006), [@byroot](https://github.com/byroot)) ## [3.0.3] - 2022-12-27 ### Fixed - `Rack::URLMap` uses non-deprecated form of `Regexp.new`. ([#1998](https://github.com/rack/rack/pull/1998), [@weizheheng](https://github.com/weizheheng)) ## [3.0.2] - 2022-12-05 ### Fixed - `Utils.build_nested_query` URL-encodes nested field names including the square brackets. - Allow `Rack::Response` to pass through streaming bodies. ([#1993](https://github.com/rack/rack/pull/1993), [@ioquatix]) ## [3.0.1] - 2022-11-18 ### Fixed - `MethodOverride` does not look for an override if a request does not include form/parseable data. - `Rack::Lint::Wrapper` correctly handles `respond_to?` with `to_ary`, `each`, `call` and `to_path`, forwarding to the body. ([#1981](https://github.com/rack/rack/pull/1981), [@ioquatix]) ## [3.0.0] - 2022-09-06 - No changes ## [3.0.0.rc1] - 2022-09-04 ### SPEC Changes - Stream argument must implement `<<` https://github.com/rack/rack/pull/1959 - `close` may be called on `rack.input` https://github.com/rack/rack/pull/1956 - `rack.response_finished` may be used for executing code after the response has been finished https://github.com/rack/rack/pull/1952 ## [3.0.0.beta1] - 2022-08-08 ### Security - Do not use semicolon as GET parameter separator. ([#1733](https://github.com/rack/rack/pull/1733), [@jeremyevans]) ### SPEC Changes - Response array must now be non-frozen. - Response `status` must now be an integer greater than or equal to 100. - Response `headers` must now be an unfrozen hash. - Response header keys can no longer include uppercase characters. - Response header values can be an `Array` to handle multiple values (and no longer supports `\n` encoded headers). - Response body can now respond to `#call` (streaming body) instead of `#each` (enumerable body), for the equivalent of response hijacking in previous versions. - Middleware must no longer call `#each` on the body, but they can call `#to_ary` on the body if it responds to `#to_ary`. - `rack.input` is no longer required to be rewindable. - `rack.multithread`/`rack.multiprocess`/`rack.run_once`/`rack.version` are no longer required environment keys. - `SERVER_PROTOCOL` is now a required environment key, matching the HTTP protocol used in the request. - `rack.hijack?` (partial hijack) and `rack.hijack` (full hijack) are now independently optional. - `rack.hijack_io` has been removed completely. - `rack.response_finished` is an optional environment key which contains an array of callable objects that must accept `#call(env, status, headers, error)` and are invoked after the response is finished (either successfully or unsuccessfully). - It is okay to call `#close` on `rack.input` to indicate that you no longer need or care about the input. - The stream argument supplied to the streaming body and hijack must support `#<<` for writing output. ### Removed - Remove `rack.multithread`/`rack.multiprocess`/`rack.run_once`. These variables generally come too late to be useful. ([#1720](https://github.com/rack/rack/pull/1720), [@ioquatix], [@jeremyevans])) - Remove deprecated Rack::Request::SCHEME_WHITELIST. ([@jeremyevans]) - Remove internal cookie deletion using pattern matching, there are very few practical cases where it would be useful and browsers handle it correctly without us doing anything special. ([#1844](https://github.com/rack/rack/pull/1844), [@ioquatix]) - Remove `rack.version` as it comes too late to be useful. ([#1938](https://github.com/rack/rack/pull/1938), [@ioquatix]) - Extract `rackup` command, `Rack::Server`, `Rack::Handler` and related code into a separate gem. ([#1937](https://github.com/rack/rack/pull/1937), [@ioquatix]) ### Added - `Rack::Headers` added to support lower-case header keys. ([@jeremyevans]) - `Rack::Utils#set_cookie_header` now supports `escape_key: false` to avoid key escaping. ([@jeremyevans]) - `Rack::RewindableInput` supports size. ([@ahorek](https://github.com/ahorek)) - `Rack::RewindableInput::Middleware` added for making `rack.input` rewindable. ([@jeremyevans]) - The RFC 7239 Forwarded header is now supported and considered by default when looking for information on forwarding, falling back to the X-Forwarded-* headers. `Rack::Request.forwarded_priority` accessor has been added for configuring the priority of which header to check. ([#1423](https://github.com/rack/rack/issues/1423), [@jeremyevans]) - Allow response headers to contain array of values. ([#1598](https://github.com/rack/rack/issues/1598), [@ioquatix]) - Support callable body for explicit streaming support and clarify streaming response body behaviour. ([#1745](https://github.com/rack/rack/pull/1745), [@ioquatix], [#1748](https://github.com/rack/rack/pull/1748), [@wjordan]) - Allow `Rack::Builder#run` to take a block instead of an argument. ([#1942](https://github.com/rack/rack/pull/1942), [@ioquatix]) - Add `rack.response_finished` to `Rack::Lint`. ([#1802](https://github.com/rack/rack/pull/1802), [@BlakeWilliams], [#1952](https://github.com/rack/rack/pull/1952), [@ioquatix]) - The stream argument must implement `#<<`. ([#1959](https://github.com/rack/rack/pull/1959), [@ioquatix]) ### Changed - BREAKING CHANGE: Require `status` to be an Integer. ([#1662](https://github.com/rack/rack/pull/1662), [@olleolleolle](https://github.com/olleolleolle)) - BREAKING CHANGE: Query parsing now treats parameters without `=` as having the empty string value instead of nil value, to conform to the URL spec. ([#1696](https://github.com/rack/rack/issues/1696), [@jeremyevans]) - Relax validations around `Rack::Request#host` and `Rack::Request#hostname`. ([#1606](https://github.com/rack/rack/issues/1606), [@pvande](https://github.com/pvande)) - Removed antiquated handlers: FCGI, LSWS, SCGI, Thin. ([#1658](https://github.com/rack/rack/pull/1658), [@ioquatix]) - Removed options from `Rack::Builder.parse_file` and `Rack::Builder.load_file`. ([#1663](https://github.com/rack/rack/pull/1663), [@ioquatix]) - `Rack::HTTP_VERSION` has been removed and the `HTTP_VERSION` env setting is no longer set in the CGI and Webrick handlers. ([#970](https://github.com/rack/rack/issues/970), [@jeremyevans]) - `Rack::Request#[]` and `#[]=` now warn even in non-verbose mode. ([#1277](https://github.com/rack/rack/issues/1277), [@jeremyevans]) - Decrease default allowed parameter recursion level from 100 to 32. ([#1640](https://github.com/rack/rack/issues/1640), [@jeremyevans]) - Attempting to parse a multipart response with an empty body now raises Rack::Multipart::EmptyContentError. ([#1603](https://github.com/rack/rack/issues/1603), [@jeremyevans]) - `Rack::Utils.secure_compare` uses OpenSSL's faster implementation if available. ([#1711](https://github.com/rack/rack/pull/1711), [@bdewater](https://github.com/bdewater)) - `Rack::Request#POST` now caches an empty hash if input content type is not parseable. ([#749](https://github.com/rack/rack/pull/749), [@jeremyevans]) - BREAKING CHANGE: Updated `trusted_proxy?` to match full 127.0.0.0/8 network. ([#1781](https://github.com/rack/rack/pull/1781), [@snbloch](https://github.com/snbloch)) - Explicitly deprecate `Rack::File` which was an alias for `Rack::Files`. ([#1811](https://github.com/rack/rack/pull/1720), [@ioquatix]). - Moved `Rack::Session` into [separate gem](https://github.com/rack/rack-session). ([#1805](https://github.com/rack/rack/pull/1805), [@ioquatix]) - `rackup -D` option to daemonizes no longer changes the working directory to the root. ([#1813](https://github.com/rack/rack/pull/1813), [@jeremyevans]) - The `x-forwarded-proto` header is now considered before the `x-forwarded-scheme` header for determining the forwarded protocol. `Rack::Request.x_forwarded_proto_priority` accessor has been added for configuring the priority of which header to check. ([#1809](https://github.com/rack/rack/issues/1809), [@jeremyevans]) - `Rack::Request.forwarded_authority` (and methods that call it, such as `host`) now returns the last authority in the forwarded header, instead of the first, as earlier forwarded authorities can be forged by clients. This restores the Rack 2.1 behavior. ([#1829](https://github.com/rack/rack/issues/1809), [@jeremyevans]) - Use lower case cookie attributes when creating cookies, and fold cookie attributes to lower case when reading cookies (specifically impacting `secure` and `httponly` attributes). ([#1849](https://github.com/rack/rack/pull/1849), [@ioquatix]) - The response array must now be mutable (non-frozen) so middleware can modify it without allocating a new Array,therefore reducing object allocations. ([#1887](https://github.com/rack/rack/pull/1887), [#1927](https://github.com/rack/rack/pull/1927), [@amatsuda], [@ioquatix]) - `rack.hijack?` (partial hijack) and `rack.hijack` (full hijack) are now independently optional. `rack.hijack_io` is no longer required/specified. ([#1939](https://github.com/rack/rack/pull/1939), [@ioquatix]) - Allow calling close on `rack.input`. ([#1956](https://github.com/rack/rack/pull/1956), [@ioquatix]) ### Fixed - Make Rack::MockResponse handle non-hash headers. ([#1629](https://github.com/rack/rack/issues/1629), [@jeremyevans]) - TempfileReaper now deletes temp files if application raises an exception. ([#1679](https://github.com/rack/rack/issues/1679), [@jeremyevans]) - Handle cookies with values that end in '=' ([#1645](https://github.com/rack/rack/pull/1645), [@lukaso](https://github.com/lukaso)) - Make `Rack::NullLogger` respond to `#fatal!` [@jeremyevans]) - 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)) - `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)) ## [2.2.4] - 2022-06-30 - Better support for lower case headers in `Rack::ETag` middleware. ([#1919](https://github.com/rack/rack/pull/1919), [@ioquatix](https://github.com/ioquatix)) - Use custom exception on params too deep error. ([#1838](https://github.com/rack/rack/pull/1838), [@simi](https://github.com/simi)) ## [2.2.3.1] - 2022-05-27 - [CVE-2022-30123] Fix shell escaping issue in Common Logger - [CVE-2022-30122] Restrict parsing of broken MIME attachments ## [2.2.3] - 2020-06-15 ### Security - [[CVE-2020-8184](https://nvd.nist.gov/vuln/detail/CVE-2020-8184)] Do not allow percent-encoded cookie name to override existing cookie names. BREAKING CHANGE: Accessing cookie names that require URL encoding with decoded name no longer works. ([@fletchto99](https://github.com/fletchto99)) ## [2.2.2] - 2020-02-11 ### Fixed - Fix incorrect `Rack::Request#host` value. ([#1591](https://github.com/rack/rack/pull/1591), [@ioquatix]) - Revert `Rack::Handler::Thin` implementation. ([#1583](https://github.com/rack/rack/pull/1583), [@jeremyevans]) - Double assignment is still needed to prevent an "unused variable" warning. ([#1589](https://github.com/rack/rack/pull/1589), [@kamipo](https://github.com/kamipo)) - Fix to handle same_site option for session pool. ([#1587](https://github.com/rack/rack/pull/1587), [@kamipo](https://github.com/kamipo)) ## [2.2.1] - 2020-02-09 ### Fixed - Rework `Rack::Request#ip` to handle empty `forwarded_for`. ([#1577](https://github.com/rack/rack/pull/1577), [@ioquatix]) ## [2.2.0] - 2020-02-08 ### SPEC Changes - `rack.session` request environment entry must respond to `to_hash` and return unfrozen Hash. ([@jeremyevans]) - Request environment cannot be frozen. ([@jeremyevans]) - CGI values in the request environment with non-ASCII characters must use ASCII-8BIT encoding. ([@jeremyevans]) - Improve SPEC/lint relating to SERVER_NAME, SERVER_PORT and HTTP_HOST. ([#1561](https://github.com/rack/rack/pull/1561), [@ioquatix]) ### Added - `rackup` supports multiple `-r` options and will require all arguments. ([@jeremyevans]) - `Server` supports an array of paths to require for the `:require` option. ([@khotta](https://github.com/khotta)) - `Files` supports multipart range requests. ([@fatkodima](https://github.com/fatkodima)) - `Multipart::UploadedFile` supports an IO-like object instead of using the filesystem, using `:filename` and `:io` options. ([@jeremyevans]) - `Multipart::UploadedFile` supports keyword arguments `:path`, `:content_type`, and `:binary` in addition to positional arguments. ([@jeremyevans]) - `Static` supports a `:cascade` option for calling the app if there is no matching file. ([@jeremyevans]) - `Session::Abstract::SessionHash#dig`. ([@jeremyevans]) - `Response.[]` and `MockResponse.[]` for creating instances using status, headers, and body. ([@ioquatix]) - Convenient cache and content type methods for `Rack::Response`. ([#1555](https://github.com/rack/rack/pull/1555), [@ioquatix]) ### Changed - `Request#params` no longer rescues EOFError. ([@jeremyevans]) - `Directory` uses a streaming approach, significantly improving time to first byte for large directories. ([@jeremyevans]) - `Directory` no longer includes a Parent directory link in the root directory index. ([@jeremyevans]) - `QueryParser#parse_nested_query` uses original backtrace when reraising exception with new class. ([@jeremyevans]) - `ConditionalGet` follows RFC 7232 precedence if both If-None-Match and If-Modified-Since headers are provided. ([@jeremyevans]) - `.ru` files supports the `frozen-string-literal` magic comment. ([@eregon](https://github.com/eregon)) - Rely on autoload to load constants instead of requiring internal files, make sure to require 'rack' and not just 'rack/...'. ([@jeremyevans]) - BREAKING CHANGE: `Etag` will continue sending ETag even if the response should not be cached. Streaming no longer works without a workaround, see [#1619](https://github.com/rack/rack/issues/1619#issuecomment-848460528). ([@henm](https://github.com/henm)) - `Request#host_with_port` no longer includes a colon for a missing or empty port. ([@AlexWayfer](https://github.com/AlexWayfer)) - All handlers uses keywords arguments instead of an options hash argument. ([@ioquatix]) - `Files` handling of range requests no longer return a body that supports `to_path`, to ensure range requests are handled correctly. ([@jeremyevans]) - `Multipart::Generator` only includes `Content-Length` for files with paths, and `Content-Disposition` `filename` if the `UploadedFile` instance has one. ([@jeremyevans]) - `Request#ssl?` is true for the `wss` scheme (secure websockets). ([@jeremyevans]) - `Rack::HeaderHash` is memoized by default. ([#1549](https://github.com/rack/rack/pull/1549), [@ioquatix]) - `Rack::Directory` allow directory traversal inside root directory. ([#1417](https://github.com/rack/rack/pull/1417), [@ThomasSevestre](https://github.com/ThomasSevestre)) - Sort encodings by server preference. ([#1184](https://github.com/rack/rack/pull/1184), [@ioquatix], [@wjordan](https://github.com/wjordan)) - Rework host/hostname/authority implementation in `Rack::Request`. `#host` and `#host_with_port` have been changed to correctly return IPv6 addresses formatted with square brackets, as defined by [RFC3986](https://tools.ietf.org/html/rfc3986#section-3.2.2). ([#1561](https://github.com/rack/rack/pull/1561), [@ioquatix]) - `Rack::Builder` parsing options on first `#\` line is deprecated. ([#1574](https://github.com/rack/rack/pull/1574), [@ioquatix]) ### Removed - `Directory#path` as it was not used and always returned nil. ([@jeremyevans]) - `BodyProxy#each` as it was only needed to work around a bug in Ruby <1.9.3. ([@jeremyevans]) - `URLMap::INFINITY` and `URLMap::NEGATIVE_INFINITY`, in favor of `Float::INFINITY`. ([@ch1c0t](https://github.com/ch1c0t)) - Deprecation of `Rack::File`. It will be deprecated again in rack 2.2 or 3.0. ([@rafaelfranca](https://github.com/rafaelfranca)) - Support for Ruby 2.2 as it is well past EOL. ([@ioquatix]) - Remove `Rack::Files#response_body` as the implementation was broken. ([#1153](https://github.com/rack/rack/pull/1153), [@ioquatix]) - Remove `SERVER_ADDR` which was never part of the original SPEC. ([#1573](https://github.com/rack/rack/pull/1573), [@ioquatix]) ### Fixed - `Directory` correctly handles root paths containing glob metacharacters. ([@jeremyevans]) - `Cascade` uses a new response object for each call if initialized with no apps. ([@jeremyevans]) - `BodyProxy` correctly delegates keyword arguments to the body object on Ruby 2.7+. ([@jeremyevans]) - `BodyProxy#method` correctly handles methods delegated to the body object. ([@jeremyevans]) - `Request#host` and `Request#host_with_port` handle IPv6 addresses correctly. ([@AlexWayfer](https://github.com/AlexWayfer)) - `Lint` checks when response hijacking that `rack.hijack` is called with a valid object. ([@jeremyevans]) - `Response#write` correctly updates `Content-Length` if initialized with a body. ([@jeremyevans]) - `CommonLogger` includes `SCRIPT_NAME` when logging. ([@Erol](https://github.com/Erol)) - `Utils.parse_nested_query` correctly handles empty queries, using an empty instance of the params class instead of a hash. ([@jeremyevans]) - `Directory` correctly escapes paths in links. ([@yous](https://github.com/yous)) - `Request#delete_cookie` and related `Utils` methods handle `:domain` and `:path` options in same call. ([@jeremyevans]) - `Request#delete_cookie` and related `Utils` methods do an exact match on `:domain` and `:path` options. ([@jeremyevans]) - `Static` no longer adds headers when a gzipped file request has a 304 response. ([@chooh](https://github.com/chooh)) - `ContentLength` sets `Content-Length` response header even for bodies not responding to `to_ary`. ([@jeremyevans]) - Thin handler supports options passed directly to `Thin::Controllers::Controller`. ([@jeremyevans]) - WEBrick handler no longer ignores `:BindAddress` option. ([@jeremyevans]) - `ShowExceptions` handles invalid POST data. ([@jeremyevans]) - Basic authentication requires a password, even if the password is empty. ([@jeremyevans]) - `Lint` checks response is array with 3 elements, per SPEC. ([@jeremyevans]) - Support for using `:SSLEnable` option when using WEBrick handler. (Gregor Melhorn) - Close response body after buffering it when buffering. ([@ioquatix]) - Only accept `;` as delimiter when parsing cookies. ([@mrageh](https://github.com/mrageh)) - `Utils::HeaderHash#clear` clears the name mapping as well. ([@raxoft](https://github.com/raxoft)) - Support for passing `nil` `Rack::Files.new`, which notably fixes Rails' current `ActiveStorage::FileServer` implementation. ([@ioquatix]) ### Documentation - CHANGELOG updates. ([@aupajo](https://github.com/aupajo)) - Added [CONTRIBUTING](CONTRIBUTING.md). ([@dblock](https://github.com/dblock)) ## [2.0.9] - 2020-02-08 - Handle case where session id key is requested but missing ([@jeremyevans]) - Restore support for code relying on `SessionId#to_s`. ([@jeremyevans]) - Add support for `SameSite=None` cookie value. ([@hennikul](https://github.com/hennikul)) ## [2.1.2] - 2020-01-27 - Fix multipart parser for some files to prevent denial of service ([@aiomaster](https://github.com/aiomaster)) - Fix `Rack::Builder#use` with keyword arguments ([@kamipo](https://github.com/kamipo)) - Skip deflating in Rack::Deflater if Content-Length is 0 ([@jeremyevans]) - Remove `SessionHash#transform_keys`, no longer needed ([@pavel](https://github.com/pavel)) - Add to_hash to wrap Hash and Session classes ([@oleh-demyanyuk](https://github.com/oleh-demyanyuk)) - Handle case where session id key is requested but missing ([@jeremyevans]) ## [2.1.1] - 2020-01-12 - Remove `Rack::Chunked` from `Rack::Server` default middleware. ([#1475](https://github.com/rack/rack/pull/1475), [@ioquatix]) - Restore support for code relying on `SessionId#to_s`. ([@jeremyevans]) ## [2.1.0] - 2020-01-10 ### Added - Add support for `SameSite=None` cookie value. ([@hennikul](https://github.com/hennikul)) - Add trailer headers. ([@eileencodes](https://github.com/eileencodes)) - Add MIME Types for video streaming. ([@styd](https://github.com/styd)) - Add MIME Type for WASM. ([@buildrtech](https://github.com/buildrtech)) - Add `Early Hints(103)` to status codes. ([@egtra](https://github.com/egtra)) - Add `Too Early(425)` to status codes. ([@y-yagi]((https://github.com/y-yagi))) - Add `Bandwidth Limit Exceeded(509)` to status codes. ([@CJKinni](https://github.com/CJKinni)) - Add method for custom `ip_filter`. ([@svcastaneda](https://github.com/svcastaneda)) - Add boot-time profiling capabilities to `rackup`. ([@tenderlove](https://github.com/tenderlove)) - Add multi mapping support for `X-Accel-Mappings` header. ([@yoshuki](https://github.com/yoshuki)) - Add `sync: false` option to `Rack::Deflater`. (Eric Wong) - Add `Builder#freeze_app` to freeze application and all middleware instances. ([@jeremyevans]) - Add API to extract cookies from `Rack::MockResponse`. ([@petercline](https://github.com/petercline)) ### Changed - Don't propagate nil values from middleware. ([@ioquatix]) - Lazily initialize the response body and only buffer it if required. ([@ioquatix]) - Fix deflater zlib buffer errors on empty body part. ([@felixbuenemann](https://github.com/felixbuenemann)) - Set `X-Accel-Redirect` to percent-encoded path. ([@diskkid](https://github.com/diskkid)) - Remove unnecessary buffer growing when parsing multipart. ([@tainoe](https://github.com/tainoe)) - Expand the root path in `Rack::Static` upon initialization. ([@rosenfeld](https://github.com/rosenfeld)) - Make `ShowExceptions` work with binary data. ([@axyjo](https://github.com/axyjo)) - Use buffer string when parsing multipart requests. ([@janko-m](https://github.com/janko-m)) - Support optional UTF-8 Byte Order Mark (BOM) in config.ru. ([@mikegee](https://github.com/mikegee)) - Handle `X-Forwarded-For` with optional port. ([@dpritchett](https://github.com/dpritchett)) - Use `Time#httpdate` format for Expires, as proposed by RFC 7231. ([@nanaya](https://github.com/nanaya)) - Make `Utils.status_code` raise an error when the status symbol is invalid instead of `500`. ([@adambutler](https://github.com/adambutler)) - Rename `Request::SCHEME_WHITELIST` to `Request::ALLOWED_SCHEMES`. - Make `Multipart::Parser.get_filename` accept files with `+` in their name. ([@lucaskanashiro](https://github.com/lucaskanashiro)) - Add Falcon to the default handler fallbacks. ([@ioquatix]) - Update codebase to avoid string mutations in preparation for `frozen_string_literals`. ([@pat](https://github.com/pat)) - Change `MockRequest#env_for` to rely on the input optionally responding to `#size` instead of `#length`. ([@janko](https://github.com/janko)) - Rename `Rack::File` -> `Rack::Files` and add deprecation notice. ([@postmodern](https://github.com/postmodern)) - Prefer Base64 “strict encoding” for Base64 cookies. ([@ioquatix]) ### Removed - BREAKING CHANGE: Remove `to_ary` from Response ([@tenderlove](https://github.com/tenderlove)) - Deprecate `Rack::Session::Memcache` in favor of `Rack::Session::Dalli` from dalli gem ([@fatkodima](https://github.com/fatkodima)) ### Fixed - Eliminate warnings for Ruby 2.7. ([@osamtimizer](https://github.com/osamtimizer])) ### Documentation - Update broken example in `Session::Abstract::ID` documentation. ([tonytonyjan](https://github.com/tonytonyjan)) - Add Padrino to the list of frameworks implementing Rack. ([@wikimatze](https://github.com/wikimatze)) - Remove Mongrel from the suggested server options in the help output. ([@tricknotes](https://github.com/tricknotes)) - Replace `HISTORY.md` and `NEWS.md` with `CHANGELOG.md`. ([@twitnithegirl](https://github.com/twitnithegirl)) - CHANGELOG updates. ([@drenmi](https://github.com/Drenmi), [@p8](https://github.com/p8)) ## [2.0.8] - 2019-12-08 ### Security - [[CVE-2019-16782](https://nvd.nist.gov/vuln/detail/CVE-2019-16782)] Prevent timing attacks targeted at session ID lookup. BREAKING CHANGE: Session ID is now a SessionId instance instead of a String. ([@tenderlove](https://github.com/tenderlove), [@rafaelfranca](https://github.com/rafaelfranca)) ## [1.6.12] - 2019-12-08 ### Security - [[CVE-2019-16782](https://nvd.nist.gov/vuln/detail/CVE-2019-16782)] Prevent timing attacks targeted at session ID lookup. BREAKING CHANGE: Session ID is now a SessionId instance instead of a String. ([@tenderlove](https://github.com/tenderlove), [@rafaelfranca](https://github.com/rafaelfranca)) ## [2.0.7] - 2019-04-02 ### Fixed - Remove calls to `#eof?` on Rack input in `Multipart::Parser`, as this breaks the specification. ([@matthewd](https://github.com/matthewd)) - Preserve forwarded IP addresses for trusted proxy chains. ([@SamSaffron](https://github.com/SamSaffron)) ## [2.0.6] - 2018-11-05 ### Fixed - [[CVE-2018-16470](https://nvd.nist.gov/vuln/detail/CVE-2018-16470)] Reduce buffer size of `Multipart::Parser` to avoid pathological parsing. ([@tenderlove](https://github.com/tenderlove)) - Fix a call to a non-existing method `#accepts_html` in the `ShowExceptions` middleware. ([@tomelm](https://github.com/tomelm)) - [[CVE-2018-16471](https://nvd.nist.gov/vuln/detail/CVE-2018-16471)] Whitelist HTTP and HTTPS schemes in `Request#scheme` to prevent a possible XSS attack. ([@PatrickTulskie](https://github.com/PatrickTulskie)) ## [2.0.5] - 2018-04-23 ### Fixed - Record errors originating from invalid UTF8 in `MethodOverride` middleware instead of breaking. ([@mclark](https://github.com/mclark)) ## [2.0.4] - 2018-01-31 ### Changed - Ensure the `Lock` middleware passes the original `env` object. ([@lugray](https://github.com/lugray)) - Improve performance of `Multipart::Parser` when uploading large files. ([@tompng](https://github.com/tompng)) - Increase buffer size in `Multipart::Parser` for better performance. ([@jkowens](https://github.com/jkowens)) - Reduce memory usage of `Multipart::Parser` when uploading large files. ([@tompng](https://github.com/tompng)) - Replace ConcurrentRuby dependency with native `Queue`. ([@devmchakan](https://github.com/devmchakan)) ### Fixed - Require the correct digest algorithm in the `ETag` middleware. ([@matthewd](https://github.com/matthewd)) ### Documentation - Update homepage links to use SSL. ([@hugoabonizio](https://github.com/hugoabonizio)) ## [2.0.3] - 2017-05-15 ### Changed - Ensure `env` values are ASCII 8-bit encoded. ([@eileencodes](https://github.com/eileencodes)) ### Fixed - Prevent exceptions when a class with mixins inherits from `Session::Abstract::ID`. ([@jnraine](https://github.com/jnraine)) ## [2.0.2] - 2017-05-08 ### Added - Allow `Session::Abstract::SessionHash#fetch` to accept a block with a default value. ([@yannvanhalewyn](https://github.com/yannvanhalewyn)) - Add `Builder#freeze_app` to freeze application and all middleware. ([@jeremyevans]) ### Changed - Freeze default session options to avoid accidental mutation. ([@kirs](https://github.com/kirs)) - Detect partial hijack without hash headers. ([@devmchakan](https://github.com/devmchakan)) - Update tests to use MiniTest 6 matchers. ([@tonytonyjan](https://github.com/tonytonyjan)) - Allow 205 Reset Content responses to set a Content-Length, as RFC 7231 proposes setting this to 0. ([@devmchakan](https://github.com/devmchakan)) ### Fixed - Handle `NULL` bytes in multipart filenames. ([@casperisfine](https://github.com/casperisfine)) - Remove warnings due to miscapitalized global. ([@ioquatix]) - Prevent exceptions caused by a race condition on multi-threaded servers. ([@sophiedeziel](https://github.com/sophiedeziel)) - Add RDoc as an explicit dependency for `doc` group. ([@tonytonyjan](https://github.com/tonytonyjan)) - Record errors originating from `Multipart::Parser` in the `MethodOverride` middleware instead of letting them bubble up. ([@carlzulauf](https://github.com/carlzulauf)) - Remove remaining use of removed `Utils#bytesize` method from the `File` middleware. ([@brauliomartinezlm](https://github.com/brauliomartinezlm)) ### Removed - Remove `deflate` encoding support to reduce caching overhead. ([@devmchakan](https://github.com/devmchakan)) ### Documentation - Update broken example in `Deflater` documentation. ([@mwpastore](https://github.com/mwpastore)) ## [2.0.1] - 2016-06-30 ### Changed - Remove JSON as an explicit dependency. ([@mperham](https://github.com/mperham)) # History/News Archive Items below this line are from the previously maintained HISTORY.md and NEWS.md files. ## [2.0.0.rc1] 2016-05-06 - Rack::Session::Abstract::ID is deprecated. Please change to use Rack::Session::Abstract::Persisted ## [2.0.0.alpha] 2015-12-04 - First-party "SameSite" cookies. Browsers omit SameSite cookies from third-party requests, closing the door on many CSRF attacks. - Pass `same_site: true` (or `:strict`) to enable: response.set_cookie 'foo', value: 'bar', same_site: true or `same_site: :lax` to use Lax enforcement: response.set_cookie 'foo', value: 'bar', same_site: :lax - Based on version 7 of the Same-site Cookies internet draft: https://tools.ietf.org/html/draft-west-first-party-cookies-07 - Thanks to Ben Toews (@mastahyeti) and Bob Long (@bobjflong) for updating to drafts 5 and 7. - Add `Rack::Events` middleware for adding event based middleware: middleware that does not care about the response body, but only cares about doing work at particular points in the request / response lifecycle. - Add `Rack::Request#authority` to calculate the authority under which the response is being made (this will be handy for h2 pushes). - Add `Rack::Response::Helpers#cache_control` and `cache_control=`. Use this for setting cache control headers on your response objects. - Add `Rack::Response::Helpers#etag` and `etag=`. Use this for setting etag values on the response. - Introduce `Rack::Response::Helpers#add_header` to add a value to a multi-valued response header. Implemented in terms of other `Response#*_header` methods, so it's available to any response-like class that includes the `Helpers` module. - Add `Rack::Request#add_header` to match. - `Rack::Session::Abstract::ID` IS DEPRECATED. Please switch to `Rack::Session::Abstract::Persisted`. `Rack::Session::Abstract::Persisted` uses a request object rather than the `env` hash. - Pull `ENV` access inside the request object in to a module. This will help with legacy Request objects that are ENV based but don't want to inherit from Rack::Request - Move most methods on the `Rack::Request` to a module `Rack::Request::Helpers` and use public API to get values from the request object. This enables users to mix `Rack::Request::Helpers` in to their own objects so they can implement `(get|set|fetch|each)_header` as they see fit (for example a proxy object). - Files and directories with + in the name are served correctly. Rather than unescaping paths like a form, we unescape with a URI parser using `Rack::Utils.unescape_path`. Fixes #265 - Tempfiles are automatically closed in the case that there were too many posted. - Added methods for manipulating response headers that don't assume they're stored as a Hash. Response-like classes may include the Rack::Response::Helpers module if they define these methods: - Rack::Response#has_header? - Rack::Response#get_header - Rack::Response#set_header - Rack::Response#delete_header - Introduce Util.get_byte_ranges that will parse the value of the HTTP_RANGE string passed to it without depending on the `env` hash. `byte_ranges` is deprecated in favor of this method. - Change Session internals to use Request objects for looking up session information. This allows us to only allocate one request object when dealing with session objects (rather than doing it every time we need to manipulate cookies, etc). - Add `Rack::Request#initialize_copy` so that the env is duped when the request gets duped. - Added methods for manipulating request specific data. This includes data set as CGI parameters, and just any arbitrary data the user wants to associate with a particular request. New methods: - Rack::Request#has_header? - Rack::Request#get_header - Rack::Request#fetch_header - Rack::Request#each_header - Rack::Request#set_header - Rack::Request#delete_header - lib/rack/utils.rb: add a method for constructing "delete" cookie headers. This allows us to construct cookie headers without depending on the side effects of mutating a hash. - Prevent extremely deep parameters from being parsed. CVE-2015-3225 ## [1.6.1] 2015-05-06 - Fix CVE-2014-9490, denial of service attack in OkJson - Use a monotonic time for Rack::Runtime, if available - RACK_MULTIPART_LIMIT changed to RACK_MULTIPART_PART_LIMIT (RACK_MULTIPART_LIMIT is deprecated and will be removed in 1.7.0) ## [1.5.3] 2015-05-06 - Fix CVE-2014-9490, denial of service attack in OkJson - Backport bug fixes to 1.5 series ## [1.6.0] 2014-01-18 - Response#unauthorized? helper - Deflater now accepts an options hash to control compression on a per-request level - Builder#warmup method for app preloading - Request#accept_language method to extract HTTP_ACCEPT_LANGUAGE - Add quiet mode of rack server, rackup --quiet - Update HTTP Status Codes to RFC 7231 - Less strict header name validation according to RFC 2616 - SPEC updated to specify headers conform to RFC7230 specification - Etag correctly marks etags as weak - Request#port supports multiple x-http-forwarded-proto values - Utils#multipart_part_limit configures the maximum number of parts a request can contain - Default host to localhost when in development mode - Various bugfixes and performance improvements ## [1.5.2] 2013-02-07 - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie - Fix CVE-2013-0262, symlink path traversal in Rack::File - Add various methods to Session for enhanced Rails compatibility - Request#trusted_proxy? now only matches whole strings - Add JSON cookie coder, to be default in Rack 1.6+ due to security concerns - URLMap host matching in environments that don't set the Host header fixed - Fix a race condition that could result in overwritten pidfiles - Various documentation additions ## [1.4.5] 2013-02-07 - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie - Fix CVE-2013-0262, symlink path traversal in Rack::File ## [1.1.6, 1.2.8, 1.3.10] 2013-02-07 - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie ## [1.5.1] 2013-01-28 - Rack::Lint check_hijack now conforms to other parts of SPEC - Added hash-like methods to Abstract::ID::SessionHash for compatibility - Various documentation corrections ## [1.5.0] 2013-01-21 - Introduced hijack SPEC, for before-response and after-response hijacking - SessionHash is no longer a Hash subclass - Rack::File cache_control parameter is removed, in place of headers options - Rack::Auth::AbstractRequest#scheme now yields strings, not symbols - Rack::Utils cookie functions now format expires in RFC 2822 format - Rack::File now has a default mime type - rackup -b 'run Rack::Files.new(".")', option provides command line configs - Rack::Deflater will no longer double encode bodies - Rack::Mime#match? provides convenience for Accept header matching - Rack::Utils#q_values provides splitting for Accept headers - Rack::Utils#best_q_match provides a helper for Accept headers - Rack::Handler.pick provides convenience for finding available servers - Puma added to the list of default servers (preferred over Webrick) - Various middleware now correctly close body when replacing it - Rack::Request#params is no longer persistent with only GET params - Rack::Request#update_param and #delete_param provide persistent operations - Rack::Request#trusted_proxy? now returns true for local unix sockets - Rack::Response no longer forces Content-Types - Rack::Sendfile provides local mapping configuration options - Rack::Utils#rfc2109 provides old netscape style time output - Updated HTTP status codes - Ruby 1.8.6 likely no longer passes tests, and is no longer fully supported ## [1.4.4, 1.3.9, 1.2.7, 1.1.5] 2013-01-13 - [SEC] Rack::Auth::AbstractRequest no longer symbolizes arbitrary strings - Fixed erroneous test case in the 1.3.x series ## [1.4.3] 2013-01-07 - Security: Prevent unbounded reads in large multipart boundaries ## [1.3.8] 2013-01-07 - Security: Prevent unbounded reads in large multipart boundaries ## [1.4.2] 2013-01-06 - Add warnings when users do not provide a session secret - Fix parsing performance for unquoted filenames - Updated URI backports - Fix URI backport version matching, and silence constant warnings - Correct parameter parsing with empty values - Correct rackup '-I' flag, to allow multiple uses - Correct rackup pidfile handling - Report rackup line numbers correctly - Fix request loops caused by non-stale nonces with time limits - Fix reloader on Windows - Prevent infinite recursions from Response#to_ary - Various middleware better conforms to the body close specification - Updated language for the body close specification - Additional notes regarding ECMA escape compatibility issues - Fix the parsing of multiple ranges in range headers - Prevent errors from empty parameter keys - Added PATCH verb to Rack::Request - Various documentation updates - Fix session merge semantics (fixes rack-test) - Rack::Static :index can now handle multiple directories - All tests now utilize Rack::Lint (special thanks to Lars Gierth) - Rack::File cache_control parameter is now deprecated, and removed by 1.5 - Correct Rack::Directory script name escaping - Rack::Static supports header rules for sophisticated configurations - Multipart parsing now works without a Content-Length header - New logos courtesy of Zachary Scott! - Rack::BodyProxy now explicitly defines #each, useful for C extensions - Cookies that are not URI escaped no longer cause exceptions ## [1.3.7] 2013-01-06 - Add warnings when users do not provide a session secret - Fix parsing performance for unquoted filenames - Updated URI backports - Fix URI backport version matching, and silence constant warnings - Correct parameter parsing with empty values - Correct rackup '-I' flag, to allow multiple uses - Correct rackup pidfile handling - Report rackup line numbers correctly - Fix request loops caused by non-stale nonces with time limits - Fix reloader on Windows - Prevent infinite recursions from Response#to_ary - Various middleware better conforms to the body close specification - Updated language for the body close specification - Additional notes regarding ECMA escape compatibility issues - Fix the parsing of multiple ranges in range headers ## [1.2.6] 2013-01-06 - Add warnings when users do not provide a session secret - Fix parsing performance for unquoted filenames ## [1.1.4] 2013-01-06 - Add warnings when users do not provide a session secret ## [1.4.1] 2012-01-22 - Alter the keyspace limit calculations to reduce issues with nested params - Add a workaround for multipart parsing where files contain unescaped "%" - Added Rack::Response::Helpers#method_not_allowed? (code 405) - Rack::File now returns 404 for illegal directory traversals - Rack::File now returns 405 for illegal methods (non HEAD/GET) - Rack::Cascade now catches 405 by default, as well as 404 - Cookies missing '--' no longer cause an exception to be raised - Various style changes and documentation spelling errors - Rack::BodyProxy always ensures to execute its block - Additional test coverage around cookies and secrets - Rack::Session::Cookie can now be supplied either secret or old_secret - Tests are no longer dependent on set order - Rack::Static no longer defaults to serving index files - Rack.release was fixed ## [1.4.0] 2011-12-28 - Ruby 1.8.6 support has officially been dropped. Not all tests pass. - Raise sane error messages for broken config.ru - Allow combining run and map in a config.ru - Rack::ContentType will not set Content-Type for responses without a body - Status code 205 does not send a response body - Rack::Response::Helpers will not rely on instance variables - Rack::Utils.build_query no longer outputs '=' for nil query values - Various mime types added - Rack::MockRequest now supports HEAD - Rack::Directory now supports files that contain RFC3986 reserved chars - Rack::File now only supports GET and HEAD requests - Rack::Server#start now passes the block to Rack::Handler::#run - Rack::Static now supports an index option - Added the Teapot status code - rackup now defaults to Thin instead of Mongrel (if installed) - Support added for HTTP_X_FORWARDED_SCHEME - Numerous bug fixes, including many fixes for new and alternate rubies ## [1.1.3] 2011-12-28 - Security fix. http://www.ocert.org/advisories/ocert-2011-003.html Further information here: http://jruby.org/2011/12/27/jruby-1-6-5-1 ## [1.3.5] 2011-10-17 - Fix annoying warnings caused by the backport in 1.3.4 ## [1.3.4] 2011-10-01 - Backport security fix from 1.9.3, also fixes some roundtrip issues in URI - Small documentation update - Fix an issue where BodyProxy could cause an infinite recursion - Add some supporting files for travis-ci ## [1.2.4] 2011-09-16 - Fix a bug with MRI regex engine to prevent XSS by malformed unicode ## [1.3.3] 2011-09-16 - Fix bug with broken query parameters in Rack::ShowExceptions - Rack::Request#cookies no longer swallows exceptions on broken input - Prevents XSS attacks enabled by bug in Ruby 1.8's regexp engine - Rack::ConditionalGet handles broken If-Modified-Since helpers ## [1.3.2] 2011-07-16 - Fix for Rails and rack-test, Rack::Utils#escape calls to_s ## [1.3.1] 2011-07-13 - Fix 1.9.1 support - Fix JRuby support - Properly handle $KCODE in Rack::Utils.escape - Make method_missing/respond_to behavior consistent for Rack::Lock, Rack::Auth::Digest::Request and Rack::Multipart::UploadedFile - Reenable passing rack.session to session middleware - Rack::CommonLogger handles streaming responses correctly - Rack::MockResponse calls close on the body object - Fix a DOS vector from MRI stdlib backport ## [1.2.3] 2011-05-22 - Pulled in relevant bug fixes from 1.3 - Fixed 1.8.6 support ## [1.3.0] 2011-05-22 - Various performance optimizations - Various multipart fixes - Various multipart refactors - Infinite loop fix for multipart - Test coverage for Rack::Server returns - Allow files with '..', but not path components that are '..' - rackup accepts handler-specific options on the command line - Request#params no longer merges POST into GET (but returns the same) - Use URI.encode_www_form_component instead. Use core methods for escaping. - Allow multi-line comments in the config file - Bug L#94 reported by Nikolai Lugovoi, query parameter unescaping. - Rack::Response now deletes Content-Length when appropriate - Rack::Deflater now supports streaming - Improved Rack::Handler loading and searching - Support for the PATCH verb - env['rack.session.options'] now contains session options - Cookies respect renew - Session middleware uses SecureRandom.hex ## [1.2.2, 1.1.2] 2011-03-13 - Security fix in Rack::Auth::Digest::MD5: when authenticator returned nil, permission was granted on empty password. ## [1.2.1] 2010-06-15 - Make CGI handler rewindable - Rename spec/ to test/ to not conflict with SPEC on lesser operating systems ## [1.2.0] 2010-06-13 - Removed Camping adapter: Camping 2.0 supports Rack as-is - Removed parsing of quoted values - Add Request.trace? and Request.options? - Add mime-type for .webm and .htc - Fix HTTP_X_FORWARDED_FOR - Various multipart fixes - Switch test suite to bacon ## [1.1.0] 2010-01-03 - Moved Auth::OpenID to rack-contrib. - SPEC change that relaxes Lint slightly to allow subclasses of the required types - SPEC change to document rack.input binary mode in greater detail - SPEC define optional rack.logger specification - File servers support X-Cascade header - Imported Config middleware - Imported ETag middleware - Imported Runtime middleware - Imported Sendfile middleware - New Logger and NullLogger middlewares - Added mime type for .ogv and .manifest. - Don't squeeze PATH_INFO slashes - Use Content-Type to determine POST params parsing - Update Rack::Utils::HTTP_STATUS_CODES hash - Add status code lookup utility - Response should call #to_i on the status - Add Request#user_agent - Request#host knows about forwarded host - Return an empty string for Request#host if HTTP_HOST and SERVER_NAME are both missing - Allow MockRequest to accept hash params - Optimizations to HeaderHash - Refactored rackup into Rack::Server - Added Utils.build_nested_query to complement Utils.parse_nested_query - Added Utils::Multipart.build_multipart to complement Utils::Multipart.parse_multipart - Extracted set and delete cookie helpers into Utils so they can be used outside Response - Extract parse_query and parse_multipart in Request so subclasses can change their behavior - Enforce binary encoding in RewindableInput - Set correct external_encoding for handlers that don't use RewindableInput ## [1.0.1] 2009-10-18 - Bump remainder of rack.versions. - Support the pure Ruby FCGI implementation. - Fix for form names containing "=": split first then unescape components - Fixes the handling of the filename parameter with semicolons in names. - Add anchor to nested params parsing regexp to prevent stack overflows - Use more compatible gzip write api instead of "<<". - Make sure that Reloader doesn't break when executed via ruby -e - Make sure WEBrick respects the :Host option - Many Ruby 1.9 fixes. ## [1.0.0] 2009-04-25 - SPEC change: Rack::VERSION has been pushed to [1,0]. - SPEC change: header values must be Strings now, split on "\n". - SPEC change: Content-Length can be missing, in this case chunked transfer encoding is used. - SPEC change: rack.input must be rewindable and support reading into a buffer, wrap with Rack::RewindableInput if it isn't. - SPEC change: rack.session is now specified. - SPEC change: Bodies can now additionally respond to #to_path with a filename to be served. - NOTE: String bodies break in 1.9, use an Array consisting of a single String instead. - New middleware Rack::Lock. - New middleware Rack::ContentType. - Rack::Reloader has been rewritten. - Major update to Rack::Auth::OpenID. - Support for nested parameter parsing in Rack::Response. - Support for redirects in Rack::Response. - HttpOnly cookie support in Rack::Response. - The Rakefile has been rewritten. - Many bugfixes and small improvements. ## [0.9.1] 2009-01-09 - Fix directory traversal exploits in Rack::File and Rack::Directory. ## [0.9] 2009-01-06 - Rack is now managed by the Rack Core Team. - Rack::Lint is stricter and follows the HTTP RFCs more closely. - Added ConditionalGet middleware. - Added ContentLength middleware. - Added Deflater middleware. - Added Head middleware. - Added MethodOverride middleware. - Rack::Mime now provides popular MIME-types and their extension. - Mongrel Header now streams. - Added Thin handler. - Official support for swiftiplied Mongrel. - Secure cookies. - Made HeaderHash case-preserving. - Many bugfixes and small improvements. ## [0.4] 2008-08-21 - New middleware, Rack::Deflater, by Christoffer Sawicki. - OpenID authentication now needs ruby-openid 2. - New Memcache sessions, by blink. - Explicit EventedMongrel handler, by Joshua Peek - Rack::Reloader is not loaded in rackup development mode. - rackup can daemonize with -D. - Many bugfixes, especially for pool sessions, URLMap, thread safety and tempfile handling. - Improved tests. - Rack moved to Git. ## [0.3] 2008-02-26 - LiteSpeed handler, by Adrian Madrid. - SCGI handler, by Jeremy Evans. - Pool sessions, by blink. - OpenID authentication, by blink. - :Port and :File options for opening FastCGI sockets, by blink. - Last-Modified HTTP header for Rack::File, by blink. - Rack::Builder#use now accepts blocks, by Corey Jewett. (See example/protectedlobster.ru) - HTTP status 201 can contain a Content-Type and a body now. - Many bugfixes, especially related to Cookie handling. ## [0.2] 2007-05-16 - HTTP Basic authentication. - Cookie Sessions. - Static file handler. - Improved Rack::Request. - Improved Rack::Response. - Added Rack::ShowStatus, for better default error messages. - Bug fixes in the Camping adapter. - Removed Rails adapter, was too alpha. ## [0.1] 2007-03-03 [@ioquatix]: https://github.com/ioquatix "Samuel Williams" [@jeremyevans]: https://github.com/jeremyevans "Jeremy Evans" [@amatsuda]: https://github.com/amatsuda "Akira Matsuda" [@wjordan]: https://github.com/wjordan "Will Jordan" [@BlakeWilliams]: https://github.com/BlakeWilliams "Blake Williams" PK!tz,, README.mdnu[# ![Rack](contrib/logo.webp) > **_NOTE:_** Rack v3.0.0 was recently released. Please check the [Upgrade > Guide](UPGRADE-GUIDE.md) for more details about migrating your existing > servers, middlewares and applications. For detailed information on specific > changes, check the [Change Log](CHANGELOG.md). Rack provides a minimal, modular, and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the bridge between web servers, web frameworks, and web application into a single method call. The exact details of this are described in the [Rack Specification], which all Rack applications should conform to. ## Installation Add the rack gem to your application bundle, or follow the instructions provided by a [supported web framework](#supported-web-frameworks): ```bash # Install it generally: $ gem install rack --pre # or, add it to your current application gemfile: $ bundle add rack --version 3.0.0 ``` If you need features from `Rack::Session` or `bin/rackup` please add those gems separately. ```bash $ gem install rack-session rackup ``` ## Usage Create a file called `config.ru` with the following contents: ```ruby run do |env| [200, {}, ["Hello World"]] end ``` Run this using the rackup gem or another [supported web server](#supported-web-servers). ```bash $ gem install rackup $ rackup $ curl http://localhost:9292 Hello World ``` ## Supported web servers Rack is supported by a wide range of servers, including: * [Agoo](https://github.com/ohler55/agoo) * [Falcon](https://github.com/socketry/falcon) **(Rack 3 Compatible)** * [Iodine](https://github.com/boazsegev/iodine) * [NGINX Unit](https://unit.nginx.org/) * [Phusion Passenger](https://www.phusionpassenger.com/) (which is mod_rack for Apache and for nginx) * [Puma](https://puma.io/) * [Thin](https://github.com/macournoyer/thin) * [Unicorn](https://yhbt.net/unicorn/) * [uWSGI](https://uwsgi-docs.readthedocs.io/en/latest/) * [Lamby](https://lamby.custominktech.com) (for AWS Lambda) You will need to consult the server documentation to find out what features and limitations they may have. In general, any valid Rack app will run the same on all these servers, without changing anything. ### Rackup Rack provides a separate gem, [rackup](https://github.com/rack/rackup) which is a generic interface for running a Rack application on supported servers, which include `WEBRick`, `Puma`, `Falcon` and others. ## Supported web frameworks These frameworks and many others support the [Rack Specification]: * [Camping](https://github.com/camping/camping) * [Hanami](https://hanamirb.org/) * [Padrino](https://padrinorb.com/) * [Roda](https://github.com/jeremyevans/roda) **(Rack 3 Compatible)** * [Ruby on Rails](https://rubyonrails.org/) * [Sinatra](https://sinatrarb.com/) * [Utopia](https://github.com/socketry/utopia) **(Rack 3 Compatible)** * [WABuR](https://github.com/ohler55/wabur) ### Older (possibly unsupported) web frameworks * [Ramaze](http://ramaze.net/) * [Rum](https://github.com/leahneukirchen/rum) ## Available middleware shipped with Rack Between the server and the framework, Rack can be customized to your applications needs using middleware. Rack itself ships with the following middleware: * `Rack::CommonLogger` for creating Apache-style logfiles. * `Rack::ConditionalGet` for returning [Not Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304) responses when the response has not changed. * `Rack::Config` for modifying the environment before processing the request. * `Rack::ContentLength` for setting a `content-length` header based on body size. * `Rack::ContentType` for setting a default `content-type` header for responses. * `Rack::Deflater` for compressing responses with gzip. * `Rack::ETag` for setting `etag` header on bodies that can be buffered. * `Rack::Events` for providing easy hooks when a request is received and when the response is sent. * `Rack::Files` for serving static files. * `Rack::Head` for returning an empty body for HEAD requests. * `Rack::Lint` for checking conformance to the [Rack Specification]. * `Rack::Lock` for serializing requests using a mutex. * `Rack::Logger` for setting a logger to handle logging errors. * `Rack::MethodOverride` for modifying the request method based on a submitted parameter. * `Rack::Recursive` for including data from other paths in the application, and for performing internal redirects. * `Rack::Reloader` for reloading files if they have been modified. * `Rack::Runtime` for including a response header with the time taken to process the request. * `Rack::Sendfile` for working with web servers that can use optimized file serving for file system paths. * `Rack::ShowException` for catching unhandled exceptions and presenting them in a nice and helpful way with clickable backtrace. * `Rack::ShowStatus` for using nice error pages for empty client error responses. * `Rack::Static` for more configurable serving of static files. * `Rack::TempfileReaper` for removing temporary files creating during a request. All these components use the same interface, which is described in detail in the [Rack Specification]. These optional components can be used in any way you wish. ### Convenience interfaces If you want to develop outside of existing frameworks, implement your own ones, or develop middleware, Rack provides many helpers to create Rack applications quickly and without doing the same web stuff all over: * `Rack::Request` which also provides query string parsing and multipart handling. * `Rack::Response` for convenient generation of HTTP replies and cookie handling. * `Rack::MockRequest` and `Rack::MockResponse` for efficient and quick testing of Rack application without real HTTP round-trips. * `Rack::Cascade` for trying additional Rack applications if an application returns a not found or method not supported response. * `Rack::Directory` for serving files under a given directory, with directory indexes. * `Rack::MediaType` for parsing content-type headers. * `Rack::Mime` for determining content-type based on file extension. * `Rack::RewindableInput` for making any IO object rewindable, using a temporary file buffer. * `Rack::URLMap` to route to multiple applications inside the same process. ## Configuration Rack exposes several configuration parameters to control various features of the implementation. ### `param_depth_limit` ```ruby Rack::Utils.param_depth_limit = 32 # default ``` The maximum amount of nesting allowed in parameters. For example, if set to 3, this query string would be allowed: ``` ?a[b][c]=d ``` but this query string would not be allowed: ``` ?a[b][c][d]=e ``` Limiting the depth prevents a possible stack overflow when parsing parameters. ### `multipart_file_limit` ```ruby Rack::Utils.multipart_file_limit = 128 # default ``` The maximum number of parts with a filename a request can contain. Accepting too many parts can lead to the server running out of file handles. The default is 128, which means that a single request can't upload more than 128 files at once. Set to 0 for no limit. Can also be set via the `RACK_MULTIPART_FILE_LIMIT` environment variable. (This is also aliased as `multipart_part_limit` and `RACK_MULTIPART_PART_LIMIT` for compatibility) ### `multipart_total_part_limit` The maximum total number of parts a request can contain of any type, including both file and non-file form fields. The default is 4096, which means that a single request can't contain more than 4096 parts. Set to 0 for no limit. Can also be set via the `RACK_MULTIPART_TOTAL_PART_LIMIT` environment variable. ## Changelog See [CHANGELOG.md](CHANGELOG.md). ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for specific details about how to make a contribution to Rack. Please post bugs, suggestions and patches to [GitHub Issues](https://github.com/rack/rack/issues). Please check our [Security Policy](https://github.com/rack/rack/security/policy) for responsible disclosure and security bug reporting process. Due to wide usage of the library, it is strongly preferred that we manage timing in order to provide viable patches at the time of disclosure. Your assistance in this matter is greatly appreciated. ## See Also ### `rack-contrib` The plethora of useful middleware created the need for a project that collects fresh Rack middleware. `rack-contrib` includes a variety of add-on components for Rack and it is easy to contribute new modules. * https://github.com/rack/rack-contrib ### `rack-session` Provides convenient session management for Rack. * https://github.com/rack/rack-session ## Thanks The Rack Core Team, consisting of * Aaron Patterson [tenderlove](https://github.com/tenderlove) * Samuel Williams [ioquatix](https://github.com/ioquatix) * Jeremy Evans [jeremyevans](https://github.com/jeremyevans) * Eileen Uchitelle [eileencodes](https://github.com/eileencodes) * Matthew Draper [matthewd](https://github.com/matthewd) * Rafael França [rafaelfranca](https://github.com/rafaelfranca) and the Rack Alumni * Ryan Tomayko [rtomayko](https://github.com/rtomayko) * Scytrin dai Kinthra [scytrin](https://github.com/scytrin) * Leah Neukirchen [leahneukirchen](https://github.com/leahneukirchen) * James Tucker [raggi](https://github.com/raggi) * Josh Peek [josh](https://github.com/josh) * José Valim [josevalim](https://github.com/josevalim) * Michael Fellinger [manveru](https://github.com/manveru) * Santiago Pastorino [spastorino](https://github.com/spastorino) * Konstantin Haase [rkh](https://github.com/rkh) would like to thank: * Adrian Madrid, for the LiteSpeed handler. * Christoffer Sawicki, for the first Rails adapter and `Rack::Deflater`. * Tim Fletcher, for the HTTP authentication code. * Luc Heinrich for the Cookie sessions, the static file handler and bugfixes. * Armin Ronacher, for the logo and racktools. * Alex Beregszaszi, Alexander Kahn, Anil Wadghule, Aredridel, Ben Alpert, Dan Kubb, Daniel Roethlisberger, Matt Todd, Tom Robinson, Phil Hagelberg, S. Brent Faulkner, Bosko Milekic, Daniel Rodríguez Troitiño, Genki Takiuchi, Geoffrey Grosenbach, Julien Sanchez, Kamal Fariz Mahyuddin, Masayoshi Takahashi, Patrick Aljordm, Mig, Kazuhiro Nishiyama, Jon Bardin, Konstantin Haase, Larry Siden, Matias Korhonen, Sam Ruby, Simon Chiang, Tim Connor, Timur Batyrshin, and Zach Brock for bug fixing and other improvements. * Eric Wong, Hongli Lai, Jeremy Kemper for their continuous support and API improvements. * Yehuda Katz and Carl Lerche for refactoring rackup. * Brian Candler, for `Rack::ContentType`. * Graham Batty, for improved handler loading. * Stephen Bannasch, for bug reports and documentation. * Gary Wright, for proposing a better `Rack::Response` interface. * Jonathan Buch, for improvements regarding `Rack::Response`. * Armin Röhrl, for tracking down bugs in the Cookie generator. * Alexander Kellett for testing the Gem and reviewing the announcement. * Marcus Rückert, for help with configuring and debugging lighttpd. * The WSGI team for the well-done and documented work they've done and Rack builds up on. * All bug reporters and patch contributors not mentioned above. ## License Rack is released under the [MIT License](MIT-LICENSE). [Rack Specification]: SPEC.rdoc PK!G TT MIT-LICENSEnu[The MIT License (MIT) Copyright (C) 2007-2021 Leah Neukirchen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!um lib/rack.rbnu[# frozen_string_literal: true # Copyright (C) 2007-2019 Leah Neukirchen # # Rack is freely distributable under the terms of an MIT-style license. # See MIT-LICENSE or https://opensource.org/licenses/MIT. # The Rack main module, serving as a namespace for all core Rack # modules and classes. # # All modules meant for use in your application are autoloaded here, # so it should be enough just to require 'rack' in your code. require_relative 'rack/version' require_relative 'rack/constants' module Rack autoload :Builder, "rack/builder" autoload :BodyProxy, "rack/body_proxy" autoload :Cascade, "rack/cascade" autoload :Chunked, "rack/chunked" autoload :CommonLogger, "rack/common_logger" autoload :ConditionalGet, "rack/conditional_get" autoload :Config, "rack/config" autoload :ContentLength, "rack/content_length" autoload :ContentType, "rack/content_type" autoload :ETag, "rack/etag" autoload :Events, "rack/events" autoload :File, "rack/file" autoload :Files, "rack/files" autoload :Deflater, "rack/deflater" autoload :Directory, "rack/directory" autoload :ForwardRequest, "rack/recursive" autoload :Handler, "rack/handler" autoload :Head, "rack/head" autoload :Headers, "rack/headers" autoload :Lint, "rack/lint" autoload :Lock, "rack/lock" autoload :Logger, "rack/logger" autoload :MediaType, "rack/media_type" autoload :MethodOverride, "rack/method_override" autoload :Mime, "rack/mime" autoload :NullLogger, "rack/null_logger" autoload :QueryParser, "rack/query_parser" autoload :Recursive, "rack/recursive" autoload :Reloader, "rack/reloader" autoload :RewindableInput, "rack/rewindable_input" autoload :Runtime, "rack/runtime" autoload :Sendfile, "rack/sendfile" autoload :Server, "rack/server" autoload :ShowExceptions, "rack/show_exceptions" autoload :ShowStatus, "rack/show_status" autoload :Static, "rack/static" autoload :TempfileReaper, "rack/tempfile_reaper" autoload :URLMap, "rack/urlmap" autoload :Utils, "rack/utils" autoload :Multipart, "rack/multipart" autoload :MockRequest, "rack/mock_request" autoload :MockResponse, "rack/mock_response" autoload :Request, "rack/request" autoload :Response, "rack/response" module Auth autoload :Basic, "rack/auth/basic" autoload :AbstractRequest, "rack/auth/abstract/request" autoload :AbstractHandler, "rack/auth/abstract/handler" autoload :Digest, "rack/auth/digest" end end PK!|Хlib/rack/files.rbnu[# frozen_string_literal: true require 'time' require_relative 'constants' require_relative 'head' require_relative 'utils' require_relative 'request' require_relative 'mime' module Rack # Rack::Files serves files below the +root+ directory given, according to the # path info of the Rack request. # e.g. when Rack::Files.new("/etc") is used, you can access 'passwd' file # as http://localhost:9292/passwd # # Handlers can detect if bodies are a Rack::Files, and use mechanisms # like sendfile on the +path+. class Files ALLOWED_VERBS = %w[GET HEAD OPTIONS] ALLOW_HEADER = ALLOWED_VERBS.join(', ') MULTIPART_BOUNDARY = 'AaB03x' attr_reader :root def initialize(root, headers = {}, default_mime = 'text/plain') @root = (::File.expand_path(root) if root) @headers = headers @default_mime = default_mime @head = Rack::Head.new(lambda { |env| get env }) end def call(env) # HEAD requests drop the response body, including 4xx error messages. @head.call env end def get(env) request = Rack::Request.new env unless ALLOWED_VERBS.include? request.request_method return fail(405, "Method Not Allowed", { 'allow' => ALLOW_HEADER }) end path_info = Utils.unescape_path request.path_info return fail(400, "Bad Request") unless Utils.valid_path?(path_info) clean_path_info = Utils.clean_path_info(path_info) path = ::File.join(@root, clean_path_info) available = begin ::File.file?(path) && ::File.readable?(path) rescue SystemCallError # Not sure in what conditions this exception can occur, but this # is a safe way to handle such an error. # :nocov: false # :nocov: end if available serving(request, path) else fail(404, "File not found: #{path_info}") end end def serving(request, path) if request.options? return [200, { 'allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []] end last_modified = ::File.mtime(path).httpdate return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified headers = { "last-modified" => last_modified } mime_type = mime_type path, @default_mime headers[CONTENT_TYPE] = mime_type if mime_type # Set custom headers headers.merge!(@headers) if @headers status = 200 size = filesize path ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size) if ranges.nil? # No ranges: ranges = [0..size - 1] elsif ranges.empty? # Unsatisfiable. Return error, and file size: response = fail(416, "Byte range unsatisfiable") response[1]["content-range"] = "bytes */#{size}" return response else # Partial content partial_content = true if ranges.size == 1 range = ranges[0] headers["content-range"] = "bytes #{range.begin}-#{range.end}/#{size}" else headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}" end status = 206 body = BaseIterator.new(path, ranges, mime_type: mime_type, size: size) size = body.bytesize end headers[CONTENT_LENGTH] = size.to_s if request.head? body = [] elsif !partial_content body = Iterator.new(path, ranges, mime_type: mime_type, size: size) end [status, headers, body] end class BaseIterator attr_reader :path, :ranges, :options def initialize(path, ranges, options) @path = path @ranges = ranges @options = options end def each ::File.open(path, "rb") do |file| ranges.each do |range| yield multipart_heading(range) if multipart? each_range_part(file, range) do |part| yield part end end yield "\r\n--#{MULTIPART_BOUNDARY}--\r\n" if multipart? end end def bytesize size = ranges.inject(0) do |sum, range| sum += multipart_heading(range).bytesize if multipart? sum += range.size end size += "\r\n--#{MULTIPART_BOUNDARY}--\r\n".bytesize if multipart? size end def close; end private def multipart? ranges.size > 1 end def multipart_heading(range) <<-EOF \r --#{MULTIPART_BOUNDARY}\r content-type: #{options[:mime_type]}\r content-range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r \r EOF end def each_range_part(file, range) file.seek(range.begin) remaining_len = range.end - range.begin + 1 while remaining_len > 0 part = file.read([8192, remaining_len].min) break unless part remaining_len -= part.length yield part end end end class Iterator < BaseIterator alias :to_path :path end private def fail(status, body, headers = {}) body += "\n" [ status, { CONTENT_TYPE => "text/plain", CONTENT_LENGTH => body.size.to_s, "x-cascade" => "pass" }.merge!(headers), [body] ] end # The MIME type for the contents of the file located at @path def mime_type(path, default_mime) Mime.mime_type(::File.extname(path), default_mime) end def filesize(path) # We check via File::size? whether this file provides size info # via stat (e.g. /proc files often don't), otherwise we have to # figure it out by reading the whole file into memory. ::File.size?(path) || ::File.read(path).bytesize end end end PK!ɸHOlib/rack/version.rbnu[# frozen_string_literal: true # Copyright (C) 2007-2019 Leah Neukirchen # # Rack is freely distributable under the terms of an MIT-style license. # See MIT-LICENSE or https://opensource.org/licenses/MIT. # The Rack main module, serving as a namespace for all core Rack # modules and classes. # # All modules meant for use in your application are autoloaded here, # so it should be enough just to require 'rack' in your code. module Rack # The Rack protocol version number implemented. VERSION = [1, 3].freeze deprecate_constant :VERSION VERSION_STRING = "1.3".freeze deprecate_constant :VERSION_STRING # The Rack protocol version number implemented. def self.version warn "Rack.version is deprecated and will be removed in Rack 3.1!", uplevel: 1 VERSION end RELEASE = "3.0.8" # Return the Rack release as a dotted string. def self.release RELEASE end end PK!3lib/rack/method_override.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'request' require_relative 'utils' module Rack class MethodOverride HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK] METHOD_OVERRIDE_PARAM_KEY = "_method" HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE" ALLOWED_METHODS = %w[POST] def initialize(app) @app = app end def call(env) if allowed_methods.include?(env[REQUEST_METHOD]) method = method_override(env) if HTTP_METHODS.include?(method) env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] = env[REQUEST_METHOD] env[REQUEST_METHOD] = method end end @app.call(env) end def method_override(env) req = Request.new(env) method = method_override_param(req) || env[HTTP_METHOD_OVERRIDE_HEADER] begin method.to_s.upcase rescue ArgumentError env[RACK_ERRORS].puts "Invalid string for method" end end private def allowed_methods ALLOWED_METHODS end def method_override_param(req) req.POST[METHOD_OVERRIDE_PARAM_KEY] if req.form_data? || req.parseable_data? rescue Utils::InvalidParameterError, Utils::ParameterTypeError, QueryParser::ParamsTooDeepError req.get_header(RACK_ERRORS).puts "Invalid or incomplete POST params" rescue EOFError req.get_header(RACK_ERRORS).puts "Bad request content body" end end end PK!4 4 lib/rack/chunked.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'utils' module Rack warn "Rack::Chunked is deprecated and will be removed in Rack 3.1", uplevel: 1 # Middleware that applies chunked transfer encoding to response bodies # when the response does not include a content-length header. # # This supports the trailer response header to allow the use of trailing # headers in the chunked encoding. However, using this requires you manually # specify a response body that supports a +trailers+ method. Example: # # [200, { 'trailer' => 'expires'}, ["Hello", "World"]] # # error raised # # body = ["Hello", "World"] # def body.trailers # { 'expires' => Time.now.to_s } # end # [200, { 'trailer' => 'expires'}, body] # # No exception raised class Chunked include Rack::Utils # A body wrapper that emits chunked responses. class Body TERM = "\r\n" TAIL = "0#{TERM}" # Store the response body to be chunked. def initialize(body) @body = body end # For each element yielded by the response body, yield # the element in chunked encoding. def each(&block) term = TERM @body.each do |chunk| size = chunk.bytesize next if size == 0 yield [size.to_s(16), term, chunk.b, term].join end yield TAIL yield_trailers(&block) yield term end # Close the response body if the response body supports it. def close @body.close if @body.respond_to?(:close) end private # Do nothing as this class does not support trailer headers. def yield_trailers end end # A body wrapper that emits chunked responses and also supports # sending Trailer headers. Note that the response body provided to # initialize must have a +trailers+ method that returns a hash # of trailer headers, and the rack response itself should have a # Trailer header listing the headers that the +trailers+ method # will return. class TrailerBody < Body private # Yield strings for each trailer header. def yield_trailers @body.trailers.each_pair do |k, v| yield "#{k}: #{v}\r\n" end end end def initialize(app) @app = app end # Whether the HTTP version supports chunked encoding (HTTP 1.1 does). def chunkable_version?(ver) case ver # pre-HTTP/1.0 (informally "HTTP/0.9") HTTP requests did not have # a version (nor response headers) when 'HTTP/1.0', nil, 'HTTP/0.9' false else true end end # If the rack app returns a response that should have a body, # but does not have content-length or transfer-encoding headers, # modify the response to use chunked transfer-encoding. def call(env) status, headers, body = response = @app.call(env) if chunkable_version?(env[SERVER_PROTOCOL]) && !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) && !headers[CONTENT_LENGTH] && !headers[TRANSFER_ENCODING] headers[TRANSFER_ENCODING] = 'chunked' if headers['trailer'] response[2] = TrailerBody.new(body) else response[2] = Body.new(body) end end response end end end PK!44lib/rack/events.rbnu[# frozen_string_literal: true require_relative 'body_proxy' require_relative 'request' require_relative 'response' module Rack ### This middleware provides hooks to certain places in the request / # response lifecycle. This is so that middleware that don't need to filter # the response data can safely leave it alone and not have to send messages # down the traditional "rack stack". # # The events are: # # * on_start(request, response) # # This event is sent at the start of the request, before the next # middleware in the chain is called. This method is called with a request # object, and a response object. Right now, the response object is always # nil, but in the future it may actually be a real response object. # # * on_commit(request, response) # # The response has been committed. The application has returned, but the # response has not been sent to the webserver yet. This method is always # called with a request object and the response object. The response # object is constructed from the rack triple that the application returned. # Changes may still be made to the response object at this point. # # * on_send(request, response) # # The webserver has started iterating over the response body and presumably # has started sending data over the wire. This method is always called with # a request object and the response object. The response object is # constructed from the rack triple that the application returned. Changes # SHOULD NOT be made to the response object as the webserver has already # started sending data. Any mutations will likely result in an exception. # # * on_finish(request, response) # # The webserver has closed the response, and all data has been written to # the response socket. The request and response object should both be # read-only at this point. The body MAY NOT be available on the response # object as it may have been flushed to the socket. # # * on_error(request, response, error) # # An exception has occurred in the application or an `on_commit` event. # This method will get the request, the response (if available) and the # exception that was raised. # # ## Order # # `on_start` is called on the handlers in the order that they were passed to # the constructor. `on_commit`, on_send`, `on_finish`, and `on_error` are # called in the reverse order. `on_finish` handlers are called inside an # `ensure` block, so they are guaranteed to be called even if something # raises an exception. If something raises an exception in a `on_finish` # method, then nothing is guaranteed. class Events module Abstract def on_start(req, res) end def on_commit(req, res) end def on_send(req, res) end def on_finish(req, res) end def on_error(req, res, e) end end class EventedBodyProxy < Rack::BodyProxy # :nodoc: attr_reader :request, :response def initialize(body, request, response, handlers, &block) super(body, &block) @request = request @response = response @handlers = handlers end def each @handlers.reverse_each { |handler| handler.on_send request, response } super end end class BufferedResponse < Rack::Response::Raw # :nodoc: attr_reader :body def initialize(status, headers, body) super(status, headers) @body = body end def to_a; [status, headers, body]; end end def initialize(app, handlers) @app = app @handlers = handlers end def call(env) request = make_request env on_start request, nil begin status, headers, body = @app.call request.env response = make_response status, headers, body on_commit request, response rescue StandardError => e on_error request, response, e on_finish request, response raise end body = EventedBodyProxy.new(body, request, response, @handlers) do on_finish request, response end [response.status, response.headers, body] end private def on_error(request, response, e) @handlers.reverse_each { |handler| handler.on_error request, response, e } end def on_commit(request, response) @handlers.reverse_each { |handler| handler.on_commit request, response } end def on_start(request, response) @handlers.each { |handler| handler.on_start request, nil } end def on_finish(request, response) @handlers.reverse_each { |handler| handler.on_finish request, response } end def make_request(env) Rack::Request.new env end def make_response(status, headers, body) BufferedResponse.new status, headers, body end end end PK!HSGbGblib/rack/request.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'utils' require_relative 'media_type' module Rack # Rack::Request provides a convenient interface to a Rack # environment. It is stateless, the environment +env+ passed to the # constructor will be directly modified. # # req = Rack::Request.new(env) # req.post? # req.params["data"] class Request class << self attr_accessor :ip_filter # The priority when checking forwarded headers. The default # is [:forwarded, :x_forwarded], which means, check the # +Forwarded+ header first, followed by the appropriate # X-Forwarded-* header. You can revert the priority by # reversing the priority, or remove checking of either # or both headers by removing elements from the array. # # This should be set as appropriate in your environment # based on what reverse proxies are in use. If you are not # using reverse proxies, you should probably use an empty # array. attr_accessor :forwarded_priority # The priority when checking either the X-Forwarded-Proto # or X-Forwarded-Scheme header for the forwarded protocol. # The default is [:proto, :scheme], to try the # X-Forwarded-Proto header before the # X-Forwarded-Scheme header. Rack 2 had behavior # similar to [:scheme, :proto]. You can remove either or # both of the entries in array to ignore that respective header. attr_accessor :x_forwarded_proto_priority end @forwarded_priority = [:forwarded, :x_forwarded] @x_forwarded_proto_priority = [:proto, :scheme] valid_ipv4_octet = /\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])/ trusted_proxies = Regexp.union( /\A127#{valid_ipv4_octet}{3}\z/, # localhost IPv4 range 127.x.x.x, per RFC-3330 /\A::1\z/, # localhost IPv6 ::1 /\Af[cd][0-9a-f]{2}(?::[0-9a-f]{0,4}){0,7}\z/i, # private IPv6 range fc00 .. fdff /\A10#{valid_ipv4_octet}{3}\z/, # private IPv4 range 10.x.x.x /\A172\.(1[6-9]|2[0-9]|3[01])#{valid_ipv4_octet}{2}\z/, # private IPv4 range 172.16.0.0 .. 172.31.255.255 /\A192\.168#{valid_ipv4_octet}{2}\z/, # private IPv4 range 192.168.x.x /\Alocalhost\z|\Aunix(\z|:)/i, # localhost hostname, and unix domain sockets ) self.ip_filter = lambda { |ip| trusted_proxies.match?(ip) } ALLOWED_SCHEMES = %w(https http wss ws).freeze def initialize(env) @env = env @params = nil end def params @params ||= super end def update_param(k, v) super @params = nil end def delete_param(k) v = super @params = nil v end module Env # The environment of the request. attr_reader :env def initialize(env) @env = env # This module is included at least in `ActionDispatch::Request` # The call to `super()` allows additional mixed-in initializers are called super() end # Predicate method to test to see if `name` has been set as request # specific data def has_header?(name) @env.key? name end # Get a request specific value for `name`. def get_header(name) @env[name] end # If a block is given, it yields to the block if the value hasn't been set # on the request. def fetch_header(name, &block) @env.fetch(name, &block) end # Loops through each key / value pair in the request specific data. def each_header(&block) @env.each(&block) end # Set a request specific value for `name` to `v` def set_header(name, v) @env[name] = v end # Add a header that may have multiple values. # # Example: # request.add_header 'Accept', 'image/png' # request.add_header 'Accept', '*/*' # # assert_equal 'image/png,*/*', request.get_header('Accept') # # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 def add_header(key, v) if v.nil? get_header key elsif has_header? key set_header key, "#{get_header key},#{v}" else set_header key, v end end # Delete a request specific value for `name`. def delete_header(name) @env.delete name end def initialize_copy(other) @env = other.env.dup end end module Helpers # The set of form-data media-types. Requests that do not indicate # one of the media types present in this list will not be eligible # for form-data / param parsing. FORM_DATA_MEDIA_TYPES = [ 'application/x-www-form-urlencoded', 'multipart/form-data' ] # The set of media-types. Requests that do not indicate # one of the media types present in this list will not be eligible # for param parsing like soap attachments or generic multiparts PARSEABLE_DATA_MEDIA_TYPES = [ 'multipart/related', 'multipart/mixed' ] # Default ports depending on scheme. Used to decide whether or not # to include the port in a generated URI. DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 } # The address of the client which connected to the proxy. HTTP_X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR' # The contents of the host/:authority header sent to the proxy. HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST' HTTP_FORWARDED = 'HTTP_FORWARDED' # The value of the scheme sent to the proxy. HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME' # The protocol used to connect to the proxy. HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO' # The port used to connect to the proxy. HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT' # Another way for specifying https scheme was used. HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL' def body; get_header(RACK_INPUT) end def script_name; get_header(SCRIPT_NAME).to_s end def script_name=(s); set_header(SCRIPT_NAME, s.to_s) end def path_info; get_header(PATH_INFO).to_s end def path_info=(s); set_header(PATH_INFO, s.to_s) end def request_method; get_header(REQUEST_METHOD) end def query_string; get_header(QUERY_STRING).to_s end def content_length; get_header('CONTENT_LENGTH') end def logger; get_header(RACK_LOGGER) end def user_agent; get_header('HTTP_USER_AGENT') end # the referer of the client def referer; get_header('HTTP_REFERER') end alias referrer referer def session fetch_header(RACK_SESSION) do |k| set_header RACK_SESSION, default_session end end def session_options fetch_header(RACK_SESSION_OPTIONS) do |k| set_header RACK_SESSION_OPTIONS, {} end end # Checks the HTTP request method (or verb) to see if it was of type DELETE def delete?; request_method == DELETE end # Checks the HTTP request method (or verb) to see if it was of type GET def get?; request_method == GET end # Checks the HTTP request method (or verb) to see if it was of type HEAD def head?; request_method == HEAD end # Checks the HTTP request method (or verb) to see if it was of type OPTIONS def options?; request_method == OPTIONS end # Checks the HTTP request method (or verb) to see if it was of type LINK def link?; request_method == LINK end # Checks the HTTP request method (or verb) to see if it was of type PATCH def patch?; request_method == PATCH end # Checks the HTTP request method (or verb) to see if it was of type POST def post?; request_method == POST end # Checks the HTTP request method (or verb) to see if it was of type PUT def put?; request_method == PUT end # Checks the HTTP request method (or verb) to see if it was of type TRACE def trace?; request_method == TRACE end # Checks the HTTP request method (or verb) to see if it was of type UNLINK def unlink?; request_method == UNLINK end def scheme if get_header(HTTPS) == 'on' 'https' elsif get_header(HTTP_X_FORWARDED_SSL) == 'on' 'https' elsif forwarded_scheme forwarded_scheme else get_header(RACK_URL_SCHEME) end end # The authority of the incoming request as defined by RFC3976. # https://tools.ietf.org/html/rfc3986#section-3.2 # # In HTTP/1, this is the `host` header. # In HTTP/2, this is the `:authority` pseudo-header. def authority forwarded_authority || host_authority || server_authority end # The authority as defined by the `SERVER_NAME` and `SERVER_PORT` # variables. def server_authority host = self.server_name port = self.server_port if host if port "#{host}:#{port}" else host end end end def server_name get_header(SERVER_NAME) end def server_port get_header(SERVER_PORT) end def cookies hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |key| set_header(key, {}) end string = get_header(HTTP_COOKIE) unless string == get_header(RACK_REQUEST_COOKIE_STRING) hash.replace Utils.parse_cookies_header(string) set_header(RACK_REQUEST_COOKIE_STRING, string) end hash end def content_type content_type = get_header('CONTENT_TYPE') content_type.nil? || content_type.empty? ? nil : content_type end def xhr? get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest" end # The `HTTP_HOST` header. def host_authority get_header(HTTP_HOST) end def host_with_port(authority = self.authority) host, _, port = split_authority(authority) if port == DEFAULT_PORTS[self.scheme] host else authority end end # Returns a formatted host, suitable for being used in a URI. def host split_authority(self.authority)[0] end # Returns an address suitable for being to resolve to an address. # In the case of a domain name or IPv4 address, the result is the same # as +host+. In the case of IPv6 or future address formats, the square # brackets are removed. def hostname split_authority(self.authority)[1] end def port if authority = self.authority _, _, port = split_authority(authority) end port || forwarded_port&.last || DEFAULT_PORTS[scheme] || server_port end def forwarded_for forwarded_priority.each do |type| case type when :forwarded if forwarded_for = get_http_forwarded(:for) return(forwarded_for.map! do |authority| split_authority(authority)[1] end) end when :x_forwarded if value = get_header(HTTP_X_FORWARDED_FOR) return(split_header(value).map do |authority| split_authority(wrap_ipv6(authority))[1] end) end end end nil end def forwarded_port forwarded_priority.each do |type| case type when :forwarded if forwarded = get_http_forwarded(:for) return(forwarded.map do |authority| split_authority(authority)[2] end.compact) end when :x_forwarded if value = get_header(HTTP_X_FORWARDED_PORT) return split_header(value).map(&:to_i) end end end nil end def forwarded_authority forwarded_priority.each do |type| case type when :forwarded if forwarded = get_http_forwarded(:host) return forwarded.last end when :x_forwarded if value = get_header(HTTP_X_FORWARDED_HOST) return wrap_ipv6(split_header(value).last) end end end nil end def ssl? scheme == 'https' || scheme == 'wss' end def ip remote_addresses = split_header(get_header('REMOTE_ADDR')) external_addresses = reject_trusted_ip_addresses(remote_addresses) unless external_addresses.empty? return external_addresses.last end if (forwarded_for = self.forwarded_for) && !forwarded_for.empty? # The forwarded for addresses are ordered: client, proxy1, proxy2. # So we reject all the trusted addresses (proxy*) and return the # last client. Or if we trust everyone, we just return the first # address. return reject_trusted_ip_addresses(forwarded_for).last || forwarded_for.first end # If all the addresses are trusted, and we aren't forwarded, just return # the first remote address, which represents the source of the request. remote_addresses.first end # The media type (type/subtype) portion of the CONTENT_TYPE header # without any media type parameters. e.g., when CONTENT_TYPE is # "text/plain;charset=utf-8", the media-type is "text/plain". # # For more information on the use of media types in HTTP, see: # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 def media_type MediaType.type(content_type) end # The media type parameters provided in CONTENT_TYPE as a Hash, or # an empty Hash if no CONTENT_TYPE or media-type parameters were # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8", # this method responds with the following Hash: # { 'charset' => 'utf-8' } def media_type_params MediaType.params(content_type) end # The character set of the request body if a "charset" media type # parameter was given, or nil if no "charset" was specified. Note # that, per RFC2616, text/* media types that specify no explicit # charset are to be considered ISO-8859-1. def content_charset media_type_params['charset'] end # Determine whether the request body contains form-data by checking # the request content-type for one of the media-types: # "application/x-www-form-urlencoded" or "multipart/form-data". The # list of form-data media types can be modified through the # +FORM_DATA_MEDIA_TYPES+ array. # # A request body is also assumed to contain form-data when no # content-type header is provided and the request_method is POST. def form_data? type = media_type meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD) (meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type) end # Determine whether the request body contains data by checking # the request media_type against registered parse-data media-types def parseable_data? PARSEABLE_DATA_MEDIA_TYPES.include?(media_type) end # Returns the data received in the query string. def GET if get_header(RACK_REQUEST_QUERY_STRING) == query_string get_header(RACK_REQUEST_QUERY_HASH) else query_hash = parse_query(query_string, '&') set_header(RACK_REQUEST_QUERY_STRING, query_string) set_header(RACK_REQUEST_QUERY_HASH, query_hash) end end # Returns the data received in the request body. # # This method support both application/x-www-form-urlencoded and # multipart/form-data. def POST if error = get_header(RACK_REQUEST_FORM_ERROR) raise error.class, error.message, cause: error.cause end begin rack_input = get_header(RACK_INPUT) # If the form hash was already memoized: if form_hash = get_header(RACK_REQUEST_FORM_HASH) # And it was memoized from the same input: if get_header(RACK_REQUEST_FORM_INPUT).equal?(rack_input) return form_hash end end # Otherwise, figure out how to parse the input: if rack_input.nil? set_header RACK_REQUEST_FORM_INPUT, nil set_header(RACK_REQUEST_FORM_HASH, {}) elsif form_data? || parseable_data? unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart) form_vars = get_header(RACK_INPUT).read # Fix for Safari Ajax postings that always append \0 # form_vars.sub!(/\0\z/, '') # performance replacement: form_vars.slice!(-1) if form_vars.end_with?("\0") set_header RACK_REQUEST_FORM_VARS, form_vars set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&') end set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT) get_header RACK_REQUEST_FORM_HASH else set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT) set_header(RACK_REQUEST_FORM_HASH, {}) end rescue => error set_header(RACK_REQUEST_FORM_ERROR, error) raise end end # The union of GET and POST data. # # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params. def params self.GET.merge(self.POST) end # Destructively update a parameter, whether it's in GET and/or POST. Returns nil. # # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET. # # env['rack.input'] is not touched. def update_param(k, v) found = false if self.GET.has_key?(k) found = true self.GET[k] = v end if self.POST.has_key?(k) found = true self.POST[k] = v end unless found self.GET[k] = v end end # Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter. # # If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works. # # env['rack.input'] is not touched. def delete_param(k) post_value, get_value = self.POST.delete(k), self.GET.delete(k) post_value || get_value end def base_url "#{scheme}://#{host_with_port}" end # Tries to return a remake of the original request URL as a string. def url base_url + fullpath end def path script_name + path_info end def fullpath query_string.empty? ? path : "#{path}?#{query_string}" end def accept_encoding parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING")) end def accept_language parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE")) end def trusted_proxy?(ip) Rack::Request.ip_filter.call(ip) end # shortcut for request.params[key] def [](key) warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead", uplevel: 1) params[key.to_s] end # shortcut for request.params[key] = value # # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params. def []=(key, value) warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead", uplevel: 1) params[key.to_s] = value end # like Hash#values_at def values_at(*keys) keys.map { |key| params[key] } end private def default_session; {}; end # Assist with compatibility when processing `X-Forwarded-For`. def wrap_ipv6(host) # Even thought IPv6 addresses should be wrapped in square brackets, # sometimes this is not done in various legacy/underspecified headers. # So we try to fix this situation for compatibility reasons. # Try to detect IPv6 addresses which aren't escaped yet: if !host.start_with?('[') && host.count(':') > 1 "[#{host}]" else host end end def parse_http_accept_header(header) header.to_s.split(",").each(&:strip!).map do |part| attribute, parameters = part.split(";", 2).each(&:strip!) quality = 1.0 if parameters and /\Aq=([\d.]+)/ =~ parameters quality = $1.to_f end [attribute, quality] end end # Get an array of values set in the RFC 7239 `Forwarded` request header. def get_http_forwarded(token) Utils.forwarded_values(get_header(HTTP_FORWARDED))&.[](token) end def query_parser Utils.default_query_parser end def parse_query(qs, d = '&') query_parser.parse_nested_query(qs, d) end def parse_multipart Rack::Multipart.extract_multipart(self, query_parser) end def split_header(value) value ? value.strip.split(/[,\s]+/) : [] end # ipv6 extracted from resolv stdlib, simplified # to remove numbered match group creation. ipv6 = Regexp.union( /(?:[0-9A-Fa-f]{1,4}:){7} [0-9A-Fa-f]{1,4}/x, /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? :: (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?/x, /(?:[0-9A-Fa-f]{1,4}:){6,6} \d+\.\d+\.\d+\.\d+/x, /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? :: (?:[0-9A-Fa-f]{1,4}:)* \d+\.\d+\.\d+\.\d+/x, /[Ff][Ee]80 (?::[0-9A-Fa-f]{1,4}){7} %[-0-9A-Za-z._~]+/x, /[Ff][Ee]80: (?: (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? :: (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? | :(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? )? :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+/x) AUTHORITY = / \A (? # Match IPv6 as a string of hex digits and colons in square brackets \[(?
#{ipv6})\] | # Match any other printable string (except square brackets) as a hostname (?
[[[:graph:]&&[^\[\]]]]*?) ) (:(?\d+))? \z /x private_constant :AUTHORITY def split_authority(authority) return [] if authority.nil? return [] unless match = AUTHORITY.match(authority) return match[:host], match[:address], match[:port]&.to_i end def reject_trusted_ip_addresses(ip_addresses) ip_addresses.reject { |ip| trusted_proxy?(ip) } end FORWARDED_SCHEME_HEADERS = { proto: HTTP_X_FORWARDED_PROTO, scheme: HTTP_X_FORWARDED_SCHEME }.freeze private_constant :FORWARDED_SCHEME_HEADERS def forwarded_scheme forwarded_priority.each do |type| case type when :forwarded if (forwarded_proto = get_http_forwarded(:proto)) && (scheme = allowed_scheme(forwarded_proto.last)) return scheme end when :x_forwarded x_forwarded_proto_priority.each do |x_type| if header = FORWARDED_SCHEME_HEADERS[x_type] split_header(get_header(header)).reverse_each do |scheme| if allowed_scheme(scheme) return scheme end end end end end end nil end def allowed_scheme(header) header if ALLOWED_SCHEMES.include?(header) end def forwarded_priority Request.forwarded_priority end def x_forwarded_proto_priority Request.x_forwarded_proto_priority end end include Env include Helpers end end # :nocov: require_relative 'multipart' unless defined?(Rack::Multipart) # :nocov: PK![Jlib/rack/deflater.rbnu[# frozen_string_literal: true require "zlib" require "time" # for Time.httpdate require_relative 'constants' require_relative 'utils' require_relative 'request' require_relative 'body_proxy' module Rack # This middleware enables content encoding of http responses, # usually for purposes of compression. # # Currently supported encodings: # # * gzip # * identity (no transformation) # # This middleware automatically detects when encoding is supported # and allowed. For example no encoding is made when a cache # directive of 'no-transform' is present, when the response status # code is one that doesn't allow an entity body, or when the body # is empty. # # Note that despite the name, Deflater does not support the +deflate+ # encoding. class Deflater # Creates Rack::Deflater middleware. Options: # # :if :: a lambda enabling / disabling deflation based on returned boolean value # (e.g use Rack::Deflater, :if => lambda { |*, body| sum=0; body.each { |i| sum += i.length }; sum > 512 }). # However, be aware that calling `body.each` inside the block will break cases where `body.each` is not idempotent, # such as when it is an +IO+ instance. # :include :: a list of content types that should be compressed. By default, all content types are compressed. # :sync :: determines if the stream is going to be flushed after every chunk. Flushing after every chunk reduces # latency for time-sensitive streaming applications, but hurts compression and throughput. # Defaults to +true+. def initialize(app, options = {}) @app = app @condition = options[:if] @compressible_types = options[:include] @sync = options.fetch(:sync, true) end def call(env) status, headers, body = response = @app.call(env) unless should_deflate?(env, status, headers, body) return response end request = Request.new(env) encoding = Utils.select_best_encoding(%w(gzip identity), request.accept_encoding) # Set the Vary HTTP header. vary = headers["vary"].to_s.split(",").map(&:strip) unless vary.include?("*") || vary.any?{|v| v.downcase == 'accept-encoding'} headers["vary"] = vary.push("Accept-Encoding").join(",") end case encoding when "gzip" headers['content-encoding'] = "gzip" headers.delete(CONTENT_LENGTH) mtime = headers["last-modified"] mtime = Time.httpdate(mtime).to_i if mtime response[2] = GzipStream.new(body, mtime, @sync) response when "identity" response else # when nil # Only possible encoding values here are 'gzip', 'identity', and nil message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found." bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) } [406, { CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s }, bp] end end # Body class used for gzip encoded responses. class GzipStream BUFFER_LENGTH = 128 * 1_024 # Initialize the gzip stream. Arguments: # body :: Response body to compress with gzip # mtime :: The modification time of the body, used to set the # modification time in the gzip header. # sync :: Whether to flush each gzip chunk as soon as it is ready. def initialize(body, mtime, sync) @body = body @mtime = mtime @sync = sync end # Yield gzip compressed strings to the given block. def each(&block) @writer = block gzip = ::Zlib::GzipWriter.new(self) gzip.mtime = @mtime if @mtime # @body.each is equivalent to @body.gets (slow) if @body.is_a? ::File # XXX: Should probably be ::IO while part = @body.read(BUFFER_LENGTH) gzip.write(part) gzip.flush if @sync end else @body.each { |part| # Skip empty strings, as they would result in no output, # and flushing empty parts would raise Zlib::BufError. next if part.empty? gzip.write(part) gzip.flush if @sync } end ensure gzip.finish end # Call the block passed to #each with the gzipped data. def write(data) @writer.call(data) end # Close the original body if possible. def close @body.close if @body.respond_to?(:close) end end private # Whether the body should be compressed. def should_deflate?(env, status, headers, body) # Skip compressing empty entity body responses and responses with # no-transform set. if Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) || /\bno-transform\b/.match?(headers[CACHE_CONTROL].to_s) || headers['content-encoding']&.!~(/\bidentity\b/) return false end # Skip if @compressible_types are given and does not include request's content type return false if @compressible_types && !(headers.has_key?(CONTENT_TYPE) && @compressible_types.include?(headers[CONTENT_TYPE][/[^;]*/])) # Skip if @condition lambda is given and evaluates to false return false if @condition && !@condition.call(env, status, headers, body) # No point in compressing empty body, also handles usage with # Rack::Sendfile. return false if headers[CONTENT_LENGTH] == '0' true end end end PK!;`fflib/rack/runtime.rbnu[# frozen_string_literal: true require_relative 'utils' module Rack # Sets an "x-runtime" response header, indicating the response # time of the request, in seconds # # You can put it right before the application to see the processing # time, or before all the other middlewares to include time for them, # too. class Runtime FORMAT_STRING = "%0.6f" # :nodoc: HEADER_NAME = "x-runtime" # :nodoc: def initialize(app, name = nil) @app = app @header_name = HEADER_NAME @header_name += "-#{name.to_s.downcase}" if name end def call(env) start_time = Utils.clock_time _, headers, _ = response = @app.call(env) request_time = Utils.clock_time - start_time unless headers.key?(@header_name) headers[@header_name] = FORMAT_STRING % request_time end response end end end PK!_llib/rack/null_logger.rbnu[# frozen_string_literal: true require_relative 'constants' module Rack class NullLogger def initialize(app) @app = app end def call(env) env[RACK_LOGGER] = self @app.call(env) end def info(progname = nil, &block); end def debug(progname = nil, &block); end def warn(progname = nil, &block); end def error(progname = nil, &block); end def fatal(progname = nil, &block); end def unknown(progname = nil, &block); end def info? ; end def debug? ; end def warn? ; end def error? ; end def fatal? ; end def debug! ; end def error! ; end def fatal! ; end def info! ; end def warn! ; end def level ; end def progname ; end def datetime_format ; end def formatter ; end def sev_threshold ; end def level=(level); end def progname=(progname); end def datetime_format=(datetime_format); end def formatter=(formatter); end def sev_threshold=(sev_threshold); end def close ; end def add(severity, message = nil, progname = nil, &block); end def log(severity, message = nil, progname = nil, &block); end def <<(msg); end def reopen(logdev = nil); end end end PK!dNNlib/rack/show_status.rbnu[# frozen_string_literal: true require 'erb' require_relative 'constants' require_relative 'utils' require_relative 'request' require_relative 'body_proxy' module Rack # Rack::ShowStatus catches all empty responses and replaces them # with a site explaining the error. # # Additional details can be put into rack.showstatus.detail # and will be shown as HTML. If such details exist, the error page # is always rendered, even if the reply was not empty. class ShowStatus def initialize(app) @app = app @template = ERB.new(TEMPLATE) end def call(env) status, headers, body = response = @app.call(env) empty = headers[CONTENT_LENGTH].to_i <= 0 # client or server error, or explicit message if (status.to_i >= 400 && empty) || env[RACK_SHOWSTATUS_DETAIL] # This double assignment is to prevent an "unused variable" warning. # Yes, it is dumb, but I don't like Ruby yelling at me. req = req = Rack::Request.new(env) message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s # This double assignment is to prevent an "unused variable" warning. # Yes, it is dumb, but I don't like Ruby yelling at me. detail = detail = env[RACK_SHOWSTATUS_DETAIL] || message html = @template.result(binding) size = html.bytesize response[2] = Rack::BodyProxy.new([html]) do body.close if body.respond_to?(:close) end headers[CONTENT_TYPE] = "text/html" headers[CONTENT_LENGTH] = size.to_s end response end def h(obj) # :nodoc: case obj when String Utils.escape_html(obj) else Utils.escape_html(obj.inspect) end end # :stopdoc: # adapted from Django # Copyright (c) Django Software Foundation and individual contributors. # Used under the modified BSD license: # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 TEMPLATE = <<'HTML' <%=h message %> at <%=h req.script_name + req.path_info %>

<%=h message %> (<%= status.to_i %>)

Request Method: <%=h req.request_method %>
Request URL: <%=h req.url %>

<%=h detail %>

You're seeing this error because you use Rack::ShowStatus.

HTML # :startdoc: end end PK!ӎ cTcTlib/rack/utils.rbnu[# -*- encoding: binary -*- # frozen_string_literal: true require 'uri' require 'fileutils' require 'set' require 'tempfile' require 'time' require_relative 'query_parser' require_relative 'mime' require_relative 'headers' require_relative 'constants' module Rack # Rack::Utils contains a grab-bag of useful methods for writing web # applications adopted from all kinds of Ruby libraries. module Utils ParameterTypeError = QueryParser::ParameterTypeError InvalidParameterError = QueryParser::InvalidParameterError ParamsTooDeepError = QueryParser::ParamsTooDeepError DEFAULT_SEP = QueryParser::DEFAULT_SEP COMMON_SEP = QueryParser::COMMON_SEP KeySpaceConstrainedParams = QueryParser::Params class << self attr_accessor :default_query_parser end # The default amount of nesting to allowed by hash parameters. # This helps prevent a rogue client from triggering a possible stack overflow # when parsing parameters. self.default_query_parser = QueryParser.make_default(32) module_function # URI escapes. (CGI style space to +) def escape(s) URI.encode_www_form_component(s) end # Like URI escaping, but with %20 instead of +. Strictly speaking this is # true URI escaping. def escape_path(s) ::URI::DEFAULT_PARSER.escape s end # Unescapes the **path** component of a URI. See Rack::Utils.unescape for # unescaping query parameters or form components. def unescape_path(s) ::URI::DEFAULT_PARSER.unescape s end # Unescapes a URI escaped string with +encoding+. +encoding+ will be the # target encoding of the string returned, and it defaults to UTF-8 def unescape(s, encoding = Encoding::UTF_8) URI.decode_www_form_component(s, encoding) end class << self attr_accessor :multipart_total_part_limit attr_accessor :multipart_file_limit # multipart_part_limit is the original name of multipart_file_limit, but # the limit only counts parts with filenames. alias multipart_part_limit multipart_file_limit alias multipart_part_limit= multipart_file_limit= end # The maximum number of file parts a request can contain. Accepting too # many parts can lead to the server running out of file handles. # Set to `0` for no limit. self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i # The maximum total number of parts a request can contain. Accepting too # many can lead to excessive memory use and parsing time. self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i def self.param_depth_limit default_query_parser.param_depth_limit end def self.param_depth_limit=(v) self.default_query_parser = self.default_query_parser.new_depth_limit(v) end def self.key_space_limit warn("`Rack::Utils.key_space_limit` is deprecated as this value no longer has an effect. It will be removed in Rack 3.1", uplevel: 1) 65536 end def self.key_space_limit=(v) warn("`Rack::Utils.key_space_limit=` is deprecated and no longer has an effect. It will be removed in Rack 3.1", uplevel: 1) end if defined?(Process::CLOCK_MONOTONIC) def clock_time Process.clock_gettime(Process::CLOCK_MONOTONIC) end else # :nocov: def clock_time Time.now.to_f end # :nocov: end def parse_query(qs, d = nil, &unescaper) Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper) end def parse_nested_query(qs, d = nil) Rack::Utils.default_query_parser.parse_nested_query(qs, d) end def build_query(params) params.map { |k, v| if v.class == Array build_query(v.map { |x| [k, x] }) else v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}" end }.join("&") end def build_nested_query(value, prefix = nil) case value when Array value.map { |v| build_nested_query(v, "#{prefix}[]") }.join("&") when Hash value.map { |k, v| build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k) }.delete_if(&:empty?).join('&') when nil escape(prefix) else raise ArgumentError, "value must be a Hash" if prefix.nil? "#{escape(prefix)}=#{escape(value)}" end end def q_values(q_value_header) q_value_header.to_s.split(/\s*,\s*/).map do |part| value, parameters = part.split(/\s*;\s*/, 2) quality = 1.0 if parameters && (md = /\Aq=([\d.]+)/.match(parameters)) quality = md[1].to_f end [value, quality] end end def forwarded_values(forwarded_header) return nil unless forwarded_header forwarded_header = forwarded_header.to_s.gsub("\n", ";") forwarded_header.split(/\s*;\s*/).each_with_object({}) do |field, values| field.split(/\s*,\s*/).each do |pair| return nil unless pair =~ /\A\s*(by|for|host|proto)\s*=\s*"?([^"]+)"?\s*\Z/i (values[$1.downcase.to_sym] ||= []) << $2 end end end module_function :forwarded_values # Return best accept value to use, based on the algorithm # in RFC 2616 Section 14. If there are multiple best # matches (same specificity and quality), the value returned # is arbitrary. def best_q_match(q_value_header, available_mimes) values = q_values(q_value_header) matches = values.map do |req_mime, quality| match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) } next unless match [match, quality] end.compact.sort_by do |match, quality| (match.split('/', 2).count('*') * -10) + quality end.last matches&.first end ESCAPE_HTML = { "&" => "&", "<" => "<", ">" => ">", "'" => "'", '"' => """, "/" => "/" } ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys) # Escape ampersands, brackets and quotes to their HTML/XML entities. def escape_html(string) string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] } end def select_best_encoding(available_encodings, accept_encoding) # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html expanded_accept_encoding = [] accept_encoding.each do |m, q| preference = available_encodings.index(m) || available_encodings.size if m == "*" (available_encodings - accept_encoding.map(&:first)).each do |m2| expanded_accept_encoding << [m2, q, preference] end else expanded_accept_encoding << [m, q, preference] end end encoding_candidates = expanded_accept_encoding .sort_by { |_, q, p| [-q, p] } .map!(&:first) unless encoding_candidates.include?("identity") encoding_candidates.push("identity") end expanded_accept_encoding.each do |m, q| encoding_candidates.delete(m) if q == 0.0 end (encoding_candidates & available_encodings)[0] end # :call-seq: # parse_cookies_header(value) -> hash # # Parse cookies from the provided header +value+ according to RFC6265. The # syntax for cookie headers only supports semicolons. Returns a map of # cookie +key+ to cookie +value+. # # parse_cookies_header('myname=myvalue; max-age=0') # # => {"myname"=>"myvalue", "max-age"=>"0"} # def parse_cookies_header(value) return {} unless value value.split(/; */n).each_with_object({}) do |cookie, cookies| next if cookie.empty? key, value = cookie.split('=', 2) cookies[key] = (unescape(value) rescue value) unless cookies.key?(key) end end def add_cookie_to_header(header, key, value) warn("add_cookie_to_header is deprecated and will be removed in Rack 3.1", uplevel: 1) case header when nil, '' return set_cookie_header(key, value) when String [header, set_cookie_header(key, value)] when Array header + [set_cookie_header(key, value)] else raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}" end end # :call-seq: # parse_cookies(env) -> hash # # Parse cookies from the provided request environment using # parse_cookies_header. Returns a map of cookie +key+ to cookie +value+. # # parse_cookies({'HTTP_COOKIE' => 'myname=myvalue'}) # # => {'myname' => 'myvalue'} # def parse_cookies(env) parse_cookies_header env[HTTP_COOKIE] end # :call-seq: # set_cookie_header(key, value) -> encoded string # # Generate an encoded string using the provided +key+ and +value+ suitable # for the +set-cookie+ header according to RFC6265. The +value+ may be an # instance of either +String+ or +Hash+. # # If the cookie +value+ is an instance of +Hash+, it considers the following # cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance # of +Time+), +secure+, +http_only+, +same_site+ and +value+. For more # details about the interpretation of these fields, consult # [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2). # # An extra cookie attribute +escape_key+ can be provided to control whether # or not the cookie key is URL encoded. If explicitly set to +false+, the # cookie key name will not be url encoded (escaped). The default is +true+. # # set_cookie_header("myname", "myvalue") # # => "myname=myvalue" # # set_cookie_header("myname", {value: "myvalue", max_age: 10}) # # => "myname=myvalue; max-age=10" # def set_cookie_header(key, value) case value when Hash key = escape(key) unless value[:escape_key] == false domain = "; domain=#{value[:domain]}" if value[:domain] path = "; path=#{value[:path]}" if value[:path] max_age = "; max-age=#{value[:max_age]}" if value[:max_age] expires = "; expires=#{value[:expires].httpdate}" if value[:expires] secure = "; secure" if value[:secure] httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only]) same_site = case value[:same_site] when false, nil nil when :none, 'None', :None '; SameSite=None' when :lax, 'Lax', :Lax '; SameSite=Lax' when true, :strict, 'Strict', :Strict '; SameSite=Strict' else raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}" end value = value[:value] else key = escape(key) end value = [value] unless Array === value return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}" end # :call-seq: # set_cookie_header!(headers, key, value) -> header value # # Append a cookie in the specified headers with the given cookie +key+ and # +value+ using set_cookie_header. # # If the headers already contains a +set-cookie+ key, it will be converted # to an +Array+ if not already, and appended to. def set_cookie_header!(headers, key, value) if header = headers[SET_COOKIE] if header.is_a?(Array) header << set_cookie_header(key, value) else headers[SET_COOKIE] = [header, set_cookie_header(key, value)] end else headers[SET_COOKIE] = set_cookie_header(key, value) end end # :call-seq: # delete_set_cookie_header(key, value = {}) -> encoded string # # Generate an encoded string based on the given +key+ and +value+ using # set_cookie_header for the purpose of causing the specified cookie to be # deleted. The +value+ may be an instance of +Hash+ and can include # attributes as outlined by set_cookie_header. The encoded cookie will have # a +max_age+ of 0 seconds, an +expires+ date in the past and an empty # +value+. When used with the +set-cookie+ header, it will cause the client # to *remove* any matching cookie. # # delete_set_cookie_header("myname") # # => "myname=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" # def delete_set_cookie_header(key, value = {}) set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: '')) end def make_delete_cookie_header(header, key, value) warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1) delete_set_cookie_header!(header, key, value) end def delete_cookie_header!(headers, key, value = {}) headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value) return nil end def add_remove_cookie_to_header(header, key, value = {}) warn("add_remove_cookie_to_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1) delete_set_cookie_header!(header, key, value) end # :call-seq: # delete_set_cookie_header!(header, key, value = {}) -> header value # # Set an expired cookie in the specified headers with the given cookie # +key+ and +value+ using delete_set_cookie_header. This causes # the client to immediately delete the specified cookie. # # delete_set_cookie_header!(nil, "mycookie") # # => "mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" # # If the header is non-nil, it will be modified in place. # # header = [] # delete_set_cookie_header!(header, "mycookie") # # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"] # header # # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"] # def delete_set_cookie_header!(header, key, value = {}) if header header = Array(header) header << delete_set_cookie_header(key, value) else header = delete_set_cookie_header(key, value) end return header end def rfc2822(time) time.rfc2822 end # Parses the "Range:" header, if present, into an array of Range objects. # Returns nil if the header is missing or syntactically invalid. # Returns an empty array if none of the ranges are satisfiable. def byte_ranges(env, size) get_byte_ranges env['HTTP_RANGE'], size end def get_byte_ranges(http_range, size) # See return nil unless http_range && http_range =~ /bytes=([^;]+)/ ranges = [] $1.split(/,\s*/).each do |range_spec| return nil unless range_spec.include?('-') range = range_spec.split('-') r0, r1 = range[0], range[1] if r0.nil? || r0.empty? return nil if r1.nil? # suffix-byte-range-spec, represents trailing suffix of file r0 = size - r1.to_i r0 = 0 if r0 < 0 r1 = size - 1 else r0 = r0.to_i if r1.nil? r1 = size - 1 else r1 = r1.to_i return nil if r1 < r0 # backwards range is syntactically invalid r1 = size - 1 if r1 >= size end end ranges << (r0..r1) if r0 <= r1 end ranges end # :nocov: if defined?(OpenSSL.fixed_length_secure_compare) # Constant time string comparison. # # NOTE: the values compared should be of fixed length, such as strings # that have already been processed by HMAC. This should not be used # on variable length plaintext strings because it could leak length info # via timing attacks. def secure_compare(a, b) return false unless a.bytesize == b.bytesize OpenSSL.fixed_length_secure_compare(a, b) end # :nocov: else def secure_compare(a, b) return false unless a.bytesize == b.bytesize l = a.unpack("C*") r, i = 0, -1 b.each_byte { |v| r |= v ^ l[i += 1] } r == 0 end end # Context allows the use of a compatible middleware at different points # in a request handling stack. A compatible middleware must define # #context which should take the arguments env and app. The first of which # would be the request environment. The second of which would be the rack # application that the request would be forwarded to. class Context attr_reader :for, :app def initialize(app_f, app_r) raise 'running context does not respond to #context' unless app_f.respond_to? :context @for, @app = app_f, app_r end def call(env) @for.context(env, @app) end def recontext(app) self.class.new(@for, app) end def context(env, app = @app) recontext(app).call(env) end end # A wrapper around Headers # header when set. # # @api private class HeaderHash < Hash # :nodoc: def self.[](headers) warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1 if headers.is_a?(Headers) && !headers.frozen? return headers end new_headers = Headers.new headers.each{|k,v| new_headers[k] = v} new_headers end def self.new(hash = {}) warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1 headers = Headers.new hash.each{|k,v| headers[k] = v} headers end def self.allocate raise TypeError, "cannot allocate HeaderHash" end end # Every standard HTTP code mapped to the appropriate message. # Generated with: # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \ # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \ # puts "#{m[1]} => \x27#{m[2].strip}\x27,"' HTTP_STATUS_CODES = { 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 103 => 'Early Hints', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', 208 => 'Already Reported', 226 => 'IM Used', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => '(Unused)', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Payload Too Large', 414 => 'URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', 417 => 'Expectation Failed', 421 => 'Misdirected Request', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Too Early', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable for Legal Reasons', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 509 => 'Bandwidth Limit Exceeded', 510 => 'Not Extended', 511 => 'Network Authentication Required' } # Responses with HTTP status codes that should not have an entity body STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])] SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message| [message.downcase.gsub(/\s|-|'/, '_').to_sym, code] }.flatten] def status_code(status) if status.is_a?(Symbol) SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" } else status.to_i end end PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact) def clean_path_info(path_info) parts = path_info.split PATH_SEPS clean = [] parts.each do |part| next if part.empty? || part == '.' part == '..' ? clean.pop : clean << part end clean_path = clean.join(::File::SEPARATOR) clean_path.prepend("/") if parts.empty? || parts.first.empty? clean_path end NULL_BYTE = "\0" def valid_path?(path) path.valid_encoding? && !path.include?(NULL_BYTE) end end end PK!ѝo  lib/rack/head.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'body_proxy' module Rack # Rack::Head returns an empty body for all HEAD requests. It leaves # all other requests unchanged. class Head def initialize(app) @app = app end def call(env) _, _, body = response = @app.call(env) if env[REQUEST_METHOD] == HEAD response[2] = Rack::BodyProxy.new([]) do body.close if body.respond_to? :close end end response end end end PK!/{{lib/rack/mock_request.rbnu[# frozen_string_literal: true require 'uri' require 'stringio' require_relative 'constants' require_relative 'mock_response' module Rack # Rack::MockRequest helps testing your Rack application without # actually using HTTP. # # After performing a request on a URL with get/post/put/patch/delete, it # returns a MockResponse with useful helper methods for effective # testing. # # You can pass a hash with additional configuration to the # get/post/put/patch/delete. # :input:: A String or IO-like to be used as rack.input. # :fatal:: Raise a FatalWarning if the app writes to rack.errors. # :lint:: If true, wrap the application in a Rack::Lint. class MockRequest class FatalWarning < RuntimeError end class FatalWarner def puts(warning) raise FatalWarning, warning end def write(warning) raise FatalWarning, warning end def flush end def string "" end end DEFAULT_ENV = { RACK_INPUT => StringIO.new, RACK_ERRORS => StringIO.new, }.freeze def initialize(app) @app = app end # Make a GET request and return a MockResponse. See #request. def get(uri, opts = {}) request(GET, uri, opts) end # Make a POST request and return a MockResponse. See #request. def post(uri, opts = {}) request(POST, uri, opts) end # Make a PUT request and return a MockResponse. See #request. def put(uri, opts = {}) request(PUT, uri, opts) end # Make a PATCH request and return a MockResponse. See #request. def patch(uri, opts = {}) request(PATCH, uri, opts) end # Make a DELETE request and return a MockResponse. See #request. def delete(uri, opts = {}) request(DELETE, uri, opts) end # Make a HEAD request and return a MockResponse. See #request. def head(uri, opts = {}) request(HEAD, uri, opts) end # Make an OPTIONS request and return a MockResponse. See #request. def options(uri, opts = {}) request(OPTIONS, uri, opts) end # Make a request using the given request method for the given # uri to the rack application and return a MockResponse. # Options given are passed to MockRequest.env_for. def request(method = GET, uri = "", opts = {}) env = self.class.env_for(uri, opts.merge(method: method)) if opts[:lint] app = Rack::Lint.new(@app) else app = @app end errors = env[RACK_ERRORS] status, headers, body = app.call(env) MockResponse.new(status, headers, body, errors) ensure body.close if body.respond_to?(:close) end # For historical reasons, we're pinning to RFC 2396. # URI::Parser = URI::RFC2396_Parser def self.parse_uri_rfc2396(uri) @parser ||= URI::Parser.new @parser.parse(uri) end # Return the Rack environment used for a request to +uri+. # All options that are strings are added to the returned environment. # Options: # :fatal :: Whether to raise an exception if request outputs to rack.errors # :input :: The rack.input to set # :http_version :: The SERVER_PROTOCOL to set # :method :: The HTTP request method to use # :params :: The params to use # :script_name :: The SCRIPT_NAME to set def self.env_for(uri = "", opts = {}) uri = parse_uri_rfc2396(uri) uri.path = "/#{uri.path}" unless uri.path[0] == ?/ env = DEFAULT_ENV.dup env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : GET).b env[SERVER_NAME] = (uri.host || "example.org").b env[SERVER_PORT] = (uri.port ? uri.port.to_s : "80").b env[SERVER_PROTOCOL] = opts[:http_version] || 'HTTP/1.1' env[QUERY_STRING] = (uri.query.to_s).b env[PATH_INFO] = (uri.path).b env[RACK_URL_SCHEME] = (uri.scheme || "http").b env[HTTPS] = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b env[SCRIPT_NAME] = opts[:script_name] || "" if opts[:fatal] env[RACK_ERRORS] = FatalWarner.new else env[RACK_ERRORS] = StringIO.new end if params = opts[:params] if env[REQUEST_METHOD] == GET params = Utils.parse_nested_query(params) if params.is_a?(String) params.update(Utils.parse_nested_query(env[QUERY_STRING])) env[QUERY_STRING] = Utils.build_nested_query(params) elsif !opts.has_key?(:input) opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded" if params.is_a?(Hash) if data = Rack::Multipart.build_multipart(params) opts[:input] = data opts["CONTENT_LENGTH"] ||= data.length.to_s opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}" else opts[:input] = Utils.build_nested_query(params) end else opts[:input] = params end end end opts[:input] ||= String.new if String === opts[:input] rack_input = StringIO.new(opts[:input]) else rack_input = opts[:input] end rack_input.set_encoding(Encoding::BINARY) env[RACK_INPUT] = rack_input env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size) opts.each { |field, value| env[field] = value if String === field } env end end end PK!Alib/rack/directory.rbnu[# frozen_string_literal: true require 'time' require_relative 'constants' require_relative 'utils' require_relative 'head' require_relative 'mime' require_relative 'files' module Rack # Rack::Directory serves entries below the +root+ given, according to the # path info of the Rack request. If a directory is found, the file's contents # will be presented in an html based index. If a file is found, the env will # be passed to the specified +app+. # # If +app+ is not specified, a Rack::Files of the same +root+ will be used. class Directory DIR_FILE = "%s%s%s%s\n" DIR_PAGE_HEADER = <<-PAGE %s

%s


PAGE DIR_PAGE_FOOTER = <<-PAGE
Name Size Type Last Modified

PAGE # Body class for directory entries, showing an index page with links # to each file. class DirectoryBody < Struct.new(:root, :path, :files) # Yield strings for each part of the directory entry def each show_path = Utils.escape_html(path.sub(/^#{root}/, '')) yield(DIR_PAGE_HEADER % [ show_path, show_path ]) unless path.chomp('/') == root yield(DIR_FILE % DIR_FILE_escape(files.call('..'))) end Dir.foreach(path) do |basename| next if basename.start_with?('.') next unless f = files.call(basename) yield(DIR_FILE % DIR_FILE_escape(f)) end yield(DIR_PAGE_FOOTER) end private # Escape each element in the array of html strings. def DIR_FILE_escape(htmls) htmls.map { |e| Utils.escape_html(e) } end end # The root of the directory hierarchy. Only requests for files and # directories inside of the root directory are supported. attr_reader :root # Set the root directory and application for serving files. def initialize(root, app = nil) @root = ::File.expand_path(root) @app = app || Files.new(@root) @head = Head.new(method(:get)) end def call(env) # strip body if this is a HEAD call @head.call env end # Internals of request handling. Similar to call but does # not remove body for HEAD requests. def get(env) script_name = env[SCRIPT_NAME] path_info = Utils.unescape_path(env[PATH_INFO]) if client_error_response = check_bad_request(path_info) || check_forbidden(path_info) client_error_response else path = ::File.join(@root, path_info) list_path(env, path, path_info, script_name) end end # Rack response to use for requests with invalid paths, or nil if path is valid. def check_bad_request(path_info) return if Utils.valid_path?(path_info) body = "Bad Request\n" [400, { CONTENT_TYPE => "text/plain", CONTENT_LENGTH => body.bytesize.to_s, "x-cascade" => "pass" }, [body]] end # Rack response to use for requests with paths outside the root, or nil if path is inside the root. def check_forbidden(path_info) return unless path_info.include? ".." return if ::File.expand_path(::File.join(@root, path_info)).start_with?(@root) body = "Forbidden\n" [403, { CONTENT_TYPE => "text/plain", CONTENT_LENGTH => body.bytesize.to_s, "x-cascade" => "pass" }, [body]] end # Rack response to use for directories under the root. def list_directory(path_info, path, script_name) url_head = (script_name.split('/') + path_info.split('/')).map do |part| Utils.escape_path part end # Globbing not safe as path could contain glob metacharacters body = DirectoryBody.new(@root, path, ->(basename) do stat = stat(::File.join(path, basename)) next unless stat url = ::File.join(*url_head + [Utils.escape_path(basename)]) mtime = stat.mtime.httpdate if stat.directory? type = 'directory' size = '-' url << '/' if basename == '..' basename = 'Parent Directory' else basename << '/' end else type = Mime.mime_type(::File.extname(basename)) size = filesize_format(stat.size) end [ url, basename, size, type, mtime ] end) [ 200, { CONTENT_TYPE => 'text/html; charset=utf-8' }, body ] end # File::Stat for the given path, but return nil for missing/bad entries. def stat(path) ::File.stat(path) rescue Errno::ENOENT, Errno::ELOOP return nil end # Rack response to use for files and directories under the root. # Unreadable and non-file, non-directory entries will get a 404 response. def list_path(env, path, path_info, script_name) if (stat = stat(path)) && stat.readable? return @app.call(env) if stat.file? return list_directory(path_info, path, script_name) if stat.directory? end entity_not_found(path_info) end # Rack response to use for unreadable and non-file, non-directory entries. def entity_not_found(path_info) body = "Entity not found: #{path_info}\n" [404, { CONTENT_TYPE => "text/plain", CONTENT_LENGTH => body.bytesize.to_s, "x-cascade" => "pass" }, [body]] end # Stolen from Ramaze FILESIZE_FORMAT = [ ['%.1fT', 1 << 40], ['%.1fG', 1 << 30], ['%.1fM', 1 << 20], ['%.1fK', 1 << 10], ] # Provide human readable file sizes def filesize_format(int) FILESIZE_FORMAT.each do |format, size| return format % (int.to_f / size) if int >= size end "#{int}B" end end end PK!Kbx  lib/rack/reloader.rbnu[# frozen_string_literal: true # Copyright (C) 2009-2018 Michael Fellinger # Rack::Reloader is subject to the terms of an MIT-style license. # See MIT-LICENSE or https://opensource.org/licenses/MIT. require 'pathname' module Rack # High performant source reloader # # This class acts as Rack middleware. # # What makes it especially suited for use in a production environment is that # any file will only be checked once and there will only be made one system # call stat(2). # # Please note that this will not reload files in the background, it does so # only when actively called. # # It is performing a check/reload cycle at the start of every request, but # also respects a cool down time, during which nothing will be done. class Reloader def initialize(app, cooldown = 10, backend = Stat) @app = app @cooldown = cooldown @last = (Time.now - cooldown) @cache = {} @mtimes = {} @reload_mutex = Mutex.new extend backend end def call(env) if @cooldown and Time.now > @last + @cooldown if Thread.list.size > 1 @reload_mutex.synchronize{ reload! } else reload! end @last = Time.now end @app.call(env) end def reload!(stderr = $stderr) rotation do |file, mtime| previous_mtime = @mtimes[file] ||= mtime safe_load(file, mtime, stderr) if mtime > previous_mtime end end # A safe Kernel::load, issuing the hooks depending on the results def safe_load(file, mtime, stderr = $stderr) load(file) stderr.puts "#{self.class}: reloaded `#{file}'" file rescue LoadError, SyntaxError => ex stderr.puts ex ensure @mtimes[file] = mtime end module Stat def rotation files = [$0, *$LOADED_FEATURES].uniq paths = ['./', *$LOAD_PATH].uniq files.map{|file| next if /\.(so|bundle)$/.match?(file) # cannot reload compiled files found, stat = figure_path(file, paths) next unless found && stat && mtime = stat.mtime @cache[file] = found yield(found, mtime) }.compact end # Takes a relative or absolute +file+ name, a couple possible +paths+ that # the +file+ might reside in. Returns the full path and File::Stat for the # path. def figure_path(file, paths) found = @cache[file] found = file if !found and Pathname.new(file).absolute? found, stat = safe_stat(found) return found, stat if found paths.find do |possible_path| path = ::File.join(possible_path, file) found, stat = safe_stat(path) return ::File.expand_path(found), stat if found end return false, false end def safe_stat(file) return unless file stat = ::File.stat(file) return file, stat if stat.file? rescue Errno::ENOENT, Errno::ENOTDIR, Errno::ESRCH @cache.delete(file) and false end end end end PK!VMVwwlib/rack/etag.rbnu[# frozen_string_literal: true require 'digest/sha2' require_relative 'constants' require_relative 'utils' module Rack # Automatically sets the etag header on all String bodies. # # The etag header is skipped if etag or last-modified headers are sent or if # a sendfile body (body.responds_to :to_path) is given (since such cases # should be handled by apache/nginx). # # On initialization, you can pass two parameters: a cache-control directive # used when etag is absent and a directive when it is present. The first # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate" class ETag ETAG_STRING = Rack::ETAG DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL) @app = app @cache_control = cache_control @no_cache_control = no_cache_control end def call(env) status, headers, body = response = @app.call(env) if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers) body = body.to_ary digest = digest_body(body) headers[ETAG_STRING] = %(W/"#{digest}") if digest end unless headers[CACHE_CONTROL] if digest headers[CACHE_CONTROL] = @cache_control if @cache_control else headers[CACHE_CONTROL] = @no_cache_control if @no_cache_control end end response end private def etag_status?(status) status == 200 || status == 201 end def skip_caching?(headers) headers.key?(ETAG_STRING) || headers.key?('last-modified') end def digest_body(body) digest = nil body.each do |part| (digest ||= Digest::SHA256.new) << part unless part.empty? end digest && digest.hexdigest.byteslice(0,32) end end end PK!ю lib/rack/static.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'files' require_relative 'mime' module Rack # The Rack::Static middleware intercepts requests for static files # (javascript files, images, stylesheets, etc) based on the url prefixes or # route mappings passed in the options, and serves them using a Rack::Files # object. This allows a Rack stack to serve both static and dynamic content. # # Examples: # # Serve all requests beginning with /media from the "media" folder located # in the current directory (ie media/*): # # use Rack::Static, :urls => ["/media"] # # Same as previous, but instead of returning 404 for missing files under # /media, call the next middleware: # # use Rack::Static, :urls => ["/media"], :cascade => true # # Serve all requests beginning with /css or /images from the folder "public" # in the current directory (ie public/css/* and public/images/*): # # use Rack::Static, :urls => ["/css", "/images"], :root => "public" # # Serve all requests to / with "index.html" from the folder "public" in the # current directory (ie public/index.html): # # use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public' # # Serve all requests normally from the folder "public" in the current # directory but uses index.html as default route for "/" # # use Rack::Static, :urls => [""], :root => 'public', :index => # 'index.html' # # Set custom HTTP Headers for based on rules: # # use Rack::Static, :root => 'public', # :header_rules => [ # [rule, {header_field => content, header_field => content}], # [rule, {header_field => content}] # ] # # Rules for selecting files: # # 1) All files # Provide the :all symbol # :all => Matches every file # # 2) Folders # Provide the folder path as a string # '/folder' or '/folder/subfolder' => Matches files in a certain folder # # 3) File Extensions # Provide the file extensions as an array # ['css', 'js'] or %w(css js) => Matches files ending in .css or .js # # 4) Regular Expressions / Regexp # Provide a regular expression # %r{\.(?:css|js)\z} => Matches files ending in .css or .js # /\.(?:eot|ttf|otf|woff2|woff|svg)\z/ => Matches files ending in # the most common web font formats (.eot, .ttf, .otf, .woff2, .woff, .svg) # Note: This Regexp is available as a shortcut, using the :fonts rule # # 5) Font Shortcut # Provide the :fonts symbol # :fonts => Uses the Regexp rule stated right above to match all common web font endings # # Rule Ordering: # Rules are applied in the order that they are provided. # List rather general rules above special ones. # # Complete example use case including HTTP header rules: # # use Rack::Static, :root => 'public', # :header_rules => [ # # Cache all static files in public caches (e.g. Rack::Cache) # # as well as in the browser # [:all, {'cache-control' => 'public, max-age=31536000'}], # # # Provide web fonts with cross-origin access-control-headers # # Firefox requires this when serving assets using a Content Delivery Network # [:fonts, {'access-control-allow-origin' => '*'}] # ] # class Static def initialize(app, options = {}) @app = app @urls = options[:urls] || ["/favicon.ico"] @index = options[:index] @gzip = options[:gzip] @cascade = options[:cascade] root = options[:root] || Dir.pwd # HTTP Headers @header_rules = options[:header_rules] || [] # Allow for legacy :cache_control option while prioritizing global header_rules setting @header_rules.unshift([:all, { CACHE_CONTROL => options[:cache_control] }]) if options[:cache_control] @file_server = Rack::Files.new(root) end def add_index_root?(path) @index && route_file(path) && path.end_with?('/') end def overwrite_file_path(path) @urls.kind_of?(Hash) && @urls.key?(path) || add_index_root?(path) end def route_file(path) @urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 } end def can_serve(path) route_file(path) || overwrite_file_path(path) end def call(env) path = env[PATH_INFO] if can_serve(path) if overwrite_file_path(path) env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path]) elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING']) path = env[PATH_INFO] env[PATH_INFO] += '.gz' response = @file_server.call(env) env[PATH_INFO] = path if response[0] == 404 response = nil elsif response[0] == 304 # Do nothing, leave headers as is else response[1][CONTENT_TYPE] = Mime.mime_type(::File.extname(path), 'text/plain') response[1]['content-encoding'] = 'gzip' end end path = env[PATH_INFO] response ||= @file_server.call(env) if @cascade && response[0] == 404 return @app.call(env) end headers = response[1] applicable_rules(path).each do |rule, new_headers| new_headers.each { |field, content| headers[field] = content } end response else @app.call(env) end end # Convert HTTP header rules to HTTP headers def applicable_rules(path) @header_rules.find_all do |rule, new_headers| case rule when :all true when :fonts /\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path) when String path = ::Rack::Utils.unescape(path) path.start_with?(rule) || path.start_with?('/' + rule) when Array /\.(#{rule.join('|')})\z/.match?(path) when Regexp rule.match?(path) else false end end end end end PK!l lib/rack/multipart/generator.rbnu[# frozen_string_literal: true require_relative 'uploaded_file' module Rack module Multipart class Generator def initialize(params, first = true) @params, @first = params, first if @first && !@params.is_a?(Hash) raise ArgumentError, "value must be a Hash" end end def dump return nil if @first && !multipart? return flattened_params unless @first flattened_params.map do |name, file| if file.respond_to?(:original_filename) if file.path ::File.open(file.path, 'rb') do |f| f.set_encoding(Encoding::BINARY) content_for_tempfile(f, file, name) end else content_for_tempfile(file, file, name) end else content_for_other(file, name) end end.join << "--#{MULTIPART_BOUNDARY}--\r" end private def multipart? query = lambda { |value| case value when Array value.any?(&query) when Hash value.values.any?(&query) when Rack::Multipart::UploadedFile true end } @params.values.any?(&query) end def flattened_params @flattened_params ||= begin h = Hash.new @params.each do |key, value| k = @first ? key.to_s : "[#{key}]" case value when Array value.map { |v| Multipart.build_multipart(v, false).each { |subkey, subvalue| h["#{k}[]#{subkey}"] = subvalue } } when Hash Multipart.build_multipart(value, false).each { |subkey, subvalue| h[k + subkey] = subvalue } else h[k] = value end end h end end def content_for_tempfile(io, file, name) length = ::File.stat(file.path).size if file.path filename = "; filename=\"#{Utils.escape_path(file.original_filename)}\"" <<-EOF --#{MULTIPART_BOUNDARY}\r content-disposition: form-data; name="#{name}"#{filename}\r content-type: #{file.content_type}\r #{"content-length: #{length}\r\n" if length}\r #{io.read}\r EOF end def content_for_other(file, name) <<-EOF --#{MULTIPART_BOUNDARY}\r content-disposition: form-data; name="#{name}"\r \r #{file}\r EOF end end end end PK!N$$#lib/rack/multipart/uploaded_file.rbnu[# frozen_string_literal: true require 'tempfile' require 'fileutils' module Rack module Multipart class UploadedFile # The filename, *not* including the path, of the "uploaded" file attr_reader :original_filename # The content type of the "uploaded" file attr_accessor :content_type def initialize(filepath = nil, ct = "text/plain", bin = false, path: filepath, content_type: ct, binary: bin, filename: nil, io: nil) if io @tempfile = io @original_filename = filename else raise "#{path} file does not exist" unless ::File.exist?(path) @original_filename = filename || ::File.basename(path) @tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY) @tempfile.binmode if binary FileUtils.copy_file(path, @tempfile.path) end @content_type = content_type end def path @tempfile.path if @tempfile.respond_to?(:path) end alias_method :local_path, :path def respond_to?(*args) super or @tempfile.respond_to?(*args) end def method_missing(method_name, *args, &block) #:nodoc: @tempfile.__send__(method_name, *args, &block) end end end end PK!&44lib/rack/multipart/parser.rbnu[# frozen_string_literal: true require 'strscan' require_relative '../utils' module Rack module Multipart class MultipartPartLimitError < Errno::EMFILE; end class MultipartTotalPartLimitError < StandardError; end # Use specific error class when parsing multipart request # that ends early. class EmptyContentError < ::EOFError; end # Base class for multipart exceptions that do not subclass from # other exception classes for backwards compatibility. class Error < StandardError; end EOL = "\r\n" MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/ CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/ BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni # Updated definitions from RFC 2231 ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]} ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/ SECTION = /\*[0-9]+/ REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/ REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/ EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/ EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/ EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/ EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/ EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/ EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/ EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/ DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/ RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i class Parser BUFSIZE = 1_048_576 TEXT_PLAIN = "text/plain" TEMPFILE_FACTORY = lambda { |filename, content_type| Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))]) } class BoundedIO # :nodoc: def initialize(io, content_length) @io = io @content_length = content_length @cursor = 0 end def read(size, outbuf = nil) return if @cursor >= @content_length left = @content_length - @cursor str = if left < size @io.read left, outbuf else @io.read size, outbuf end if str @cursor += str.bytesize else # Raise an error for mismatching content-length and actual contents raise EOFError, "bad content body" end str end end MultipartInfo = Struct.new :params, :tmp_files EMPTY = MultipartInfo.new(nil, []) def self.parse_boundary(content_type) return unless content_type data = content_type.match(MULTIPART) return unless data data[1] end def self.parse(io, content_length, content_type, tmpfile, bufsize, qp) return EMPTY if 0 == content_length boundary = parse_boundary content_type return EMPTY unless boundary if boundary.length > 70 # RFC 1521 Section 7.2.1 imposes a 70 character maximum for the boundary. # Most clients use no more than 55 characters. raise Error, "multipart boundary size too large (#{boundary.length} characters)" end io = BoundedIO.new(io, content_length) if content_length parser = new(boundary, tmpfile, bufsize, qp) parser.parse(io) parser.result end class Collector class MimePart < Struct.new(:body, :head, :filename, :content_type, :name) def get_data data = body if filename == "" # filename is blank which means no file has been selected return elsif filename body.rewind if body.respond_to?(:rewind) # Take the basename of the upload's original filename. # This handles the full Windows paths given by Internet Explorer # (and perhaps other broken user agents) without affecting # those which give the lone filename. fn = filename.split(/[\/\\]/).last data = { filename: fn, type: content_type, name: name, tempfile: body, head: head } end yield data end end class BufferPart < MimePart def file?; false; end def close; end end class TempfilePart < MimePart def file?; true; end def close; body.close; end end include Enumerable def initialize(tempfile) @tempfile = tempfile @mime_parts = [] @open_files = 0 end def each @mime_parts.each { |part| yield part } end def on_mime_head(mime_index, head, filename, content_type, name) if filename body = @tempfile.call(filename, content_type) body.binmode if body.respond_to?(:binmode) klass = TempfilePart @open_files += 1 else body = String.new klass = BufferPart end @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name) check_part_limits end def on_mime_body(mime_index, content) @mime_parts[mime_index].body << content end def on_mime_finish(mime_index) end private def check_part_limits file_limit = Utils.multipart_file_limit part_limit = Utils.multipart_total_part_limit if file_limit && file_limit > 0 if @open_files >= file_limit @mime_parts.each(&:close) raise MultipartPartLimitError, 'Maximum file multiparts in content reached' end end if part_limit && part_limit > 0 if @mime_parts.size >= part_limit @mime_parts.each(&:close) raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached' end end end end attr_reader :state def initialize(boundary, tempfile, bufsize, query_parser) @query_parser = query_parser @params = query_parser.make_params @bufsize = bufsize @state = :FAST_FORWARD @mime_index = 0 @collector = Collector.new tempfile @sbuf = StringScanner.new("".dup) @body_regex = /(?:#{EOL}|\A)--#{Regexp.quote(boundary)}(?:#{EOL}|--)/m @rx_max_size = boundary.bytesize + 6 # (\r\n-- at start, either \r\n or -- at finish) @head_regex = /(.*?#{EOL})#{EOL}/m end def parse(io) outbuf = String.new read_data(io, outbuf) loop do status = case @state when :FAST_FORWARD handle_fast_forward when :CONSUME_TOKEN handle_consume_token when :MIME_HEAD handle_mime_head when :MIME_BODY handle_mime_body else # when :DONE return end read_data(io, outbuf) if status == :want_read end end def result @collector.each do |part| part.get_data do |data| tag_multipart_encoding(part.filename, part.content_type, part.name, data) @query_parser.normalize_params(@params, part.name, data) end end MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body) end private def dequote(str) # From WEBrick::HTTPUtils ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup ret.gsub!(/\\(.)/, "\\1") ret end def read_data(io, outbuf) content = io.read(@bufsize, outbuf) handle_empty_content!(content) @sbuf.concat(content) end # This handles the initial parser state. We read until we find the starting # boundary, then we can transition to the next state. If we find the ending # boundary, this is an invalid multipart upload, but keep scanning for opening # boundary in that case. If no boundary found, we need to keep reading data # and retry. It's highly unlikely the initial read will not consume the # boundary. The client would have to deliberately craft a response # with the opening boundary beyond the buffer size for that to happen. def handle_fast_forward while true case consume_boundary when :BOUNDARY # found opening boundary, transition to next state @state = :MIME_HEAD return when :END_BOUNDARY # invalid multipart upload, but retry for opening boundary else # no boundary found, keep reading data return :want_read end end end def handle_consume_token tok = consume_boundary # break if we're at the end of a buffer, but not if it is the end of a field @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY) :DONE else :MIME_HEAD end end def handle_mime_head if @sbuf.scan_until(@head_regex) head = @sbuf[1] content_type = head[MULTIPART_CONTENT_TYPE, 1] if name = head[MULTIPART_CONTENT_DISPOSITION, 1] name = dequote(name) else name = head[MULTIPART_CONTENT_ID, 1] end filename = get_filename(head) if name.nil? || name.empty? name = filename || "#{content_type || TEXT_PLAIN}[]".dup end @collector.on_mime_head @mime_index, head, filename, content_type, name @state = :MIME_BODY else :want_read end end def handle_mime_body if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string @collector.on_mime_body @mime_index, body @sbuf.pos += body.length + 2 # skip \r\n after the content @state = :CONSUME_TOKEN @mime_index += 1 else # Save what we have so far if @rx_max_size < @sbuf.rest_size delta = @sbuf.rest_size - @rx_max_size @collector.on_mime_body @mime_index, @sbuf.peek(delta) @sbuf.pos += delta @sbuf.string = @sbuf.rest end :want_read end end # Scan until the we find the start or end of the boundary. # If we find it, return the appropriate symbol for the start or # end of the boundary. If we don't find the start or end of the # boundary, clear the buffer and return nil. def consume_boundary if read_buffer = @sbuf.scan_until(@body_regex) read_buffer.end_with?(EOL) ? :BOUNDARY : :END_BOUNDARY else @sbuf.terminate nil end end def get_filename(head) filename = nil case head when RFC2183 params = Hash[*head.scan(DISPPARM).flat_map(&:compact)] if filename = params['filename*'] encoding, _, filename = filename.split("'", 3) elsif filename = params['filename'] filename = $1 if filename =~ /^"(.*)"$/ end when BROKEN filename = $1 filename = $1 if filename =~ /^"(.*)"$/ end return unless filename if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) } filename = Utils.unescape_path(filename) end filename.scrub! if filename !~ /\\[^\\"]/ filename = filename.gsub(/\\(.)/, '\1') end if encoding filename.force_encoding ::Encoding.find(encoding) end filename end CHARSET = "charset" deprecate_constant :CHARSET def tag_multipart_encoding(filename, content_type, name, body) name = name.to_s encoding = Encoding::UTF_8 name.force_encoding(encoding) return if filename if content_type list = content_type.split(';') type_subtype = list.first type_subtype.strip! if TEXT_PLAIN == type_subtype rest = list.drop 1 rest.each do |param| k, v = param.split('=', 2) k.strip! v.strip! v = v[1..-2] if v.start_with?('"') && v.end_with?('"') if k == "charset" encoding = begin Encoding.find v rescue ArgumentError Encoding::BINARY end end end end end name.force_encoding(encoding) body.force_encoding(encoding) end def handle_empty_content!(content) if content.nil? || content.empty? raise EmptyContentError end end end end end PK!R  lib/rack/mock_response.rbnu[# frozen_string_literal: true require 'cgi/cookie' require 'time' require_relative 'response' module Rack # Rack::MockResponse provides useful helpers for testing your apps. # Usually, you don't create the MockResponse on your own, but use # MockRequest. class MockResponse < Rack::Response class << self alias [] new end # Headers attr_reader :original_headers, :cookies # Errors attr_accessor :errors def initialize(status, headers, body, errors = nil) @original_headers = headers if errors @errors = errors.string if errors.respond_to?(:string) else @errors = "" end super(body, status, headers) @cookies = parse_cookies_from_header buffered_body! end def =~(other) body =~ other end def match(other) body.match other end def body return @buffered_body if defined?(@buffered_body) # FIXME: apparently users of MockResponse expect the return value of # MockResponse#body to be a string. However, the real response object # returns the body as a list. # # See spec_showstatus.rb: # # should "not replace existing messages" do # ... # res.body.should == "foo!" # end buffer = @buffered_body = String.new @body.each do |chunk| buffer << chunk end return buffer end def empty? [201, 204, 304].include? status end def cookie(name) cookies.fetch(name, nil) end private def parse_cookies_from_header cookies = Hash.new if headers.has_key? 'set-cookie' set_cookie_header = headers.fetch('set-cookie') Array(set_cookie_header).each do |header_value| header_value.split("\n").each do |cookie| cookie_name, cookie_filling = cookie.split('=', 2) cookie_attributes = identify_cookie_attributes cookie_filling parsed_cookie = CGI::Cookie.new( 'name' => cookie_name.strip, 'value' => cookie_attributes.fetch('value'), 'path' => cookie_attributes.fetch('path', nil), 'domain' => cookie_attributes.fetch('domain', nil), 'expires' => cookie_attributes.fetch('expires', nil), 'secure' => cookie_attributes.fetch('secure', false) ) cookies.store(cookie_name, parsed_cookie) end end end cookies end def identify_cookie_attributes(cookie_filling) cookie_bits = cookie_filling.split(';') cookie_attributes = Hash.new cookie_attributes.store('value', cookie_bits[0].strip) cookie_bits.drop(1).each do |bit| if bit.include? '=' cookie_attribute, attribute_value = bit.split('=', 2) cookie_attributes.store(cookie_attribute.strip.downcase, attribute_value.strip) end if bit.include? 'secure' cookie_attributes.store('secure', true) end end if cookie_attributes.key? 'max-age' cookie_attributes.store('expires', Time.now + cookie_attributes['max-age'].to_i) elsif cookie_attributes.key? 'expires' cookie_attributes.store('expires', Time.httpdate(cookie_attributes['expires'])) end cookie_attributes end end end PK!|| lib/rack/conditional_get.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'utils' require_relative 'body_proxy' module Rack # Middleware that enables conditional GET using if-none-match and # if-modified-since. The application should set either or both of the # last-modified or etag response headers according to RFC 2616. When # either of the conditions is met, the response body is set to be zero # length and the response status is set to 304 Not Modified. # # Applications that defer response body generation until the body's each # message is received will avoid response body generation completely when # a conditional GET matches. # # Adapted from Michael Klishin's Merb implementation: # https://github.com/wycats/merb/blob/master/merb-core/lib/merb-core/rack/middleware/conditional_get.rb class ConditionalGet def initialize(app) @app = app end # Return empty 304 response if the response has not been # modified since the last request. def call(env) case env[REQUEST_METHOD] when "GET", "HEAD" status, headers, body = response = @app.call(env) if status == 200 && fresh?(env, headers) response[0] = 304 headers.delete(CONTENT_TYPE) headers.delete(CONTENT_LENGTH) response[2] = Rack::BodyProxy.new([]) do body.close if body.respond_to?(:close) end end response else @app.call(env) end end private # Return whether the response has not been modified since the # last request. def fresh?(env, headers) # if-none-match has priority over if-modified-since per RFC 7232 if none_match = env['HTTP_IF_NONE_MATCH'] etag_matches?(none_match, headers) elsif (modified_since = env['HTTP_IF_MODIFIED_SINCE']) && (modified_since = to_rfc2822(modified_since)) modified_since?(modified_since, headers) end end # Whether the etag response header matches the if-none-match request header. # If so, the request has not been modified. def etag_matches?(none_match, headers) headers[ETAG] == none_match end # Whether the last-modified response header matches the if-modified-since # request header. If so, the request has not been modified. def modified_since?(modified_since, headers) last_modified = to_rfc2822(headers['last-modified']) and modified_since >= last_modified end # Return a Time object for the given string (which should be in RFC2822 # format), or nil if the string cannot be parsed. def to_rfc2822(since) # shortest possible valid date is the obsolete: 1 Nov 97 09:55 A # anything shorter is invalid, this avoids exceptions for common cases # most common being the empty string if since && since.length >= 16 # NOTE: there is no trivial way to write this in a non exception way # _rfc2822 returns a hash but is not that usable Time.rfc2822(since) rescue nil end end end end PK!@j lib/rack/constants.rbnu[# frozen_string_literal: true module Rack # Request env keys HTTP_HOST = 'HTTP_HOST' HTTP_PORT = 'HTTP_PORT' HTTPS = 'HTTPS' PATH_INFO = 'PATH_INFO' REQUEST_METHOD = 'REQUEST_METHOD' REQUEST_PATH = 'REQUEST_PATH' SCRIPT_NAME = 'SCRIPT_NAME' QUERY_STRING = 'QUERY_STRING' SERVER_PROTOCOL = 'SERVER_PROTOCOL' SERVER_NAME = 'SERVER_NAME' SERVER_PORT = 'SERVER_PORT' HTTP_COOKIE = 'HTTP_COOKIE' # Response Header Keys CACHE_CONTROL = 'cache-control' CONTENT_LENGTH = 'content-length' CONTENT_TYPE = 'content-type' ETAG = 'etag' EXPIRES = 'expires' SET_COOKIE = 'set-cookie' TRANSFER_ENCODING = 'transfer-encoding' # HTTP method verbs GET = 'GET' POST = 'POST' PUT = 'PUT' PATCH = 'PATCH' DELETE = 'DELETE' HEAD = 'HEAD' OPTIONS = 'OPTIONS' LINK = 'LINK' UNLINK = 'UNLINK' TRACE = 'TRACE' # Rack environment variables RACK_VERSION = 'rack.version' RACK_TEMPFILES = 'rack.tempfiles' RACK_ERRORS = 'rack.errors' RACK_LOGGER = 'rack.logger' RACK_INPUT = 'rack.input' RACK_SESSION = 'rack.session' RACK_SESSION_OPTIONS = 'rack.session.options' RACK_SHOWSTATUS_DETAIL = 'rack.showstatus.detail' RACK_URL_SCHEME = 'rack.url_scheme' RACK_HIJACK = 'rack.hijack' RACK_IS_HIJACK = 'rack.hijack?' RACK_RECURSIVE_INCLUDE = 'rack.recursive.include' RACK_MULTIPART_BUFFER_SIZE = 'rack.multipart.buffer_size' RACK_MULTIPART_TEMPFILE_FACTORY = 'rack.multipart.tempfile_factory' RACK_RESPONSE_FINISHED = 'rack.response_finished' RACK_REQUEST_FORM_INPUT = 'rack.request.form_input' RACK_REQUEST_FORM_HASH = 'rack.request.form_hash' RACK_REQUEST_FORM_VARS = 'rack.request.form_vars' RACK_REQUEST_FORM_ERROR = 'rack.request.form_error' RACK_REQUEST_COOKIE_HASH = 'rack.request.cookie_hash' RACK_REQUEST_COOKIE_STRING = 'rack.request.cookie_string' RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash' RACK_REQUEST_QUERY_STRING = 'rack.request.query_string' RACK_METHODOVERRIDE_ORIGINAL_METHOD = 'rack.methodoverride.original_method' end PK!Y  lib/rack/cascade.rbnu[# frozen_string_literal: true require_relative 'constants' module Rack # Rack::Cascade tries a request on several apps, and returns the # first response that is not 404 or 405 (or in a list of configured # status codes). If all applications tried return one of the configured # status codes, return the last response. class Cascade # deprecated, no longer used NotFound = [404, { CONTENT_TYPE => "text/plain" }, []] # An array of applications to try in order. attr_reader :apps # Set the apps to send requests to, and what statuses result in # cascading. Arguments: # # apps: An enumerable of rack applications. # cascade_for: The statuses to use cascading for. If a response is received # from an app, the next app is tried. def initialize(apps, cascade_for = [404, 405]) @apps = [] apps.each { |app| add app } @cascade_for = {} [*cascade_for].each { |status| @cascade_for[status] = true } end # Call each app in order. If the responses uses a status that requires # cascading, try the next app. If all responses require cascading, # return the response from the last app. def call(env) return [404, { CONTENT_TYPE => "text/plain" }, []] if @apps.empty? result = nil last_body = nil @apps.each do |app| # The SPEC says that the body must be closed after it has been iterated # by the server, or if it is replaced by a middleware action. Cascade # replaces the body each time a cascade happens. It is assumed that nil # does not respond to close, otherwise the previous application body # will be closed. The final application body will not be closed, as it # will be passed to the server as a result. last_body.close if last_body.respond_to? :close result = app.call(env) return result unless @cascade_for.include?(result[0].to_i) last_body = result[2] end result end # Append an app to the list of apps to cascade. This app will # be tried last. def add(app) @apps << app end # Whether the given app is one of the apps to cascade to. def include?(app) @apps.include?(app) end alias_method :<<, :add end end PK!ʆ66lib/rack/show_exceptions.rbnu[# frozen_string_literal: true require 'ostruct' require 'erb' require_relative 'constants' require_relative 'utils' require_relative 'request' module Rack # Rack::ShowExceptions catches all exceptions raised from the app it # wraps. It shows a useful backtrace with the sourcefile and # clickable context, the whole Rack environment and the request # data. # # Be careful when you use this on public-facing sites as it could # reveal information helpful to attackers. class ShowExceptions CONTEXT = 7 def initialize(app) @app = app end def call(env) @app.call(env) rescue StandardError, LoadError, SyntaxError => e exception_string = dump_exception(e) env[RACK_ERRORS].puts(exception_string) env[RACK_ERRORS].flush if accepts_html?(env) content_type = "text/html" body = pretty(env, e) else content_type = "text/plain" body = exception_string end [ 500, { CONTENT_TYPE => content_type, CONTENT_LENGTH => body.bytesize.to_s, }, [body], ] end def prefers_plaintext?(env) !accepts_html?(env) end def accepts_html?(env) Rack::Utils.best_q_match(env["HTTP_ACCEPT"], %w[text/html]) end private :accepts_html? def dump_exception(exception) if exception.respond_to?(:detailed_message) message = exception.detailed_message(highlight: false) else message = exception.message end string = "#{exception.class}: #{message}\n".dup string << exception.backtrace.map { |l| "\t#{l}" }.join("\n") string end def pretty(env, exception) req = Rack::Request.new(env) # This double assignment is to prevent an "unused variable" warning. # Yes, it is dumb, but I don't like Ruby yelling at me. path = path = (req.script_name + req.path_info).squeeze("/") # This double assignment is to prevent an "unused variable" warning. # Yes, it is dumb, but I don't like Ruby yelling at me. frames = frames = exception.backtrace.map { |line| frame = OpenStruct.new if line =~ /(.*?):(\d+)(:in `(.*)')?/ frame.filename = $1 frame.lineno = $2.to_i frame.function = $4 begin lineno = frame.lineno - 1 lines = ::File.readlines(frame.filename) frame.pre_context_lineno = [lineno - CONTEXT, 0].max frame.pre_context = lines[frame.pre_context_lineno...lineno] frame.context_line = lines[lineno].chomp frame.post_context_lineno = [lineno + CONTEXT, lines.size].min frame.post_context = lines[lineno + 1..frame.post_context_lineno] rescue end frame else nil end }.compact template.result(binding) end def template TEMPLATE end def h(obj) # :nodoc: case obj when String Utils.escape_html(obj) else Utils.escape_html(obj.inspect) end end # :stopdoc: # adapted from Django # Copyright (c) Django Software Foundation and individual contributors. # Used under the modified BSD license: # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 TEMPLATE = ERB.new(<<-'HTML'.gsub(/^ /, '')) <%=h exception.class %> at <%=h path %>

<%=h exception.class %> at <%=h path %>

<% if exception.respond_to?(:detailed_message) %>

<%=h exception.detailed_message(highlight: false) %>

<% else %>

<%=h exception.message %>

<% end %>
Ruby <% if first = frames.first %> <%=h first.filename %>: in <%=h first.function %>, line <%=h frames.first.lineno %> <% else %> unknown location <% end %>
Web <%=h req.request_method %> <%=h(req.host + path)%>

Jump to:

Traceback (innermost first)

    <% frames.each { |frame| %>
  • <%=h frame.filename %>: in <%=h frame.function %> <% if frame.context_line %>
    <% if frame.pre_context %>
      <% frame.pre_context.each { |line| %>
    1. <%=h line %>
    2. <% } %>
    <% end %>
    1. <%=h frame.context_line %>...
    <% if frame.post_context %>
      <% frame.post_context.each { |line| %>
    1. <%=h line %>
    2. <% } %>
    <% end %>
    <% end %>
  • <% } %>

Request information

GET

<% if req.GET and not req.GET.empty? %> <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val.inspect %>
<% else %>

No GET data.

<% end %>

POST

<% if ((req.POST and not req.POST.empty?) rescue (no_post_data = "Invalid POST data"; nil)) %> <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val.inspect %>
<% else %>

<%= no_post_data || "No POST data" %>.

<% end %> <% unless req.cookies.empty? %> <% req.cookies.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val.inspect %>
<% else %>

No cookie data.

<% end %>

Rack ENV

<% env.sort_by { |k, v| k.to_s }.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val.inspect %>

You're seeing this error because you use Rack::ShowExceptions.

HTML # :startdoc: end end PK!O~lib/rack/recursive.rbnu[# frozen_string_literal: true require 'uri' require_relative 'constants' module Rack # Rack::ForwardRequest gets caught by Rack::Recursive and redirects # the current request to the app at +url+. # # raise ForwardRequest.new("/not-found") # class ForwardRequest < Exception attr_reader :url, :env def initialize(url, env = {}) @url = URI(url) @env = env @env[PATH_INFO] = @url.path @env[QUERY_STRING] = @url.query if @url.query @env[HTTP_HOST] = @url.host if @url.host @env[HTTP_PORT] = @url.port if @url.port @env[RACK_URL_SCHEME] = @url.scheme if @url.scheme super "forwarding to #{url}" end end # Rack::Recursive allows applications called down the chain to # include data from other applications (by using # rack['rack.recursive.include'][...] or raise a # ForwardRequest to redirect internally. class Recursive def initialize(app) @app = app end def call(env) dup._call(env) end def _call(env) @script_name = env[SCRIPT_NAME] @app.call(env.merge(RACK_RECURSIVE_INCLUDE => method(:include))) rescue ForwardRequest => req call(env.merge(req.env)) end def include(env, path) unless path.index(@script_name) == 0 && (path[@script_name.size] == ?/ || path[@script_name.size].nil?) raise ArgumentError, "can only include below #{@script_name}, not #{path}" end env = env.merge(PATH_INFO => path, SCRIPT_NAME => @script_name, REQUEST_METHOD => GET, "CONTENT_LENGTH" => "0", "CONTENT_TYPE" => "", RACK_INPUT => StringIO.new("")) @app.call(env) end end end PK!YYd  lib/rack/tempfile_reaper.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'body_proxy' module Rack # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart) # Ideas/strategy based on posts by Eric Wong and Charles Oliver Nutter # https://groups.google.com/forum/#!searchin/rack-devel/temp/rack-devel/brK8eh-MByw/sw61oJJCGRMJ class TempfileReaper def initialize(app) @app = app end def call(env) env[RACK_TEMPFILES] ||= [] begin _, _, body = response = @app.call(env) rescue Exception env[RACK_TEMPFILES]&.each(&:close!) raise end response[2] = BodyProxy.new(body) do env[RACK_TEMPFILES]&.each(&:close!) end response end end end PK!;~ ~ lib/rack/rewindable_input.rbnu[# -*- encoding: binary -*- # frozen_string_literal: true require 'tempfile' require_relative 'constants' module Rack # Class which can make any IO object rewindable, including non-rewindable ones. It does # this by buffering the data into a tempfile, which is rewindable. # # Don't forget to call #close when you're done. This frees up temporary resources that # RewindableInput uses, though it does *not* close the original IO object. class RewindableInput # Makes rack.input rewindable, for compatibility with applications and middleware # designed for earlier versions of Rack (where rack.input was required to be # rewindable). class Middleware def initialize(app) @app = app end def call(env) env[RACK_INPUT] = RewindableInput.new(env[RACK_INPUT]) @app.call(env) end end def initialize(io) @io = io @rewindable_io = nil @unlinked = false end def gets make_rewindable unless @rewindable_io @rewindable_io.gets end def read(*args) make_rewindable unless @rewindable_io @rewindable_io.read(*args) end def each(&block) make_rewindable unless @rewindable_io @rewindable_io.each(&block) end def rewind make_rewindable unless @rewindable_io @rewindable_io.rewind end def size make_rewindable unless @rewindable_io @rewindable_io.size end # Closes this RewindableInput object without closing the originally # wrapped IO object. Cleans up any temporary resources that this RewindableInput # has created. # # This method may be called multiple times. It does nothing on subsequent calls. def close if @rewindable_io if @unlinked @rewindable_io.close else @rewindable_io.close! end @rewindable_io = nil end end private def make_rewindable # Buffer all data into a tempfile. Since this tempfile is private to this # RewindableInput object, we chmod it so that nobody else can read or write # it. On POSIX filesystems we also unlink the file so that it doesn't # even have a file entry on the filesystem anymore, though we can still # access it because we have the file handle open. @rewindable_io = Tempfile.new('RackRewindableInput') @rewindable_io.chmod(0000) @rewindable_io.set_encoding(Encoding::BINARY) @rewindable_io.binmode # :nocov: if filesystem_has_posix_semantics? raise 'Unlink failed. IO closed.' if @rewindable_io.closed? @unlinked = true end # :nocov: buffer = "".dup while @io.read(1024 * 4, buffer) entire_buffer_written_out = false while !entire_buffer_written_out written = @rewindable_io.write(buffer) entire_buffer_written_out = written == buffer.bytesize if !entire_buffer_written_out buffer.slice!(0 .. written - 1) end end end @rewindable_io.rewind end def filesystem_has_posix_semantics? RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/ end end end PK!Y488lib/rack/sendfile.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'utils' require_relative 'body_proxy' module Rack # = Sendfile # # The Sendfile middleware intercepts responses whose body is being # served from a file and replaces it with a server specific x-sendfile # header. The web server is then responsible for writing the file contents # to the client. This can dramatically reduce the amount of work required # by the Ruby backend and takes advantage of the web server's optimized file # delivery code. # # In order to take advantage of this middleware, the response body must # respond to +to_path+ and the request must include an x-sendfile-type # header. Rack::Files and other components implement +to_path+ so there's # rarely anything you need to do in your application. The x-sendfile-type # header is typically set in your web servers configuration. The following # sections attempt to document # # === Nginx # # Nginx supports the x-accel-redirect header. This is similar to x-sendfile # but requires parts of the filesystem to be mapped into a private URL # hierarchy. # # The following example shows the Nginx configuration required to create # a private "/files/" area, enable x-accel-redirect, and pass the special # x-sendfile-type and x-accel-mapping headers to the backend: # # location ~ /files/(.*) { # internal; # alias /var/www/$1; # } # # location / { # proxy_redirect off; # # proxy_set_header Host $host; # proxy_set_header X-Real-IP $remote_addr; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # # proxy_set_header x-sendfile-type x-accel-redirect; # proxy_set_header x-accel-mapping /var/www/=/files/; # # proxy_pass http://127.0.0.1:8080/; # } # # Note that the x-sendfile-type header must be set exactly as shown above. # The x-accel-mapping header should specify the location on the file system, # followed by an equals sign (=), followed name of the private URL pattern # that it maps to. The middleware performs a simple substitution on the # resulting path. # # See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile # # === lighttpd # # Lighttpd has supported some variation of the x-sendfile header for some # time, although only recent version support x-sendfile in a reverse proxy # configuration. # # $HTTP["host"] == "example.com" { # proxy-core.protocol = "http" # proxy-core.balancer = "round-robin" # proxy-core.backends = ( # "127.0.0.1:8000", # "127.0.0.1:8001", # ... # ) # # proxy-core.allow-x-sendfile = "enable" # proxy-core.rewrite-request = ( # "x-sendfile-type" => (".*" => "x-sendfile") # ) # } # # See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore # # === Apache # # x-sendfile is supported under Apache 2.x using a separate module: # # https://tn123.org/mod_xsendfile/ # # Once the module is compiled and installed, you can enable it using # XSendFile config directive: # # RequestHeader Set x-sendfile-type x-sendfile # ProxyPassReverse / http://localhost:8001/ # XSendFile on # # === Mapping parameter # # The third parameter allows for an overriding extension of the # x-accel-mapping header. Mappings should be provided in tuples of internal to # external. The internal values may contain regular expression syntax, they # will be matched with case indifference. class Sendfile def initialize(app, variation = nil, mappings = []) @app = app @variation = variation @mappings = mappings.map do |internal, external| [/^#{internal}/i, external] end end def call(env) _, headers, body = response = @app.call(env) if body.respond_to?(:to_path) case type = variation(env) when /x-accel-redirect/i path = ::File.expand_path(body.to_path) if url = map_accel_path(env, path) headers[CONTENT_LENGTH] = '0' # '?' must be percent-encoded because it is not query string but a part of path headers[type.downcase] = ::Rack::Utils.escape_path(url).gsub('?', '%3F') obody = body response[2] = Rack::BodyProxy.new([]) do obody.close if obody.respond_to?(:close) end else env[RACK_ERRORS].puts "x-accel-mapping header missing" end when /x-sendfile|x-lighttpd-send-file/i path = ::File.expand_path(body.to_path) headers[CONTENT_LENGTH] = '0' headers[type.downcase] = path obody = body response[2] = Rack::BodyProxy.new([]) do obody.close if obody.respond_to?(:close) end when '', nil else env[RACK_ERRORS].puts "Unknown x-sendfile variation: '#{type}'.\n" end end response end private def variation(env) @variation || env['sendfile.type'] || env['HTTP_X_SENDFILE_TYPE'] end def map_accel_path(env, path) if mapping = @mappings.find { |internal, _| internal =~ path } path.sub(*mapping) elsif mapping = env['HTTP_X_ACCEL_MAPPING'] mapping.split(',').map(&:strip).each do |m| internal, external = m.split('=', 2).map(&:strip) new_path = path.sub(/^#{internal}/i, external) return new_path unless path == new_path end path end end end end PK!M}Y??lib/rack/mock.rbnu[# frozen_string_literal: true require_relative 'mock_request' PK!dPlib/rack/logger.rbnu[# frozen_string_literal: true require 'logger' require_relative 'constants' module Rack # Sets up rack.logger to write to rack.errors stream class Logger def initialize(app, level = ::Logger::INFO) @app, @level = app, level end def call(env) logger = ::Logger.new(env[RACK_ERRORS]) logger.level = @level env[RACK_LOGGER] = logger @app.call(env) end end end PK!U``"lib/rack/multipart.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'utils' require_relative 'multipart/parser' require_relative 'multipart/generator' module Rack # A multipart form data parser, adapted from IOWA. # # Usually, Rack::Request#POST takes care of calling this. module Multipart MULTIPART_BOUNDARY = "AaB03x" class << self def parse_multipart(env, params = Rack::Utils.default_query_parser) io = env[RACK_INPUT] if content_length = env['CONTENT_LENGTH'] content_length = content_length.to_i end content_type = env['CONTENT_TYPE'] tempfile = env[RACK_MULTIPART_TEMPFILE_FACTORY] || Parser::TEMPFILE_FACTORY bufsize = env[RACK_MULTIPART_BUFFER_SIZE] || Parser::BUFSIZE info = Parser.parse(io, content_length, content_type, tempfile, bufsize, params) env[RACK_TEMPFILES] = info.tmp_files return info.params end def extract_multipart(request, params = Rack::Utils.default_query_parser) parse_multipart(request.env) end def build_multipart(params, first = true) Generator.new(params, first).dump end end end end PK! Cx x lib/rack/common_logger.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'utils' require_relative 'body_proxy' require_relative 'request' module Rack # Rack::CommonLogger forwards every request to the given +app+, and # logs a line in the # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common] # to the configured logger. class CommonLogger # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common # # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 - # # %{%s - %s [%s] "%s %s%s %s" %d %s\n} % # # The actual format is slightly different than the above due to the # separation of SCRIPT_NAME and PATH_INFO, and because the elapsed # time in seconds is included at the end. FORMAT = %{%s - %s [%s] "%s %s%s%s %s" %d %s %0.4f\n} # +logger+ can be any object that supports the +write+ or +<<+ methods, # which includes the standard library Logger. These methods are called # with a single string argument, the log message. # If +logger+ is nil, CommonLogger will fall back env['rack.errors']. def initialize(app, logger = nil) @app = app @logger = logger end # Log all requests in common_log format after a response has been # returned. Note that if the app raises an exception, the request # will not be logged, so if exception handling middleware are used, # they should be loaded after this middleware. Additionally, because # the logging happens after the request body has been fully sent, any # exceptions raised during the sending of the response body will # cause the request not to be logged. def call(env) began_at = Utils.clock_time status, headers, body = response = @app.call(env) response[2] = BodyProxy.new(body) { log(env, status, headers, began_at) } response end private # Log the request to the configured logger. def log(env, status, response_headers, began_at) request = Rack::Request.new(env) length = extract_content_length(response_headers) msg = sprintf(FORMAT, request.ip || "-", request.get_header("REMOTE_USER") || "-", Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"), request.request_method, request.script_name, request.path_info, request.query_string.empty? ? "" : "?#{request.query_string}", request.get_header(SERVER_PROTOCOL), status.to_s[0..3], length, Utils.clock_time - began_at) msg.gsub!(/[^[:print:]\n]/) { |c| sprintf("\\x%x", c.ord) } logger = @logger || request.get_header(RACK_ERRORS) # Standard library logger doesn't support write but it supports << which actually # calls to write on the log device without formatting if logger.respond_to?(:write) logger.write(msg) else logger << msg end end # Attempt to determine the content length for the response to # include it in the logged data. def extract_content_length(headers) value = headers[CONTENT_LENGTH] !value || value.to_s == '0' ? '-' : value end end end PK!>Sf?"?"lib/rack/builder.rbnu[# frozen_string_literal: true require_relative 'urlmap' module Rack # Rack::Builder provides a domain-specific language (DSL) to construct Rack # applications. It is primarily used to parse +config.ru+ files which # instantiate several middleware and a final application which are hosted # by a Rack-compatible web server. # # Example: # # app = Rack::Builder.new do # use Rack::CommonLogger # map "/ok" do # run lambda { |env| [200, {'content-type' => 'text/plain'}, ['OK']] } # end # end # # run app # # Or # # app = Rack::Builder.app do # use Rack::CommonLogger # run lambda { |env| [200, {'content-type' => 'text/plain'}, ['OK']] } # end # # run app # # +use+ adds middleware to the stack, +run+ dispatches to an application. # You can use +map+ to construct a Rack::URLMap in a convenient way. class Builder # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom UTF_8_BOM = '\xef\xbb\xbf' # Parse the given config file to get a Rack application. # # If the config file ends in +.ru+, it is treated as a # rackup file and the contents will be treated as if # specified inside a Rack::Builder block. # # If the config file does not end in +.ru+, it is # required and Rack will use the basename of the file # to guess which constant will be the Rack application to run. # # Examples: # # Rack::Builder.parse_file('config.ru') # # Rack application built using Rack::Builder.new # # Rack::Builder.parse_file('app.rb') # # requires app.rb, which can be anywhere in Ruby's # # load path. After requiring, assumes App constant # # contains Rack application # # Rack::Builder.parse_file('./my_app.rb') # # requires ./my_app.rb, which should be in the # # process's current directory. After requiring, # # assumes MyApp constant contains Rack application def self.parse_file(path) if path.end_with?('.ru') return self.load_file(path) else require path return Object.const_get(::File.basename(path, '.rb').split('_').map(&:capitalize).join('')) end end # Load the given file as a rackup file, treating the # contents as if specified inside a Rack::Builder block. # # Ignores content in the file after +__END__+, so that # use of +__END__+ will not result in a syntax error. # # Example config.ru file: # # $ cat config.ru # # use Rack::ContentLength # require './app.rb' # run App def self.load_file(path) config = ::File.read(path) config.slice!(/\A#{UTF_8_BOM}/) if config.encoding == Encoding::UTF_8 if config[/^#\\(.*)/] fail "Parsing options from the first comment line is no longer supported: #{path}" end config.sub!(/^__END__\n.*\Z/m, '') return new_from_string(config, path) end # Evaluate the given +builder_script+ string in the context of # a Rack::Builder block, returning a Rack application. def self.new_from_string(builder_script, file = "(rackup)") # We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance. # We cannot use instance_eval(String) as that would resolve constants differently. binding, builder = TOPLEVEL_BINDING.eval('Rack::Builder.new.instance_eval { [binding, self] }') eval builder_script, binding, file return builder.to_app end # Initialize a new Rack::Builder instance. +default_app+ specifies the # default application if +run+ is not called later. If a block # is given, it is evaluated in the context of the instance. def initialize(default_app = nil, &block) @use = [] @map = nil @run = default_app @warmup = nil @freeze_app = false instance_eval(&block) if block_given? end # Create a new Rack::Builder instance and return the Rack application # generated from it. def self.app(default_app = nil, &block) self.new(default_app, &block).to_app end # Specifies middleware to use in a stack. # # class Middleware # def initialize(app) # @app = app # end # # def call(env) # env["rack.some_header"] = "setting an example" # @app.call(env) # end # end # # use Middleware # run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] } # # All requests through to this application will first be processed by the middleware class. # The +call+ method in this example sets an additional environment key which then can be # referenced in the application if required. def use(middleware, *args, &block) if @map mapping, @map = @map, nil @use << proc { |app| generate_map(app, mapping) } end @use << proc { |app| middleware.new(app, *args, &block) } end # :nocov: ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true) # :nocov: # Takes a block or argument that is an object that responds to #call and # returns a Rack response. # # You can use a block: # # run do |env| # [200, { "content-type" => "text/plain" }, ["Hello World!"]] # end # # You can also provide a lambda: # # run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] } # # You can also provide a class instance: # # class Heartbeat # def call(env) # [200, { "content-type" => "text/plain" }, ["OK"]] # end # end # # run Heartbeat.new # def run(app = nil, &block) raise ArgumentError, "Both app and block given!" if app && block_given? @run = app || block end # Takes a lambda or block that is used to warm-up the application. This block is called # before the Rack application is returned by to_app. # # warmup do |app| # client = Rack::MockRequest.new(app) # client.get('/') # end # # use SomeMiddleware # run MyApp def warmup(prc = nil, &block) @warmup = prc || block end # Creates a route within the application. Routes under the mapped path will be sent to # the Rack application specified by run inside the block. Other requests will be sent to the # default application specified by run outside the block. # # class App # def call(env) # [200, {'content-type' => 'text/plain'}, ["Hello World"]] # end # end # # class Heartbeat # def call(env) # [200, { "content-type" => "text/plain" }, ["OK"]] # end # end # # app = Rack::Builder.app do # map '/heartbeat' do # run Heartbeat.new # end # run App.new # end # # run app # # The +use+ method can also be used inside the block to specify middleware to run under a specific path: # # app = Rack::Builder.app do # map '/heartbeat' do # use Middleware # run Heartbeat.new # end # run App.new # end # # This example includes a piece of middleware which will run before +/heartbeat+ requests hit +Heartbeat+. # # Note that providing a +path+ of +/+ will ignore any default application given in a +run+ statement # outside the block. def map(path, &block) @map ||= {} @map[path] = block end # Freeze the app (set using run) and all middleware instances when building the application # in to_app. def freeze_app @freeze_app = true end # Return the Rack application generated by this instance. def to_app app = @map ? generate_map(@run, @map) : @run fail "missing run or map statement" unless app app.freeze if @freeze_app app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } } @warmup.call(app) if @warmup app end # Call the Rack application generated by this builder instance. Note that # this rebuilds the Rack application and runs the warmup code (if any) # every time it is called, so it should not be used if performance is important. def call(env) to_app.call(env) end private # Generate a URLMap instance by generating new Rack applications for each # map block in this instance. def generate_map(default_app, mapping) mapped = default_app ? { '/' => default_app } : {} mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app } URLMap.new(mapped) end end end PK!lib/rack/auth/digest/md5.rbnu[require_relative '../digest' PK!lib/rack/auth/digest/request.rbnu[require_relative '../digest' PK!lib/rack/auth/digest/nonce.rbnu[require_relative '../digest' PK!lib/rack/auth/digest/params.rbnu[require_relative '../digest' PK!  lib/rack/auth/digest.rbnu[# frozen_string_literal: true require_relative 'abstract/handler' require_relative 'abstract/request' require 'digest/md5' require 'base64' module Rack warn "Rack::Auth::Digest is deprecated and will be removed in Rack 3.1", uplevel: 1 module Auth module Digest # Rack::Auth::Digest::Nonce is the default nonce generator for the # Rack::Auth::Digest::MD5 authentication handler. # # +private_key+ needs to set to a constant string. # # +time_limit+ can be optionally set to an integer (number of seconds), # to limit the validity of the generated nonces. class Nonce class << self attr_accessor :private_key, :time_limit end def self.parse(string) new(*Base64.decode64(string).split(' ', 2)) end def initialize(timestamp = Time.now, given_digest = nil) @timestamp, @given_digest = timestamp.to_i, given_digest end def to_s Base64.encode64("#{@timestamp} #{digest}").strip end def digest ::Digest::MD5.hexdigest("#{@timestamp}:#{self.class.private_key}") end def valid? digest == @given_digest end def stale? !self.class.time_limit.nil? && (Time.now.to_i - @timestamp) > self.class.time_limit end def fresh? !stale? end end class Params < Hash def self.parse(str) Params[*split_header_value(str).map do |param| k, v = param.split('=', 2) [k, dequote(v)] end.flatten] end def self.dequote(str) # From WEBrick::HTTPUtils ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup ret.gsub!(/\\(.)/, "\\1") ret end def self.split_header_value(str) str.scan(/\w+\=(?:"[^\"]+"|[^,]+)/n) end def initialize super() yield self if block_given? end def [](k) super k.to_s end def []=(k, v) super k.to_s, v.to_s end UNQUOTED = ['nc', 'stale'] def to_s map do |k, v| "#{k}=#{(UNQUOTED.include?(k) ? v.to_s : quote(v))}" end.join(', ') end def quote(str) # From WEBrick::HTTPUtils '"' + str.gsub(/[\\\"]/o, "\\\1") + '"' end end class Request < Auth::AbstractRequest def method @env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] || @env[REQUEST_METHOD] end def digest? "digest" == scheme end def correct_uri? request.fullpath == uri end def nonce @nonce ||= Nonce.parse(params['nonce']) end def params @params ||= Params.parse(parts.last) end def respond_to?(sym, *) super or params.has_key? sym.to_s end def method_missing(sym, *args) return super unless params.has_key?(key = sym.to_s) return params[key] if args.size == 0 raise ArgumentError, "wrong number of arguments (#{args.size} for 0)" end end # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of # HTTP Digest Authentication, as per RFC 2617. # # Initialize with the [Rack] application that you want protecting, # and a block that looks up a plaintext password for a given username. # # +opaque+ needs to be set to a constant base64/hexadecimal string. # class MD5 < AbstractHandler attr_accessor :opaque attr_writer :passwords_hashed def initialize(app, realm = nil, opaque = nil, &authenticator) @passwords_hashed = nil if opaque.nil? and realm.respond_to? :values_at realm, opaque, @passwords_hashed = realm.values_at :realm, :opaque, :passwords_hashed end super(app, realm, &authenticator) @opaque = opaque end def passwords_hashed? !!@passwords_hashed end def call(env) auth = Request.new(env) unless auth.provided? return unauthorized end if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth) return bad_request end if valid?(auth) if auth.nonce.stale? return unauthorized(challenge(stale: true)) else env['REMOTE_USER'] = auth.username return @app.call(env) end end unauthorized end private QOP = 'auth' def params(hash = {}) Params.new do |params| params['realm'] = realm params['nonce'] = Nonce.new.to_s params['opaque'] = H(opaque) params['qop'] = QOP hash.each { |k, v| params[k] = v } end end def challenge(hash = {}) "Digest #{params(hash)}" end def valid?(auth) valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth) end def valid_qop?(auth) QOP == auth.qop end def valid_opaque?(auth) H(opaque) == auth.opaque end def valid_nonce?(auth) auth.nonce.valid? end def valid_digest?(auth) pw = @authenticator.call(auth.username) pw && Rack::Utils.secure_compare(digest(auth, pw), auth.response) end def md5(data) ::Digest::MD5.hexdigest(data) end alias :H :md5 def KD(secret, data) H "#{secret}:#{data}" end def A1(auth, password) "#{auth.username}:#{auth.realm}:#{password}" end def A2(auth) "#{auth.method}:#{auth.uri}" end def digest(auth, password) password_hash = passwords_hashed? ? password : H(A1(auth, password)) KD password_hash, "#{auth.nonce}:#{auth.nc}:#{auth.cnonce}:#{QOP}:#{H A2(auth)}" end end end end end PK!lib/rack/auth/basic.rbnu[# frozen_string_literal: true require_relative 'abstract/handler' require_relative 'abstract/request' require 'base64' module Rack module Auth # Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617. # # Initialize with the Rack application that you want protecting, # and a block that checks if a username and password pair are valid. class Basic < AbstractHandler def call(env) auth = Basic::Request.new(env) return unauthorized unless auth.provided? return bad_request unless auth.basic? if valid?(auth) env['REMOTE_USER'] = auth.username return @app.call(env) end unauthorized end private def challenge 'Basic realm="%s"' % realm end def valid?(auth) @authenticator.call(*auth.credentials) end class Request < Auth::AbstractRequest def basic? "basic" == scheme && credentials.length == 2 end def credentials @credentials ||= Base64.decode64(params).split(':', 2) end def username credentials.first end end end end end PK!,UU!lib/rack/auth/abstract/request.rbnu[# frozen_string_literal: true require_relative '../../request' module Rack module Auth class AbstractRequest def initialize(env) @env = env end def request @request ||= Request.new(@env) end def provided? !authorization_key.nil? && valid? end def valid? !@env[authorization_key].nil? end def parts @parts ||= @env[authorization_key].split(' ', 2) end def scheme @scheme ||= parts.first&.downcase end def params @params ||= parts.last end private AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION'] def authorization_key @authorization_key ||= AUTHORIZATION_KEYS.detect { |key| @env.has_key?(key) } end end end end PK!lGRR!lib/rack/auth/abstract/handler.rbnu[# frozen_string_literal: true require_relative '../../constants' module Rack module Auth # Rack::Auth::AbstractHandler implements common authentication functionality. # # +realm+ should be set for all handlers. class AbstractHandler attr_accessor :realm def initialize(app, realm = nil, &authenticator) @app, @realm, @authenticator = app, realm, authenticator end private def unauthorized(www_authenticate = challenge) return [ 401, { CONTENT_TYPE => 'text/plain', CONTENT_LENGTH => '0', 'www-authenticate' => www_authenticate.to_s }, [] ] end def bad_request return [ 400, { CONTENT_TYPE => 'text/plain', CONTENT_LENGTH => '0' }, [] ] end end end end PK!՝[[lib/rack/lint.rbnuȯ# frozen_string_literal: true require 'forwardable' require_relative 'constants' require_relative 'utils' module Rack # Rack::Lint validates your application and the requests and # responses according to the Rack spec. class Lint def initialize(app) @app = app end # :stopdoc: class LintError < RuntimeError; end # AUTHORS: n.b. The trailing whitespace between paragraphs is important and # should not be removed. The whitespace creates paragraphs in the RDoc # output. # ## This specification aims to formalize the Rack protocol. You ## can (and should) use Rack::Lint to enforce it. ## ## When you develop middleware, be sure to add a Lint before and ## after to catch all mistakes. ## ## = Rack applications ## ## A Rack application is a Ruby object (not a class) that ## responds to +call+. def call(env = nil) Wrapper.new(@app, env).response end class Wrapper def initialize(app, env) @app = app @env = env @response = nil @head_request = false @status = nil @headers = nil @body = nil @invoked = nil @content_length = nil @closed = false @size = 0 end def response ## It takes exactly one argument, the *environment* raise LintError, "No env given" unless @env check_environment(@env) @env[RACK_INPUT] = InputWrapper.new(@env[RACK_INPUT]) @env[RACK_ERRORS] = ErrorWrapper.new(@env[RACK_ERRORS]) ## and returns a non-frozen Array of exactly three values: @response = @app.call(@env) raise LintError, "response is not an Array, but #{@response.class}" unless @response.kind_of? Array raise LintError, "response is frozen" if @response.frozen? raise LintError, "response array has #{@response.size} elements instead of 3" unless @response.size == 3 @status, @headers, @body = @response ## The *status*, check_status(@status) ## the *headers*, check_headers(@headers) hijack_proc = check_hijack_response(@headers, @env) if hijack_proc @headers[RACK_HIJACK] = hijack_proc end ## and the *body*. check_content_type(@status, @headers) check_content_length(@status, @headers) @head_request = @env[REQUEST_METHOD] == HEAD @lint = (@env['rack.lint'] ||= []) << self if (@env['rack.lint.body_iteration'] ||= 0) > 0 raise LintError, "Middleware must not call #each directly" end return [@status, @headers, self] end ## ## == The Environment ## def check_environment(env) ## The environment must be an unfrozen instance of Hash that includes ## CGI-like headers. The Rack application is free to modify the ## environment. raise LintError, "env #{env.inspect} is not a Hash, but #{env.class}" unless env.kind_of? Hash raise LintError, "env should not be frozen, but is" if env.frozen? ## ## The environment is required to include these variables ## (adopted from {PEP 333}[https://peps.python.org/pep-0333/]), except when they'd be empty, but see ## below. ## REQUEST_METHOD:: The HTTP request method, such as ## "GET" or "POST". This cannot ever ## be an empty string, and so is ## always required. ## SCRIPT_NAME:: The initial portion of the request ## URL's "path" that corresponds to the ## application object, so that the ## application knows its virtual ## "location". This may be an empty ## string, if the application corresponds ## to the "root" of the server. ## PATH_INFO:: The remainder of the request URL's ## "path", designating the virtual ## "location" of the request's target ## within the application. This may be an ## empty string, if the request URL targets ## the application root and does not have a ## trailing slash. This value may be ## percent-encoded when originating from ## a URL. ## QUERY_STRING:: The portion of the request URL that ## follows the ?, if any. May be ## empty, but is always required! ## SERVER_NAME:: When combined with SCRIPT_NAME and ## PATH_INFO, these variables can be ## used to complete the URL. Note, however, ## that HTTP_HOST, if present, ## should be used in preference to ## SERVER_NAME for reconstructing ## the request URL. ## SERVER_NAME can never be an empty ## string, and so is always required. ## SERVER_PORT:: An optional +Integer+ which is the port the ## server is running on. Should be specified if ## the server is running on a non-standard port. ## SERVER_PROTOCOL:: A string representing the HTTP version used ## for the request. ## HTTP_ Variables:: Variables corresponding to the ## client-supplied HTTP request ## headers (i.e., variables whose ## names begin with HTTP_). The ## presence or absence of these ## variables should correspond with ## the presence or absence of the ## appropriate HTTP header in the ## request. See ## {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18] ## for specific behavior. ## In addition to this, the Rack environment must include these ## Rack-specific variables: ## rack.url_scheme:: +http+ or +https+, depending on the ## request URL. ## rack.input:: See below, the input stream. ## rack.errors:: See below, the error stream. ## rack.hijack?:: See below, if present and true, indicates ## that the server supports partial hijacking. ## rack.hijack:: See below, if present, an object responding ## to +call+ that is used to perform a full ## hijack. ## Additional environment specifications have approved to ## standardized middleware APIs. None of these are required to ## be implemented by the server. ## rack.session:: A hash-like interface for storing ## request session data. ## The store must implement: if session = env[RACK_SESSION] ## store(key, value) (aliased as []=); unless session.respond_to?(:store) && session.respond_to?(:[]=) raise LintError, "session #{session.inspect} must respond to store and []=" end ## fetch(key, default = nil) (aliased as []); unless session.respond_to?(:fetch) && session.respond_to?(:[]) raise LintError, "session #{session.inspect} must respond to fetch and []" end ## delete(key); unless session.respond_to?(:delete) raise LintError, "session #{session.inspect} must respond to delete" end ## clear; unless session.respond_to?(:clear) raise LintError, "session #{session.inspect} must respond to clear" end ## to_hash (returning unfrozen Hash instance); unless session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen? raise LintError, "session #{session.inspect} must respond to to_hash and return unfrozen Hash instance" end end ## rack.logger:: A common object interface for logging messages. ## The object must implement: if logger = env[RACK_LOGGER] ## info(message, &block) unless logger.respond_to?(:info) raise LintError, "logger #{logger.inspect} must respond to info" end ## debug(message, &block) unless logger.respond_to?(:debug) raise LintError, "logger #{logger.inspect} must respond to debug" end ## warn(message, &block) unless logger.respond_to?(:warn) raise LintError, "logger #{logger.inspect} must respond to warn" end ## error(message, &block) unless logger.respond_to?(:error) raise LintError, "logger #{logger.inspect} must respond to error" end ## fatal(message, &block) unless logger.respond_to?(:fatal) raise LintError, "logger #{logger.inspect} must respond to fatal" end end ## rack.multipart.buffer_size:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes. if bufsize = env[RACK_MULTIPART_BUFFER_SIZE] unless bufsize.is_a?(Integer) && bufsize > 0 raise LintError, "rack.multipart.buffer_size must be an Integer > 0 if specified" end end ## rack.multipart.tempfile_factory:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile. if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY] raise LintError, "rack.multipart.tempfile_factory must respond to #call" unless tempfile_factory.respond_to?(:call) env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type| io = tempfile_factory.call(filename, content_type) raise LintError, "rack.multipart.tempfile_factory return value must respond to #<<" unless io.respond_to?(:<<) io end end ## The server or the application can store their own data in the ## environment, too. The keys must contain at least one dot, ## and should be prefixed uniquely. The prefix rack. ## is reserved for use with the Rack core distribution and other ## accepted specifications and must not be used otherwise. ## %w[REQUEST_METHOD SERVER_NAME QUERY_STRING SERVER_PROTOCOL rack.input rack.errors].each { |header| raise LintError, "env missing required key #{header}" unless env.include? header } ## The SERVER_PORT must be an Integer if set. server_port = env["SERVER_PORT"] unless server_port.nil? || (Integer(server_port) rescue false) raise LintError, "env[SERVER_PORT] is not an Integer" end ## The SERVER_NAME must be a valid authority as defined by RFC7540. unless (URI.parse("http://#{env[SERVER_NAME]}/") rescue false) raise LintError, "#{env[SERVER_NAME]} must be a valid authority" end ## The HTTP_HOST must be a valid authority as defined by RFC7540. unless (URI.parse("http://#{env[HTTP_HOST]}/") rescue false) raise LintError, "#{env[HTTP_HOST]} must be a valid authority" end ## The SERVER_PROTOCOL must match the regexp HTTP/\d(\.\d)?. server_protocol = env['SERVER_PROTOCOL'] unless %r{HTTP/\d(\.\d)?}.match?(server_protocol) raise LintError, "env[SERVER_PROTOCOL] does not match HTTP/\\d(\\.\\d)?" end ## If the HTTP_VERSION is present, it must equal the SERVER_PROTOCOL. if env['HTTP_VERSION'] && env['HTTP_VERSION'] != server_protocol raise LintError, "env[HTTP_VERSION] does not equal env[SERVER_PROTOCOL]" end ## The environment must not contain the keys ## HTTP_CONTENT_TYPE or HTTP_CONTENT_LENGTH ## (use the versions without HTTP_). %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header| if env.include? header raise LintError, "env contains #{header}, must use #{header[5..-1]}" end } ## The CGI keys (named without a period) must have String values. ## If the string values for CGI keys contain non-ASCII characters, ## they should use ASCII-8BIT encoding. env.each { |key, value| next if key.include? "." # Skip extensions unless value.kind_of? String raise LintError, "env variable #{key} has non-string value #{value.inspect}" end next if value.encoding == Encoding::ASCII_8BIT unless value.b !~ /[\x80-\xff]/n raise LintError, "env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}" end } ## There are the following restrictions: ## * rack.url_scheme must either be +http+ or +https+. unless %w[http https].include?(env[RACK_URL_SCHEME]) raise LintError, "rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}" end ## * There must be a valid input stream in rack.input. check_input env[RACK_INPUT] ## * There must be a valid error stream in rack.errors. check_error env[RACK_ERRORS] ## * There may be a valid hijack callback in rack.hijack check_hijack env ## * The REQUEST_METHOD must be a valid token. unless env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/ raise LintError, "REQUEST_METHOD unknown: #{env[REQUEST_METHOD].dump}" end ## * The SCRIPT_NAME, if non-empty, must start with / if env.include?(SCRIPT_NAME) && env[SCRIPT_NAME] != "" && env[SCRIPT_NAME] !~ /\A\// raise LintError, "SCRIPT_NAME must start with /" end ## * The PATH_INFO, if non-empty, must start with / if env.include?(PATH_INFO) && env[PATH_INFO] != "" && env[PATH_INFO] !~ /\A\// raise LintError, "PATH_INFO must start with /" end ## * The CONTENT_LENGTH, if given, must consist of digits only. if env.include?("CONTENT_LENGTH") && env["CONTENT_LENGTH"] !~ /\A\d+\z/ raise LintError, "Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}" end ## * One of SCRIPT_NAME or PATH_INFO must be ## set. PATH_INFO should be / if ## SCRIPT_NAME is empty. unless env[SCRIPT_NAME] || env[PATH_INFO] raise LintError, "One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)" end ## SCRIPT_NAME never should be /, but instead be empty. unless env[SCRIPT_NAME] != "/" raise LintError, "SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'" end ## rack.response_finished:: An array of callables run by the server after the response has been ## processed. This would typically be invoked after sending the response to the client, but it could also be ## invoked if an error occurs while generating the response or sending the response; in that case, the error ## argument will be a subclass of +Exception+. ## The callables are invoked with +env, status, headers, error+ arguments and should not raise any ## exceptions. They should be invoked in reverse order of registration. if callables = env[RACK_RESPONSE_FINISHED] raise LintError, "rack.response_finished must be an array of callable objects" unless callables.is_a?(Array) callables.each do |callable| raise LintError, "rack.response_finished values must respond to call(env, status, headers, error)" unless callable.respond_to?(:call) end end end ## ## === The Input Stream ## ## The input stream is an IO-like object which contains the raw HTTP ## POST data. def check_input(input) ## When applicable, its external encoding must be "ASCII-8BIT" and it ## must be opened in binary mode, for Ruby 1.9 compatibility. if input.respond_to?(:external_encoding) && input.external_encoding != Encoding::ASCII_8BIT raise LintError, "rack.input #{input} does not have ASCII-8BIT as its external encoding" end if input.respond_to?(:binmode?) && !input.binmode? raise LintError, "rack.input #{input} is not opened in binary mode" end ## The input stream must respond to +gets+, +each+, and +read+. [:gets, :each, :read].each { |method| unless input.respond_to? method raise LintError, "rack.input #{input} does not respond to ##{method}" end } end class InputWrapper def initialize(input) @input = input end ## * +gets+ must be called without arguments and return a string, ## or +nil+ on EOF. def gets(*args) raise LintError, "rack.input#gets called with arguments" unless args.size == 0 v = @input.gets unless v.nil? or v.kind_of? String raise LintError, "rack.input#gets didn't return a String" end v end ## * +read+ behaves like IO#read. ## Its signature is read([length, [buffer]]). ## ## If given, +length+ must be a non-negative Integer (>= 0) or +nil+, ## and +buffer+ must be a String and may not be nil. ## ## If +length+ is given and not nil, then this method reads at most ## +length+ bytes from the input stream. ## ## If +length+ is not given or nil, then this method reads ## all data until EOF. ## ## When EOF is reached, this method returns nil if +length+ is given ## and not nil, or "" if +length+ is not given or is nil. ## ## If +buffer+ is given, then the read data will be placed ## into +buffer+ instead of a newly created String object. def read(*args) unless args.size <= 2 raise LintError, "rack.input#read called with too many arguments" end if args.size >= 1 unless args.first.kind_of?(Integer) || args.first.nil? raise LintError, "rack.input#read called with non-integer and non-nil length" end unless args.first.nil? || args.first >= 0 raise LintError, "rack.input#read called with a negative length" end end if args.size >= 2 unless args[1].kind_of?(String) raise LintError, "rack.input#read called with non-String buffer" end end v = @input.read(*args) unless v.nil? or v.kind_of? String raise LintError, "rack.input#read didn't return nil or a String" end if args[0].nil? unless !v.nil? raise LintError, "rack.input#read(nil) returned nil on EOF" end end v end ## * +each+ must be called without arguments and only yield Strings. def each(*args) raise LintError, "rack.input#each called with arguments" unless args.size == 0 @input.each { |line| unless line.kind_of? String raise LintError, "rack.input#each didn't yield a String" end yield line } end ## * +close+ can be called on the input stream to indicate that the ## any remaining input is not needed. def close(*args) @input.close(*args) end end ## ## === The Error Stream ## def check_error(error) ## The error stream must respond to +puts+, +write+ and +flush+. [:puts, :write, :flush].each { |method| unless error.respond_to? method raise LintError, "rack.error #{error} does not respond to ##{method}" end } end class ErrorWrapper def initialize(error) @error = error end ## * +puts+ must be called with a single argument that responds to +to_s+. def puts(str) @error.puts str end ## * +write+ must be called with a single argument that is a String. def write(str) raise LintError, "rack.errors#write not called with a String" unless str.kind_of? String @error.write str end ## * +flush+ must be called without arguments and must be called ## in order to make the error appear for sure. def flush @error.flush end ## * +close+ must never be called on the error stream. def close(*args) raise LintError, "rack.errors#close must not be called" end end ## ## === Hijacking ## ## The hijacking interfaces provides a means for an application to take ## control of the HTTP connection. There are two distinct hijack ## interfaces: full hijacking where the application takes over the raw ## connection, and partial hijacking where the application takes over ## just the response body stream. In both cases, the application is ## responsible for closing the hijacked stream. ## ## Full hijacking only works with HTTP/1. Partial hijacking is functionally ## equivalent to streaming bodies, and is still optionally supported for ## backwards compatibility with older Rack versions. ## ## ==== Full Hijack ## ## Full hijack is used to completely take over an HTTP/1 connection. It ## occurs before any headers are written and causes the request to ## ignores any response generated by the application. ## ## It is intended to be used when applications need access to raw HTTP/1 ## connection. ## def check_hijack(env) ## If +rack.hijack+ is present in +env+, it must respond to +call+ if original_hijack = env[RACK_HIJACK] raise LintError, "rack.hijack must respond to call" unless original_hijack.respond_to?(:call) env[RACK_HIJACK] = proc do io = original_hijack.call ## and return an +IO+ instance which can be used to read and write ## to the underlying connection using HTTP/1 semantics and ## formatting. raise LintError, "rack.hijack must return an IO instance" unless io.is_a?(IO) io end end end ## ## ==== Partial Hijack ## ## Partial hijack is used for bi-directional streaming of the request and ## response body. It occurs after the status and headers are written by ## the server and causes the server to ignore the Body of the response. ## ## It is intended to be used when applications need bi-directional ## streaming. ## def check_hijack_response(headers, env) ## If +rack.hijack?+ is present in +env+ and truthy, if env[RACK_IS_HIJACK] ## an application may set the special response header +rack.hijack+ if original_hijack = headers[RACK_HIJACK] ## to an object that responds to +call+, unless original_hijack.respond_to?(:call) raise LintError, 'rack.hijack header must respond to #call' end ## accepting a +stream+ argument. return proc do |io| original_hijack.call StreamWrapper.new(io) end end ## ## After the response status and headers have been sent, this hijack ## callback will be invoked with a +stream+ argument which follows the ## same interface as outlined in "Streaming Body". Servers must ## ignore the +body+ part of the response tuple when the ## +rack.hijack+ response header is present. Using an empty +Array+ ## instance is recommended. else ## ## The special response header +rack.hijack+ must only be set ## if the request +env+ has a truthy +rack.hijack?+. if headers.key?(RACK_HIJACK) raise LintError, 'rack.hijack header must not be present if server does not support hijacking' end end nil end ## == The Response ## ## === The Status ## def check_status(status) ## This is an HTTP status. It must be an Integer greater than or equal to ## 100. unless status.is_a?(Integer) && status >= 100 raise LintError, "Status must be an Integer >=100" end end ## ## === The Headers ## def check_headers(headers) ## The headers must be a unfrozen Hash. unless headers.kind_of?(Hash) raise LintError, "headers object should be a hash, but isn't (got #{headers.class} as headers)" end if headers.frozen? raise LintError, "headers object should not be frozen, but is" end headers.each do |key, value| ## The header keys must be Strings. unless key.kind_of? String raise LintError, "header key must be a string, was #{key.class}" end ## Special headers starting "rack." are for communicating with the ## server, and must not be sent back to the client. next if key.start_with?("rack.") ## The header must not contain a +Status+ key. raise LintError, "header must not contain status" if key == "status" ## Header keys must conform to RFC7230 token specification, i.e. cannot ## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}". raise LintError, "invalid header name: #{key}" if key =~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/ ## Header keys must not contain uppercase ASCII characters (A-Z). raise LintError, "uppercase character in header name: #{key}" if key =~ /[A-Z]/ ## Header values must be either a String instance, if value.kind_of?(String) check_header_value(key, value) elsif value.kind_of?(Array) ## or an Array of String instances, value.each{|value| check_header_value(key, value)} else raise LintError, "a header value must be a String or Array of Strings, but the value of '#{key}' is a #{value.class}" end end end def check_header_value(key, value) ## such that each String instance must not contain characters below 037. if value =~ /[\000-\037]/ raise LintError, "invalid header value #{key}: #{value.inspect}" end end ## ## === The content-type ## def check_content_type(status, headers) headers.each { |key, value| ## There must not be a content-type header key when the +Status+ is 1xx, ## 204, or 304. if key == "content-type" if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i raise LintError, "content-type header found in #{status} response, not allowed" end return end } end ## ## === The content-length ## def check_content_length(status, headers) headers.each { |key, value| if key == 'content-length' ## There must not be a content-length header key when the ## +Status+ is 1xx, 204, or 304. if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i raise LintError, "content-length header found in #{status} response, not allowed" end @content_length = value end } end def verify_content_length(size) if @head_request unless size == 0 raise LintError, "Response body was given for HEAD request, but should be empty" end elsif @content_length unless @content_length == size.to_s raise LintError, "content-length header was #{@content_length}, but should be #{size}" end end end ## ## === The Body ## ## The Body is typically an +Array+ of +String+ instances, an enumerable ## that yields +String+ instances, a +Proc+ instance, or a File-like ## object. ## ## The Body must respond to +each+ or +call+. It may optionally respond ## to +to_path+ or +to_ary+. A Body that responds to +each+ is considered ## to be an Enumerable Body. A Body that responds to +call+ is considered ## to be a Streaming Body. ## ## A Body that responds to both +each+ and +call+ must be treated as an ## Enumerable Body, not a Streaming Body. If it responds to +each+, you ## must call +each+ and not +call+. If the Body doesn't respond to ## +each+, then you can assume it responds to +call+. ## ## The Body must either be consumed or returned. The Body is consumed by ## optionally calling either +each+ or +call+. ## Then, if the Body responds to +close+, it must be called to release ## any resources associated with the generation of the body. ## In other words, +close+ must always be called at least once; typically ## after the web server has sent the response to the client, but also in ## cases where the Rack application makes internal/virtual requests and ## discards the response. ## def close ## ## After calling +close+, the Body is considered closed and should not ## be consumed again. @closed = true ## If the original Body is replaced by a new Body, the new Body must ## also consume the original Body by calling +close+ if possible. @body.close if @body.respond_to?(:close) index = @lint.index(self) unless @env['rack.lint'][0..index].all? {|lint| lint.instance_variable_get(:@closed)} raise LintError, "Body has not been closed" end end def verify_to_path ## ## If the Body responds to +to_path+, it must return a +String+ ## path for the local file system whose contents are identical ## to that produced by calling +each+; this may be used by the ## server as an alternative, possibly more efficient way to ## transport the response. The +to_path+ method does not consume ## the body. if @body.respond_to?(:to_path) unless ::File.exist? @body.to_path raise LintError, "The file identified by body.to_path does not exist" end end end ## ## ==== Enumerable Body ## def each ## The Enumerable Body must respond to +each+. raise LintError, "Enumerable Body must respond to each" unless @body.respond_to?(:each) ## It must only be called once. raise LintError, "Response body must only be invoked once (#{@invoked})" unless @invoked.nil? ## It must not be called after being closed. raise LintError, "Response body is already closed" if @closed @invoked = :each @body.each do |chunk| ## and must only yield String values. unless chunk.kind_of? String raise LintError, "Body yielded non-string value #{chunk.inspect}" end ## ## The Body itself should not be an instance of String, as this will ## break in Ruby 1.9. ## ## Middleware must not call +each+ directly on the Body. ## Instead, middleware can return a new Body that calls +each+ on the ## original Body, yielding at least once per iteration. if @lint[0] == self @env['rack.lint.body_iteration'] += 1 else if (@env['rack.lint.body_iteration'] -= 1) > 0 raise LintError, "New body must yield at least once per iteration of old body" end end @size += chunk.bytesize yield chunk end verify_content_length(@size) verify_to_path end BODY_METHODS = {to_ary: true, each: true, call: true, to_path: true} def to_path @body.to_path end def respond_to?(name, *) if BODY_METHODS.key?(name) @body.respond_to?(name) else super end end ## ## If the Body responds to +to_ary+, it must return an +Array+ whose ## contents are identical to that produced by calling +each+. ## Middleware may call +to_ary+ directly on the Body and return a new ## Body in its place. In other words, middleware can only process the ## Body directly if it responds to +to_ary+. If the Body responds to both ## +to_ary+ and +close+, its implementation of +to_ary+ must call ## +close+. def to_ary @body.to_ary.tap do |content| unless content == @body.enum_for.to_a raise LintError, "#to_ary not identical to contents produced by calling #each" end end ensure close end ## ## ==== Streaming Body ## def call(stream) ## The Streaming Body must respond to +call+. raise LintError, "Streaming Body must respond to call" unless @body.respond_to?(:call) ## It must only be called once. raise LintError, "Response body must only be invoked once (#{@invoked})" unless @invoked.nil? ## It must not be called after being closed. raise LintError, "Response body is already closed" if @closed @invoked = :call ## It takes a +stream+ argument. ## ## The +stream+ argument must implement: ## read, write, <<, flush, close, close_read, close_write, closed? ## @body.call(StreamWrapper.new(stream)) end class StreamWrapper extend Forwardable ## The semantics of these IO methods must be a best effort match to ## those of a normal Ruby IO or Socket object, using standard arguments ## and raising standard exceptions. Servers are encouraged to simply ## pass on real IO objects, although it is recognized that this approach ## is not directly compatible with HTTP/2. REQUIRED_METHODS = [ :read, :write, :<<, :flush, :close, :close_read, :close_write, :closed? ] def_delegators :@stream, *REQUIRED_METHODS def initialize(stream) @stream = stream REQUIRED_METHODS.each do |method_name| raise LintError, "Stream must respond to #{method_name}" unless stream.respond_to?(method_name) end end end # :startdoc: end end end ## ## == Thanks ## Some parts of this specification are adopted from {PEP 333 – Python Web Server Gateway Interface v1.0}[https://peps.python.org/pep-0333/] ## I'd like to thank everyone involved in that effort. PK!&{!{!lib/rack/query_parser.rbnu[# frozen_string_literal: true require 'uri' module Rack class QueryParser DEFAULT_SEP = /[&] */n COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n } # ParameterTypeError is the error that is raised when incoming structural # parameters (parsed by parse_nested_query) contain conflicting types. class ParameterTypeError < TypeError; end # InvalidParameterError is the error that is raised when incoming structural # parameters (parsed by parse_nested_query) contain invalid format or byte # sequence. class InvalidParameterError < ArgumentError; end # ParamsTooDeepError is the error that is raised when params are recursively # nested over the specified limit. class ParamsTooDeepError < RangeError; end def self.make_default(_key_space_limit=(not_deprecated = true; nil), param_depth_limit) unless not_deprecated warn("`first argument `key_space limit` is deprecated and no longer has an effect. Please call with only one argument, which will be required in a future version of Rack", uplevel: 1) end new Params, param_depth_limit end attr_reader :param_depth_limit def initialize(params_class, _key_space_limit=(not_deprecated = true; nil), param_depth_limit) unless not_deprecated warn("`second argument `key_space limit` is deprecated and no longer has an effect. Please call with only two arguments, which will be required in a future version of Rack", uplevel: 1) end @params_class = params_class @param_depth_limit = param_depth_limit end # Stolen from Mongrel, with some small modifications: # Parses a query string by breaking it up at the '&'. You can also use this # to parse cookies by changing the characters used in the second parameter # (which defaults to '&'). def parse_query(qs, separator = nil, &unescaper) unescaper ||= method(:unescape) params = make_params (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p| next if p.empty? k, v = p.split('=', 2).map!(&unescaper) if cur = params[k] if cur.class == Array params[k] << v else params[k] = [cur, v] end else params[k] = v end end return params.to_h end # parse_nested_query expands a query string into structural types. Supported # types are Arrays, Hashes and basic value types. It is possible to supply # query strings with parameters of conflicting types, in this case a # ParameterTypeError is raised. Users are encouraged to return a 400 in this # case. def parse_nested_query(qs, separator = nil) params = make_params unless qs.nil? || qs.empty? (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p| k, v = p.split('=', 2).map! { |s| unescape(s) } _normalize_params(params, k, v, 0) end end return params.to_h rescue ArgumentError => e raise InvalidParameterError, e.message, e.backtrace end # normalize_params recursively expands parameters into structural types. If # the structural types represented by two different parameter names are in # conflict, a ParameterTypeError is raised. The depth argument is deprecated # and should no longer be used, it is kept for backwards compatibility with # earlier versions of rack. def normalize_params(params, name, v, _depth=nil) _normalize_params(params, name, v, 0) end private def _normalize_params(params, name, v, depth) raise ParamsTooDeepError if depth >= param_depth_limit if !name # nil name, treat same as empty string (required by tests) k = after = '' elsif depth == 0 # Start of parsing, don't treat [] or [ at start of string specially if start = name.index('[', 1) # Start of parameter nesting, use part before brackets as key k = name[0, start] after = name[start, name.length] else # Plain parameter with no nesting k = name after = '' end elsif name.start_with?('[]') # Array nesting k = '[]' after = name[2, name.length] elsif name.start_with?('[') && (start = name.index(']', 1)) # Hash nesting, use the part inside brackets as the key k = name[1, start-1] after = name[start+1, name.length] else # Probably malformed input, nested but not starting with [ # treat full name as key for backwards compatibility. k = name after = '' end return if k.empty? if after == '' if k == '[]' && depth != 0 return [v] else params[k] = v end elsif after == "[" params[name] = v elsif after == "[]" params[k] ||= [] raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) params[k] << v elsif after.start_with?('[]') # Recognize x[][y] (hash inside array) parameters unless after[2] == '[' && after.end_with?(']') && (child_key = after[3, after.length-4]) && !child_key.empty? && !child_key.index('[') && !child_key.index(']') # Handle other nested array parameters child_key = after[2, after.length] end params[k] ||= [] raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key) _normalize_params(params[k].last, child_key, v, depth + 1) else params[k] << _normalize_params(make_params, child_key, v, depth + 1) end else params[k] ||= make_params raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k]) params[k] = _normalize_params(params[k], after, v, depth + 1) end params end def make_params @params_class.new end def new_depth_limit(param_depth_limit) self.class.new @params_class, param_depth_limit end private def params_hash_type?(obj) obj.kind_of?(@params_class) end def params_hash_has_key?(hash, key) return false if /\[\]/.match?(key) key.split(/[\[\]]+/).inject(hash) do |h, part| next h if part == '' return false unless params_hash_type?(h) && h.key?(part) h[part] end true end def unescape(string, encoding = Encoding::UTF_8) URI.decode_www_form_component(string, encoding) end class Params def initialize @size = 0 @params = {} end def [](key) @params[key] end def []=(key, value) @params[key] = value end def key?(key) @params.key?(key) end # Recursively unwraps nested `Params` objects and constructs an object # of the same shape, but using the objects' internal representations # (Ruby hashes) in place of the objects. The result is a hash consisting # purely of Ruby primitives. # # Mutation warning! # # 1. This method mutates the internal representation of the `Params` # objects in order to save object allocations. # # 2. The value you get back is a reference to the internal hash # representation, not a copy. # # 3. Because the `Params` object's internal representation is mutable # through the `#[]=` method, it is not thread safe. The result of # getting the hash representation while another thread is adding a # key to it is non-deterministic. # def to_h @params.each do |key, value| case value when self # Handle circular references gracefully. @params[key] = @params when Params @params[key] = value.to_h when Array value.map! { |v| v.kind_of?(Params) ? v.to_h : v } else # Ignore anything that is not a `Params` object or # a collection that can contain one. end end @params end alias_method :to_params_hash, :to_h end end end PK!:}= = lib/rack/urlmap.rbnu[# frozen_string_literal: true require 'set' require_relative 'constants' module Rack # Rack::URLMap takes a hash mapping urls or paths to apps, and # dispatches accordingly. Support for HTTP/1.1 host names exists if # the URLs start with http:// or https://. # # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part # relevant for dispatch is in the SCRIPT_NAME, and the rest in the # PATH_INFO. This should be taken care of when you need to # reconstruct the URL in order to create links. # # URLMap dispatches in such a way that the longest paths are tried # first, since they are most specific. class URLMap def initialize(map = {}) remap(map) end def remap(map) @known_hosts = Set[] @mapping = map.map { |location, app| if location =~ %r{\Ahttps?://(.*?)(/.*)} host, location = $1, $2 @known_hosts << host else host = nil end unless location[0] == ?/ raise ArgumentError, "paths need to start with /" end location = location.chomp('/') match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING) [host, location, match, app] }.sort_by do |(host, location, _, _)| [host ? -host.size : Float::INFINITY, -location.size] end end def call(env) path = env[PATH_INFO] script_name = env[SCRIPT_NAME] http_host = env[HTTP_HOST] server_name = env[SERVER_NAME] server_port = env[SERVER_PORT] is_same_server = casecmp?(http_host, server_name) || casecmp?(http_host, "#{server_name}:#{server_port}") is_host_known = @known_hosts.include? http_host @mapping.each do |host, location, match, app| unless casecmp?(http_host, host) \ || casecmp?(server_name, host) \ || (!host && is_same_server) \ || (!host && !is_host_known) # If we don't have a matching host, default to the first without a specified host next end next unless m = match.match(path.to_s) rest = m[1] next unless !rest || rest.empty? || rest[0] == ?/ env[SCRIPT_NAME] = (script_name + location) env[PATH_INFO] = rest return app.call(env) end [404, { CONTENT_TYPE => "text/plain", "x-cascade" => "pass" }, ["Not Found: #{path}"]] ensure env[PATH_INFO] = path env[SCRIPT_NAME] = script_name end private def casecmp?(v1, v2) # if both nil, or they're the same string return true if v1 == v2 # if either are nil... (but they're not the same) return false if v1.nil? return false if v2.nil? # otherwise check they're not case-insensitive the same v1.casecmp(v2).zero? end end end PK!Oi lib/rack/headers.rbnu[module Rack # Rack::Headers is a Hash subclass that downcases all keys. It's designed # to be used by rack applications that don't implement the Rack 3 SPEC # (by using non-lowercase response header keys), automatically handling # the downcasing of keys. class Headers < Hash def self.[](*items) if items.length % 2 != 0 if items.length == 1 && items.first.is_a?(Hash) new.merge!(items.first) else raise ArgumentError, "odd number of arguments for Rack::Headers" end else hash = new loop do break if items.length == 0 key = items.shift value = items.shift hash[key] = value end hash end end def [](key) super(downcase_key(key)) end def []=(key, value) super(key.downcase.freeze, value) end alias store []= def assoc(key) super(downcase_key(key)) end def compare_by_identity raise TypeError, "Rack::Headers cannot compare by identity, use regular Hash" end def delete(key) super(downcase_key(key)) end def dig(key, *a) super(downcase_key(key), *a) end def fetch(key, *default, &block) key = downcase_key(key) super end def fetch_values(*a) super(*a.map!{|key| downcase_key(key)}) end def has_key?(key) super(downcase_key(key)) end alias include? has_key? alias key? has_key? alias member? has_key? def invert hash = self.class.new each{|key, value| hash[value] = key} hash end def merge(hash, &block) dup.merge!(hash, &block) end def reject(&block) hash = dup hash.reject!(&block) hash end def replace(hash) clear update(hash) end def select(&block) hash = dup hash.select!(&block) hash end def to_proc lambda{|x| self[x]} end def transform_values(&block) dup.transform_values!(&block) end def update(hash, &block) hash.each do |key, value| self[key] = if block_given? && include?(key) block.call(key, self[key], value) else value end end self end alias merge! update def values_at(*keys) keys.map{|key| self[key]} end # :nocov: if RUBY_VERSION >= '2.5' # :nocov: def slice(*a) h = self.class.new a.each{|k| h[k] = self[k] if has_key?(k)} h end def transform_keys(&block) dup.transform_keys!(&block) end def transform_keys! hash = self.class.new each do |k, v| hash[yield k] = v end replace(hash) end end # :nocov: if RUBY_VERSION >= '3.0' # :nocov: def except(*a) super(*a.map!{|key| downcase_key(key)}) end end private def downcase_key(key) key.is_a?(String) ? key.downcase : key end end end PK!М}lib/rack/file.rbnu[# frozen_string_literal: true require_relative 'files' module Rack warn "Rack::File is deprecated and will be removed in Rack 3.1", uplevel: 1 File = Files end PK!N4qlib/rack/content_type.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'utils' module Rack # Sets the content-type header on responses which don't have one. # # Builder Usage: # use Rack::ContentType, "text/plain" # # When no content type argument is provided, "text/html" is the # default. class ContentType include Rack::Utils def initialize(app, content_type = "text/html") @app = app @content_type = content_type end def call(env) status, headers, _ = response = @app.call(env) unless STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) headers[CONTENT_TYPE] ||= @content_type end response end end end PK!K?33lib/rack/body_proxy.rbnu[# frozen_string_literal: true module Rack # Proxy for response bodies allowing calling a block when # the response body is closed (after the response has been fully # sent to the client). class BodyProxy # Set the response body to wrap, and the block to call when the # response has been fully sent. def initialize(body, &block) @body = body @block = block @closed = false end # Return whether the wrapped body responds to the method. def respond_to_missing?(method_name, include_all = false) super or @body.respond_to?(method_name, include_all) end # If not already closed, close the wrapped body and # then call the block the proxy was initialized with. def close return if @closed @closed = true begin @body.close if @body.respond_to?(:close) ensure @block.call end end # Whether the proxy is closed. The proxy starts as not closed, # and becomes closed on the first call to close. def closed? @closed end # Delegate missing methods to the wrapped body. def method_missing(method_name, *args, &block) @body.__send__(method_name, *args, &block) end # :nocov: ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) # :nocov: end end PK!i\lib/rack/config.rbnu[# frozen_string_literal: true module Rack # Rack::Config modifies the environment using the block given during # initialization. # # Example: # use Rack::Config do |env| # env['my-key'] = 'some-value' # end class Config def initialize(app, &block) @app = app @block = block end def call(env) @block.call(env) @app.call(env) end end end PK!gGlib/rack/media_type.rbnu[# frozen_string_literal: true module Rack # Rack::MediaType parse media type and parameters out of content_type string class MediaType SPLIT_PATTERN = %r{\s*[;,]\s*} class << self # The media type (type/subtype) portion of the CONTENT_TYPE header # without any media type parameters. e.g., when CONTENT_TYPE is # "text/plain;charset=utf-8", the media-type is "text/plain". # # For more information on the use of media types in HTTP, see: # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 def type(content_type) return nil unless content_type content_type.split(SPLIT_PATTERN, 2).first.tap(&:downcase!) end # The media type parameters provided in CONTENT_TYPE as a Hash, or # an empty Hash if no CONTENT_TYPE or media-type parameters were # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8", # this method responds with the following Hash: # { 'charset' => 'utf-8' } def params(content_type) return {} if content_type.nil? content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh| k, v = s.split('=', 2) hsh[k.tap(&:downcase!)] = strip_doublequotes(v) end end private def strip_doublequotes(str) (str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str end end end end PK!>==lib/rack/lock.rbnu[# frozen_string_literal: true require_relative 'body_proxy' module Rack # Rack::Lock locks every request inside a mutex, so that every request # will effectively be executed synchronously. class Lock def initialize(app, mutex = Mutex.new) @app, @mutex = app, mutex end def call(env) @mutex.lock begin response = @app.call(env) returned = response << BodyProxy.new(response.pop) { unlock } ensure unlock unless returned end end private def unlock @mutex.unlock end end end PK!iZ&&lib/rack/content_length.rbnu[# frozen_string_literal: true require_relative 'constants' require_relative 'utils' module Rack # Sets the content-length header on responses that do not specify # a content-length or transfer-encoding header. Note that this # does not fix responses that have an invalid content-length # header specified. class ContentLength include Rack::Utils def initialize(app) @app = app end def call(env) status, headers, body = response = @app.call(env) if !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) && !headers[CONTENT_LENGTH] && !headers[TRANSFER_ENCODING] && body.respond_to?(:to_ary) response[2] = body = body.to_ary headers[CONTENT_LENGTH] = body.sum(&:bytesize).to_s end response end end end PK!$**lib/rack/response.rbnu[# frozen_string_literal: true require 'time' require_relative 'constants' require_relative 'utils' require_relative 'media_type' require_relative 'headers' module Rack # Rack::Response provides a convenient interface to create a Rack # response. # # It allows setting of headers and cookies, and provides useful # defaults (an OK response with empty headers and body). # # You can use Response#write to iteratively generate your response, # but note that this is buffered by Rack::Response until you call # +finish+. +finish+ however can take a block inside which calls to # +write+ are synchronous with the Rack response. # # Your application's +call+ should end returning Response#finish. class Response def self.[](status, headers, body) self.new(body, status, headers) end CHUNKED = 'chunked' STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY attr_accessor :length, :status, :body attr_reader :headers # Deprecated, use headers instead. def header warn 'Rack::Response#header is deprecated and will be removed in Rack 3.1', uplevel: 1 headers end # Initialize the response object with the specified +body+, +status+ # and +headers+. # # If the +body+ is +nil+, construct an empty response object with internal # buffering. # # If the +body+ responds to +to_str+, assume it's a string-like object and # construct a buffered response object containing using that string as the # initial contents of the buffer. # # Otherwise it is expected +body+ conforms to the normal requirements of a # Rack response body, typically implementing one of +each+ (enumerable # body) or +call+ (streaming body). # # The +status+ defaults to +200+ which is the "OK" HTTP status code. You # can provide any other valid status code. # # The +headers+ must be a +Hash+ of key-value header pairs which conform to # the Rack specification for response headers. The key must be a +String+ # instance and the value can be either a +String+ or +Array+ instance. def initialize(body = nil, status = 200, headers = {}) @status = status.to_i unless headers.is_a?(Hash) warn "Providing non-hash headers to Rack::Response is deprecated and will be removed in Rack 3.1", uplevel: 1 end @headers = Headers.new # Convert headers input to a plain hash with lowercase keys. headers.each do |k, v| @headers[k] = v end @writer = self.method(:append) @block = nil # Keep track of whether we have expanded the user supplied body. if body.nil? @body = [] @buffered = true @length = 0 elsif body.respond_to?(:to_str) @body = [body] @buffered = true @length = body.to_str.bytesize else @body = body @buffered = nil # undetermined as of yet. @length = 0 end yield self if block_given? end def redirect(target, status = 302) self.status = status self.location = target end def chunked? CHUNKED == get_header(TRANSFER_ENCODING) end def no_entity_body? # The response body is an enumerable body and it is not allowed to have an entity body. @body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status] end # Generate a response array consistent with the requirements of the SPEC. # @return [Array] a 3-tuple suitable of `[status, headers, body]` # which is suitable to be returned from the middleware `#call(env)` method. def finish(&block) if no_entity_body? delete_header CONTENT_TYPE delete_header CONTENT_LENGTH close return [@status, @headers, []] else if block_given? @block = block return [@status, @headers, self] else return [@status, @headers, @body] end end end alias to_a finish # For *response def each(&callback) @body.each(&callback) @buffered = true if @block @writer = callback @block.call(self) end end # Append to body and update content-length. # # NOTE: Do not mix #write and direct #body access! # def write(chunk) buffered_body! @writer.call(chunk.to_s) end def close @body.close if @body.respond_to?(:close) end def empty? @block == nil && @body.empty? end def has_header?(key) raise ArgumentError unless key.is_a?(String) @headers.key?(key) end def get_header(key) raise ArgumentError unless key.is_a?(String) @headers[key] end def set_header(key, value) raise ArgumentError unless key.is_a?(String) @headers[key] = value end def delete_header(key) raise ArgumentError unless key.is_a?(String) @headers.delete key end alias :[] :get_header alias :[]= :set_header module Helpers def invalid?; status < 100 || status >= 600; end def informational?; status >= 100 && status < 200; end def successful?; status >= 200 && status < 300; end def redirection?; status >= 300 && status < 400; end def client_error?; status >= 400 && status < 500; end def server_error?; status >= 500 && status < 600; end def ok?; status == 200; end def created?; status == 201; end def accepted?; status == 202; end def no_content?; status == 204; end def moved_permanently?; status == 301; end def bad_request?; status == 400; end def unauthorized?; status == 401; end def forbidden?; status == 403; end def not_found?; status == 404; end def method_not_allowed?; status == 405; end def not_acceptable?; status == 406; end def request_timeout?; status == 408; end def precondition_failed?; status == 412; end def unprocessable?; status == 422; end def redirect?; [301, 302, 303, 307, 308].include? status; end def include?(header) has_header?(header) end # Add a header that may have multiple values. # # Example: # response.add_header 'vary', 'accept-encoding' # response.add_header 'vary', 'cookie' # # assert_equal 'accept-encoding,cookie', response.get_header('vary') # # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 def add_header(key, value) raise ArgumentError unless key.is_a?(String) if value.nil? return get_header(key) end value = value.to_s if header = get_header(key) if header.is_a?(Array) header << value else set_header(key, [header, value]) end else set_header(key, value) end end # Get the content type of the response. def content_type get_header CONTENT_TYPE end # Set the content type of the response. def content_type=(content_type) set_header CONTENT_TYPE, content_type end def media_type MediaType.type(content_type) end def media_type_params MediaType.params(content_type) end def content_length cl = get_header CONTENT_LENGTH cl ? cl.to_i : cl end def location get_header "location" end def location=(location) set_header "location", location end def set_cookie(key, value) add_header SET_COOKIE, Utils.set_cookie_header(key, value) end def delete_cookie(key, value = {}) set_header(SET_COOKIE, Utils.delete_set_cookie_header!( get_header(SET_COOKIE), key, value ) ) end def set_cookie_header get_header SET_COOKIE end def set_cookie_header=(value) set_header SET_COOKIE, value end def cache_control get_header CACHE_CONTROL end def cache_control=(value) set_header CACHE_CONTROL, value end # Specifies that the content shouldn't be cached. Overrides `cache!` if already called. def do_not_cache! set_header CACHE_CONTROL, "no-cache, must-revalidate" set_header EXPIRES, Time.now.httpdate end # Specify that the content should be cached. # @param duration [Integer] The number of seconds until the cache expires. # @option directive [String] The cache control directive, one of "public", "private", "no-cache" or "no-store". def cache!(duration = 3600, directive: "public") unless headers[CACHE_CONTROL] =~ /no-cache/ set_header CACHE_CONTROL, "#{directive}, max-age=#{duration}" set_header EXPIRES, (Time.now + duration).httpdate end end def etag get_header ETAG end def etag=(value) set_header ETAG, value end protected def buffered_body! if @buffered.nil? if @body.is_a?(Array) # The user supplied body was an array: @body = @body.compact @body.each do |part| @length += part.to_s.bytesize end elsif @body.respond_to?(:each) # Turn the user supplied body into a buffered array: body = @body @body = Array.new body.each do |part| @writer.call(part.to_s) end body.close if body.respond_to?(:close) @buffered = true else @buffered = false end end return @buffered end def append(chunk) @body << chunk unless chunked? @length += chunk.bytesize set_header(CONTENT_LENGTH, @length.to_s) end return chunk end end include Helpers class Raw include Helpers attr_reader :headers attr_accessor :status def initialize(status, headers) @status = status @headers = headers end def has_header?(key) headers.key?(key) end def get_header(key) headers[key] end def set_header(key, value) headers[key] = value end def delete_header(key) headers.delete(key) end end end end PK!?6/lib/rack/mime.rbnu[# frozen_string_literal: true module Rack module Mime # Returns String with mime type if found, otherwise use +fallback+. # +ext+ should be filename extension in the '.ext' format that # File.extname(file) returns. # +fallback+ may be any object # # Also see the documentation for MIME_TYPES # # Usage: # Rack::Mime.mime_type('.foo') # # This is a shortcut for: # Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream') def mime_type(ext, fallback = 'application/octet-stream') MIME_TYPES.fetch(ext.to_s.downcase, fallback) end module_function :mime_type # Returns true if the given value is a mime match for the given mime match # specification, false otherwise. # # Rack::Mime.match?('text/html', 'text/*') => true # Rack::Mime.match?('text/plain', '*') => true # Rack::Mime.match?('text/html', 'application/json') => false def match?(value, matcher) v1, v2 = value.split('/', 2) m1, m2 = matcher.split('/', 2) (m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2) end module_function :match? # List of most common mime-types, selected various sources # according to their usefulness in a webserving scope for Ruby # users. # # To amend this list with your local mime.types list you can use: # # require 'webrick/httputils' # list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types') # Rack::Mime::MIME_TYPES.merge!(list) # # N.B. On Ubuntu the mime.types file does not include the leading period, so # users may need to modify the data before merging into the hash. MIME_TYPES = { ".123" => "application/vnd.lotus-1-2-3", ".3dml" => "text/vnd.in3d.3dml", ".3g2" => "video/3gpp2", ".3gp" => "video/3gpp", ".a" => "application/octet-stream", ".acc" => "application/vnd.americandynamics.acc", ".ace" => "application/x-ace-compressed", ".acu" => "application/vnd.acucobol", ".aep" => "application/vnd.audiograph", ".afp" => "application/vnd.ibm.modcap", ".ai" => "application/postscript", ".aif" => "audio/x-aiff", ".aiff" => "audio/x-aiff", ".ami" => "application/vnd.amiga.ami", ".apng" => "image/apng", ".appcache" => "text/cache-manifest", ".apr" => "application/vnd.lotus-approach", ".asc" => "application/pgp-signature", ".asf" => "video/x-ms-asf", ".asm" => "text/x-asm", ".aso" => "application/vnd.accpac.simply.aso", ".asx" => "video/x-ms-asf", ".atc" => "application/vnd.acucorp", ".atom" => "application/atom+xml", ".atomcat" => "application/atomcat+xml", ".atomsvc" => "application/atomsvc+xml", ".atx" => "application/vnd.antix.game-component", ".au" => "audio/basic", ".avi" => "video/x-msvideo", ".avif" => "image/avif", ".bat" => "application/x-msdownload", ".bcpio" => "application/x-bcpio", ".bdm" => "application/vnd.syncml.dm+wbxml", ".bh2" => "application/vnd.fujitsu.oasysprs", ".bin" => "application/octet-stream", ".bmi" => "application/vnd.bmi", ".bmp" => "image/bmp", ".box" => "application/vnd.previewsystems.box", ".btif" => "image/prs.btif", ".bz" => "application/x-bzip", ".bz2" => "application/x-bzip2", ".c" => "text/x-c", ".c4g" => "application/vnd.clonk.c4group", ".cab" => "application/vnd.ms-cab-compressed", ".cc" => "text/x-c", ".ccxml" => "application/ccxml+xml", ".cdbcmsg" => "application/vnd.contact.cmsg", ".cdkey" => "application/vnd.mediastation.cdkey", ".cdx" => "chemical/x-cdx", ".cdxml" => "application/vnd.chemdraw+xml", ".cdy" => "application/vnd.cinderella", ".cer" => "application/pkix-cert", ".cgm" => "image/cgm", ".chat" => "application/x-chat", ".chm" => "application/vnd.ms-htmlhelp", ".chrt" => "application/vnd.kde.kchart", ".cif" => "chemical/x-cif", ".cii" => "application/vnd.anser-web-certificate-issue-initiation", ".cil" => "application/vnd.ms-artgalry", ".cla" => "application/vnd.claymore", ".class" => "application/octet-stream", ".clkk" => "application/vnd.crick.clicker.keyboard", ".clkp" => "application/vnd.crick.clicker.palette", ".clkt" => "application/vnd.crick.clicker.template", ".clkw" => "application/vnd.crick.clicker.wordbank", ".clkx" => "application/vnd.crick.clicker", ".clp" => "application/x-msclip", ".cmc" => "application/vnd.cosmocaller", ".cmdf" => "chemical/x-cmdf", ".cml" => "chemical/x-cml", ".cmp" => "application/vnd.yellowriver-custom-menu", ".cmx" => "image/x-cmx", ".com" => "application/x-msdownload", ".conf" => "text/plain", ".cpio" => "application/x-cpio", ".cpp" => "text/x-c", ".cpt" => "application/mac-compactpro", ".crd" => "application/x-mscardfile", ".crl" => "application/pkix-crl", ".crt" => "application/x-x509-ca-cert", ".csh" => "application/x-csh", ".csml" => "chemical/x-csml", ".csp" => "application/vnd.commonspace", ".css" => "text/css", ".csv" => "text/csv", ".curl" => "application/vnd.curl", ".cww" => "application/prs.cww", ".cxx" => "text/x-c", ".daf" => "application/vnd.mobius.daf", ".davmount" => "application/davmount+xml", ".dcr" => "application/x-director", ".dd2" => "application/vnd.oma.dd2+xml", ".ddd" => "application/vnd.fujixerox.ddd", ".deb" => "application/x-debian-package", ".der" => "application/x-x509-ca-cert", ".dfac" => "application/vnd.dreamfactory", ".diff" => "text/x-diff", ".dis" => "application/vnd.mobius.dis", ".djv" => "image/vnd.djvu", ".djvu" => "image/vnd.djvu", ".dll" => "application/x-msdownload", ".dmg" => "application/octet-stream", ".dna" => "application/vnd.dna", ".doc" => "application/msword", ".docm" => "application/vnd.ms-word.document.macroEnabled.12", ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".dot" => "application/msword", ".dotm" => "application/vnd.ms-word.template.macroEnabled.12", ".dotx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.template", ".dp" => "application/vnd.osgi.dp", ".dpg" => "application/vnd.dpgraph", ".dsc" => "text/prs.lines.tag", ".dtd" => "application/xml-dtd", ".dts" => "audio/vnd.dts", ".dtshd" => "audio/vnd.dts.hd", ".dv" => "video/x-dv", ".dvi" => "application/x-dvi", ".dwf" => "model/vnd.dwf", ".dwg" => "image/vnd.dwg", ".dxf" => "image/vnd.dxf", ".dxp" => "application/vnd.spotfire.dxp", ".ear" => "application/java-archive", ".ecelp4800" => "audio/vnd.nuera.ecelp4800", ".ecelp7470" => "audio/vnd.nuera.ecelp7470", ".ecelp9600" => "audio/vnd.nuera.ecelp9600", ".ecma" => "application/ecmascript", ".edm" => "application/vnd.novadigm.edm", ".edx" => "application/vnd.novadigm.edx", ".efif" => "application/vnd.picsel", ".ei6" => "application/vnd.pg.osasli", ".eml" => "message/rfc822", ".eol" => "audio/vnd.digital-winds", ".eot" => "application/vnd.ms-fontobject", ".eps" => "application/postscript", ".es3" => "application/vnd.eszigno3+xml", ".esf" => "application/vnd.epson.esf", ".etx" => "text/x-setext", ".exe" => "application/x-msdownload", ".ext" => "application/vnd.novadigm.ext", ".ez" => "application/andrew-inset", ".ez2" => "application/vnd.ezpix-album", ".ez3" => "application/vnd.ezpix-package", ".f" => "text/x-fortran", ".f77" => "text/x-fortran", ".f90" => "text/x-fortran", ".fbs" => "image/vnd.fastbidsheet", ".fdf" => "application/vnd.fdf", ".fe_launch" => "application/vnd.denovo.fcselayout-link", ".fg5" => "application/vnd.fujitsu.oasysgp", ".fli" => "video/x-fli", ".flif" => "image/flif", ".flo" => "application/vnd.micrografx.flo", ".flv" => "video/x-flv", ".flw" => "application/vnd.kde.kivio", ".flx" => "text/vnd.fmi.flexstor", ".fly" => "text/vnd.fly", ".fm" => "application/vnd.framemaker", ".fnc" => "application/vnd.frogans.fnc", ".for" => "text/x-fortran", ".fpx" => "image/vnd.fpx", ".fsc" => "application/vnd.fsc.weblaunch", ".fst" => "image/vnd.fst", ".ftc" => "application/vnd.fluxtime.clip", ".fti" => "application/vnd.anser-web-funds-transfer-initiation", ".fvt" => "video/vnd.fvt", ".fzs" => "application/vnd.fuzzysheet", ".g3" => "image/g3fax", ".gac" => "application/vnd.groove-account", ".gdl" => "model/vnd.gdl", ".gem" => "application/octet-stream", ".gemspec" => "text/x-script.ruby", ".ghf" => "application/vnd.groove-help", ".gif" => "image/gif", ".gim" => "application/vnd.groove-identity-message", ".gmx" => "application/vnd.gmx", ".gph" => "application/vnd.flographit", ".gqf" => "application/vnd.grafeq", ".gram" => "application/srgs", ".grv" => "application/vnd.groove-injector", ".grxml" => "application/srgs+xml", ".gtar" => "application/x-gtar", ".gtm" => "application/vnd.groove-tool-message", ".gtw" => "model/vnd.gtw", ".gv" => "text/vnd.graphviz", ".gz" => "application/x-gzip", ".h" => "text/x-c", ".h261" => "video/h261", ".h263" => "video/h263", ".h264" => "video/h264", ".hbci" => "application/vnd.hbci", ".hdf" => "application/x-hdf", ".heic" => "image/heic", ".heics" => "image/heic-sequence", ".heif" => "image/heif", ".heifs" => "image/heif-sequence", ".hh" => "text/x-c", ".hlp" => "application/winhlp", ".hpgl" => "application/vnd.hp-hpgl", ".hpid" => "application/vnd.hp-hpid", ".hps" => "application/vnd.hp-hps", ".hqx" => "application/mac-binhex40", ".htc" => "text/x-component", ".htke" => "application/vnd.kenameaapp", ".htm" => "text/html", ".html" => "text/html", ".hvd" => "application/vnd.yamaha.hv-dic", ".hvp" => "application/vnd.yamaha.hv-voice", ".hvs" => "application/vnd.yamaha.hv-script", ".icc" => "application/vnd.iccprofile", ".ice" => "x-conference/x-cooltalk", ".ico" => "image/vnd.microsoft.icon", ".ics" => "text/calendar", ".ief" => "image/ief", ".ifb" => "text/calendar", ".ifm" => "application/vnd.shana.informed.formdata", ".igl" => "application/vnd.igloader", ".igs" => "model/iges", ".igx" => "application/vnd.micrografx.igx", ".iif" => "application/vnd.shana.informed.interchange", ".imp" => "application/vnd.accpac.simply.imp", ".ims" => "application/vnd.ms-ims", ".ipk" => "application/vnd.shana.informed.package", ".irm" => "application/vnd.ibm.rights-management", ".irp" => "application/vnd.irepository.package+xml", ".iso" => "application/octet-stream", ".itp" => "application/vnd.shana.informed.formtemplate", ".ivp" => "application/vnd.immervision-ivp", ".ivu" => "application/vnd.immervision-ivu", ".jad" => "text/vnd.sun.j2me.app-descriptor", ".jam" => "application/vnd.jam", ".jar" => "application/java-archive", ".java" => "text/x-java-source", ".jisp" => "application/vnd.jisp", ".jlt" => "application/vnd.hp-jlyt", ".jnlp" => "application/x-java-jnlp-file", ".joda" => "application/vnd.joost.joda-archive", ".jp2" => "image/jp2", ".jpeg" => "image/jpeg", ".jpg" => "image/jpeg", ".jpgv" => "video/jpeg", ".jpm" => "video/jpm", ".js" => "application/javascript", ".json" => "application/json", ".karbon" => "application/vnd.kde.karbon", ".kfo" => "application/vnd.kde.kformula", ".kia" => "application/vnd.kidspiration", ".kml" => "application/vnd.google-earth.kml+xml", ".kmz" => "application/vnd.google-earth.kmz", ".kne" => "application/vnd.kinar", ".kon" => "application/vnd.kde.kontour", ".kpr" => "application/vnd.kde.kpresenter", ".ksp" => "application/vnd.kde.kspread", ".ktz" => "application/vnd.kahootz", ".kwd" => "application/vnd.kde.kword", ".latex" => "application/x-latex", ".lbd" => "application/vnd.llamagraphics.life-balance.desktop", ".lbe" => "application/vnd.llamagraphics.life-balance.exchange+xml", ".les" => "application/vnd.hhe.lesson-player", ".link66" => "application/vnd.route66.link66+xml", ".log" => "text/plain", ".lostxml" => "application/lost+xml", ".lrm" => "application/vnd.ms-lrm", ".ltf" => "application/vnd.frogans.ltf", ".lvp" => "audio/vnd.lucent.voice", ".lwp" => "application/vnd.lotus-wordpro", ".m3u" => "audio/x-mpegurl", ".m3u8" => "application/x-mpegurl", ".m4a" => "audio/mp4a-latm", ".m4v" => "video/mp4", ".ma" => "application/mathematica", ".mag" => "application/vnd.ecowin.chart", ".man" => "text/troff", ".manifest" => "text/cache-manifest", ".mathml" => "application/mathml+xml", ".mbk" => "application/vnd.mobius.mbk", ".mbox" => "application/mbox", ".mc1" => "application/vnd.medcalcdata", ".mcd" => "application/vnd.mcd", ".mdb" => "application/x-msaccess", ".mdi" => "image/vnd.ms-modi", ".mdoc" => "text/troff", ".me" => "text/troff", ".mfm" => "application/vnd.mfmp", ".mgz" => "application/vnd.proteus.magazine", ".mid" => "audio/midi", ".midi" => "audio/midi", ".mif" => "application/vnd.mif", ".mime" => "message/rfc822", ".mj2" => "video/mj2", ".mlp" => "application/vnd.dolby.mlp", ".mmd" => "application/vnd.chipnuts.karaoke-mmd", ".mmf" => "application/vnd.smaf", ".mml" => "application/mathml+xml", ".mmr" => "image/vnd.fujixerox.edmics-mmr", ".mng" => "video/x-mng", ".mny" => "application/x-msmoney", ".mov" => "video/quicktime", ".movie" => "video/x-sgi-movie", ".mp3" => "audio/mpeg", ".mp4" => "video/mp4", ".mp4a" => "audio/mp4", ".mp4s" => "application/mp4", ".mp4v" => "video/mp4", ".mpc" => "application/vnd.mophun.certificate", ".mpd" => "application/dash+xml", ".mpeg" => "video/mpeg", ".mpg" => "video/mpeg", ".mpga" => "audio/mpeg", ".mpkg" => "application/vnd.apple.installer+xml", ".mpm" => "application/vnd.blueice.multipass", ".mpn" => "application/vnd.mophun.application", ".mpp" => "application/vnd.ms-project", ".mpy" => "application/vnd.ibm.minipay", ".mqy" => "application/vnd.mobius.mqy", ".mrc" => "application/marc", ".ms" => "text/troff", ".mscml" => "application/mediaservercontrol+xml", ".mseq" => "application/vnd.mseq", ".msf" => "application/vnd.epson.msf", ".msh" => "model/mesh", ".msi" => "application/x-msdownload", ".msl" => "application/vnd.mobius.msl", ".msty" => "application/vnd.muvee.style", ".mts" => "model/vnd.mts", ".mus" => "application/vnd.musician", ".mvb" => "application/x-msmediaview", ".mwf" => "application/vnd.mfer", ".mxf" => "application/mxf", ".mxl" => "application/vnd.recordare.musicxml", ".mxml" => "application/xv+xml", ".mxs" => "application/vnd.triscape.mxs", ".mxu" => "video/vnd.mpegurl", ".n" => "application/vnd.nokia.n-gage.symbian.install", ".nc" => "application/x-netcdf", ".ngdat" => "application/vnd.nokia.n-gage.data", ".nlu" => "application/vnd.neurolanguage.nlu", ".nml" => "application/vnd.enliven", ".nnd" => "application/vnd.noblenet-directory", ".nns" => "application/vnd.noblenet-sealer", ".nnw" => "application/vnd.noblenet-web", ".npx" => "image/vnd.net-fpx", ".nsf" => "application/vnd.lotus-notes", ".oa2" => "application/vnd.fujitsu.oasys2", ".oa3" => "application/vnd.fujitsu.oasys3", ".oas" => "application/vnd.fujitsu.oasys", ".obd" => "application/x-msbinder", ".oda" => "application/oda", ".odc" => "application/vnd.oasis.opendocument.chart", ".odf" => "application/vnd.oasis.opendocument.formula", ".odg" => "application/vnd.oasis.opendocument.graphics", ".odi" => "application/vnd.oasis.opendocument.image", ".odp" => "application/vnd.oasis.opendocument.presentation", ".ods" => "application/vnd.oasis.opendocument.spreadsheet", ".odt" => "application/vnd.oasis.opendocument.text", ".oga" => "audio/ogg", ".ogg" => "application/ogg", ".ogv" => "video/ogg", ".ogx" => "application/ogg", ".org" => "application/vnd.lotus-organizer", ".otc" => "application/vnd.oasis.opendocument.chart-template", ".otf" => "application/vnd.oasis.opendocument.formula-template", ".otg" => "application/vnd.oasis.opendocument.graphics-template", ".oth" => "application/vnd.oasis.opendocument.text-web", ".oti" => "application/vnd.oasis.opendocument.image-template", ".otm" => "application/vnd.oasis.opendocument.text-master", ".ots" => "application/vnd.oasis.opendocument.spreadsheet-template", ".ott" => "application/vnd.oasis.opendocument.text-template", ".oxt" => "application/vnd.openofficeorg.extension", ".p" => "text/x-pascal", ".p10" => "application/pkcs10", ".p12" => "application/x-pkcs12", ".p7b" => "application/x-pkcs7-certificates", ".p7m" => "application/pkcs7-mime", ".p7r" => "application/x-pkcs7-certreqresp", ".p7s" => "application/pkcs7-signature", ".pas" => "text/x-pascal", ".pbd" => "application/vnd.powerbuilder6", ".pbm" => "image/x-portable-bitmap", ".pcl" => "application/vnd.hp-pcl", ".pclxl" => "application/vnd.hp-pclxl", ".pcx" => "image/x-pcx", ".pdb" => "chemical/x-pdb", ".pdf" => "application/pdf", ".pem" => "application/x-x509-ca-cert", ".pfr" => "application/font-tdpfr", ".pgm" => "image/x-portable-graymap", ".pgn" => "application/x-chess-pgn", ".pgp" => "application/pgp-encrypted", ".pic" => "image/x-pict", ".pict" => "image/pict", ".pkg" => "application/octet-stream", ".pki" => "application/pkixcmp", ".pkipath" => "application/pkix-pkipath", ".pl" => "text/x-script.perl", ".plb" => "application/vnd.3gpp.pic-bw-large", ".plc" => "application/vnd.mobius.plc", ".plf" => "application/vnd.pocketlearn", ".pls" => "application/pls+xml", ".pm" => "text/x-script.perl-module", ".pml" => "application/vnd.ctc-posml", ".png" => "image/png", ".pnm" => "image/x-portable-anymap", ".pntg" => "image/x-macpaint", ".portpkg" => "application/vnd.macports.portpkg", ".pot" => "application/vnd.ms-powerpoint", ".potm" => "application/vnd.ms-powerpoint.template.macroEnabled.12", ".potx" => "application/vnd.openxmlformats-officedocument.presentationml.template", ".ppa" => "application/vnd.ms-powerpoint", ".ppam" => "application/vnd.ms-powerpoint.addin.macroEnabled.12", ".ppd" => "application/vnd.cups-ppd", ".ppm" => "image/x-portable-pixmap", ".pps" => "application/vnd.ms-powerpoint", ".ppsm" => "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", ".ppsx" => "application/vnd.openxmlformats-officedocument.presentationml.slideshow", ".ppt" => "application/vnd.ms-powerpoint", ".pptm" => "application/vnd.ms-powerpoint.presentation.macroEnabled.12", ".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation", ".prc" => "application/vnd.palm", ".pre" => "application/vnd.lotus-freelance", ".prf" => "application/pics-rules", ".ps" => "application/postscript", ".psb" => "application/vnd.3gpp.pic-bw-small", ".psd" => "image/vnd.adobe.photoshop", ".ptid" => "application/vnd.pvi.ptid1", ".pub" => "application/x-mspublisher", ".pvb" => "application/vnd.3gpp.pic-bw-var", ".pwn" => "application/vnd.3m.post-it-notes", ".py" => "text/x-script.python", ".pya" => "audio/vnd.ms-playready.media.pya", ".pyv" => "video/vnd.ms-playready.media.pyv", ".qam" => "application/vnd.epson.quickanime", ".qbo" => "application/vnd.intu.qbo", ".qfx" => "application/vnd.intu.qfx", ".qps" => "application/vnd.publishare-delta-tree", ".qt" => "video/quicktime", ".qtif" => "image/x-quicktime", ".qxd" => "application/vnd.quark.quarkxpress", ".ra" => "audio/x-pn-realaudio", ".rake" => "text/x-script.ruby", ".ram" => "audio/x-pn-realaudio", ".rar" => "application/x-rar-compressed", ".ras" => "image/x-cmu-raster", ".rb" => "text/x-script.ruby", ".rcprofile" => "application/vnd.ipunplugged.rcprofile", ".rdf" => "application/rdf+xml", ".rdz" => "application/vnd.data-vision.rdz", ".rep" => "application/vnd.businessobjects", ".rgb" => "image/x-rgb", ".rif" => "application/reginfo+xml", ".rl" => "application/resource-lists+xml", ".rlc" => "image/vnd.fujixerox.edmics-rlc", ".rld" => "application/resource-lists-diff+xml", ".rm" => "application/vnd.rn-realmedia", ".rmp" => "audio/x-pn-realaudio-plugin", ".rms" => "application/vnd.jcp.javame.midlet-rms", ".rnc" => "application/relax-ng-compact-syntax", ".roff" => "text/troff", ".rpm" => "application/x-redhat-package-manager", ".rpss" => "application/vnd.nokia.radio-presets", ".rpst" => "application/vnd.nokia.radio-preset", ".rq" => "application/sparql-query", ".rs" => "application/rls-services+xml", ".rsd" => "application/rsd+xml", ".rss" => "application/rss+xml", ".rtf" => "application/rtf", ".rtx" => "text/richtext", ".ru" => "text/x-script.ruby", ".s" => "text/x-asm", ".saf" => "application/vnd.yamaha.smaf-audio", ".sbml" => "application/sbml+xml", ".sc" => "application/vnd.ibm.secure-container", ".scd" => "application/x-msschedule", ".scm" => "application/vnd.lotus-screencam", ".scq" => "application/scvp-cv-request", ".scs" => "application/scvp-cv-response", ".sdkm" => "application/vnd.solent.sdkm+xml", ".sdp" => "application/sdp", ".see" => "application/vnd.seemail", ".sema" => "application/vnd.sema", ".semd" => "application/vnd.semd", ".semf" => "application/vnd.semf", ".setpay" => "application/set-payment-initiation", ".setreg" => "application/set-registration-initiation", ".sfd" => "application/vnd.hydrostatix.sof-data", ".sfs" => "application/vnd.spotfire.sfs", ".sgm" => "text/sgml", ".sgml" => "text/sgml", ".sh" => "application/x-sh", ".shar" => "application/x-shar", ".shf" => "application/shf+xml", ".sig" => "application/pgp-signature", ".sit" => "application/x-stuffit", ".sitx" => "application/x-stuffitx", ".skp" => "application/vnd.koan", ".slt" => "application/vnd.epson.salt", ".smi" => "application/smil+xml", ".snd" => "audio/basic", ".so" => "application/octet-stream", ".spf" => "application/vnd.yamaha.smaf-phrase", ".spl" => "application/x-futuresplash", ".spot" => "text/vnd.in3d.spot", ".spp" => "application/scvp-vp-response", ".spq" => "application/scvp-vp-request", ".src" => "application/x-wais-source", ".srt" => "text/srt", ".srx" => "application/sparql-results+xml", ".sse" => "application/vnd.kodak-descriptor", ".ssf" => "application/vnd.epson.ssf", ".ssml" => "application/ssml+xml", ".stf" => "application/vnd.wt.stf", ".stk" => "application/hyperstudio", ".str" => "application/vnd.pg.format", ".sus" => "application/vnd.sus-calendar", ".sv4cpio" => "application/x-sv4cpio", ".sv4crc" => "application/x-sv4crc", ".svd" => "application/vnd.svd", ".svg" => "image/svg+xml", ".svgz" => "image/svg+xml", ".swf" => "application/x-shockwave-flash", ".swi" => "application/vnd.arastra.swi", ".t" => "text/troff", ".tao" => "application/vnd.tao.intent-module-archive", ".tar" => "application/x-tar", ".tbz" => "application/x-bzip-compressed-tar", ".tcap" => "application/vnd.3gpp2.tcap", ".tcl" => "application/x-tcl", ".tex" => "application/x-tex", ".texi" => "application/x-texinfo", ".texinfo" => "application/x-texinfo", ".text" => "text/plain", ".tif" => "image/tiff", ".tiff" => "image/tiff", ".tmo" => "application/vnd.tmobile-livetv", ".torrent" => "application/x-bittorrent", ".tpl" => "application/vnd.groove-tool-template", ".tpt" => "application/vnd.trid.tpt", ".tr" => "text/troff", ".tra" => "application/vnd.trueapp", ".trm" => "application/x-msterminal", ".ts" => "video/mp2t", ".tsv" => "text/tab-separated-values", ".ttf" => "application/octet-stream", ".twd" => "application/vnd.simtech-mindmapper", ".txd" => "application/vnd.genomatix.tuxedo", ".txf" => "application/vnd.mobius.txf", ".txt" => "text/plain", ".ufd" => "application/vnd.ufdl", ".umj" => "application/vnd.umajin", ".unityweb" => "application/vnd.unity", ".uoml" => "application/vnd.uoml+xml", ".uri" => "text/uri-list", ".ustar" => "application/x-ustar", ".utz" => "application/vnd.uiq.theme", ".uu" => "text/x-uuencode", ".vcd" => "application/x-cdlink", ".vcf" => "text/x-vcard", ".vcg" => "application/vnd.groove-vcard", ".vcs" => "text/x-vcalendar", ".vcx" => "application/vnd.vcx", ".vis" => "application/vnd.visionary", ".viv" => "video/vnd.vivo", ".vrml" => "model/vrml", ".vsd" => "application/vnd.visio", ".vsf" => "application/vnd.vsf", ".vtt" => "text/vtt", ".vtu" => "model/vnd.vtu", ".vxml" => "application/voicexml+xml", ".war" => "application/java-archive", ".wasm" => "application/wasm", ".wav" => "audio/x-wav", ".wax" => "audio/x-ms-wax", ".wbmp" => "image/vnd.wap.wbmp", ".wbs" => "application/vnd.criticaltools.wbs+xml", ".wbxml" => "application/vnd.wap.wbxml", ".webm" => "video/webm", ".webp" => "image/webp", ".wm" => "video/x-ms-wm", ".wma" => "audio/x-ms-wma", ".wmd" => "application/x-ms-wmd", ".wmf" => "application/x-msmetafile", ".wml" => "text/vnd.wap.wml", ".wmlc" => "application/vnd.wap.wmlc", ".wmls" => "text/vnd.wap.wmlscript", ".wmlsc" => "application/vnd.wap.wmlscriptc", ".wmv" => "video/x-ms-wmv", ".wmx" => "video/x-ms-wmx", ".wmz" => "application/x-ms-wmz", ".woff" => "application/font-woff", ".woff2" => "application/font-woff2", ".wpd" => "application/vnd.wordperfect", ".wpl" => "application/vnd.ms-wpl", ".wps" => "application/vnd.ms-works", ".wqd" => "application/vnd.wqd", ".wri" => "application/x-mswrite", ".wrl" => "model/vrml", ".wsdl" => "application/wsdl+xml", ".wspolicy" => "application/wspolicy+xml", ".wtb" => "application/vnd.webturbo", ".wvx" => "video/x-ms-wvx", ".x3d" => "application/vnd.hzn-3d-crossword", ".xar" => "application/vnd.xara", ".xbd" => "application/vnd.fujixerox.docuworks.binder", ".xbm" => "image/x-xbitmap", ".xdm" => "application/vnd.syncml.dm+xml", ".xdp" => "application/vnd.adobe.xdp+xml", ".xdw" => "application/vnd.fujixerox.docuworks", ".xenc" => "application/xenc+xml", ".xer" => "application/patch-ops-error+xml", ".xfdf" => "application/vnd.adobe.xfdf", ".xfdl" => "application/vnd.xfdl", ".xhtml" => "application/xhtml+xml", ".xif" => "image/vnd.xiff", ".xla" => "application/vnd.ms-excel", ".xlam" => "application/vnd.ms-excel.addin.macroEnabled.12", ".xls" => "application/vnd.ms-excel", ".xlsb" => "application/vnd.ms-excel.sheet.binary.macroEnabled.12", ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xlsm" => "application/vnd.ms-excel.sheet.macroEnabled.12", ".xlt" => "application/vnd.ms-excel", ".xltx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template", ".xml" => "application/xml", ".xo" => "application/vnd.olpc-sugar", ".xop" => "application/xop+xml", ".xpm" => "image/x-xpixmap", ".xpr" => "application/vnd.is-xpr", ".xps" => "application/vnd.ms-xpsdocument", ".xpw" => "application/vnd.intercon.formnet", ".xsl" => "application/xml", ".xslt" => "application/xslt+xml", ".xsm" => "application/vnd.syncml+xml", ".xspf" => "application/xspf+xml", ".xul" => "application/vnd.mozilla.xul+xml", ".xwd" => "image/x-xwindowdump", ".xyz" => "chemical/x-xyz", ".yaml" => "text/yaml", ".yml" => "text/yaml", ".zaz" => "application/vnd.zzazz.deck+xml", ".zip" => "application/zip", ".zmm" => "application/vnd.handheld-entertainment+xml", } end end PK!== SPEC.rdocnu[This specification aims to formalize the Rack protocol. You can (and should) use Rack::Lint to enforce it. When you develop middleware, be sure to add a Lint before and after to catch all mistakes. = Rack applications A Rack application is a Ruby object (not a class) that responds to +call+. It takes exactly one argument, the *environment* and returns a non-frozen Array of exactly three values: The *status*, the *headers*, and the *body*. == The Environment The environment must be an unfrozen instance of Hash that includes CGI-like headers. The Rack application is free to modify the environment. The environment is required to include these variables (adopted from {PEP 333}[https://peps.python.org/pep-0333/]), except when they'd be empty, but see below. REQUEST_METHOD:: The HTTP request method, such as "GET" or "POST". This cannot ever be an empty string, and so is always required. SCRIPT_NAME:: The initial portion of the request URL's "path" that corresponds to the application object, so that the application knows its virtual "location". This may be an empty string, if the application corresponds to the "root" of the server. PATH_INFO:: The remainder of the request URL's "path", designating the virtual "location" of the request's target within the application. This may be an empty string, if the request URL targets the application root and does not have a trailing slash. This value may be percent-encoded when originating from a URL. QUERY_STRING:: The portion of the request URL that follows the ?, if any. May be empty, but is always required! SERVER_NAME:: When combined with SCRIPT_NAME and PATH_INFO, these variables can be used to complete the URL. Note, however, that HTTP_HOST, if present, should be used in preference to SERVER_NAME for reconstructing the request URL. SERVER_NAME can never be an empty string, and so is always required. SERVER_PORT:: An optional +Integer+ which is the port the server is running on. Should be specified if the server is running on a non-standard port. SERVER_PROTOCOL:: A string representing the HTTP version used for the request. HTTP_ Variables:: Variables corresponding to the client-supplied HTTP request headers (i.e., variables whose names begin with HTTP_). The presence or absence of these variables should correspond with the presence or absence of the appropriate HTTP header in the request. See {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18] for specific behavior. In addition to this, the Rack environment must include these Rack-specific variables: rack.url_scheme:: +http+ or +https+, depending on the request URL. rack.input:: See below, the input stream. rack.errors:: See below, the error stream. rack.hijack?:: See below, if present and true, indicates that the server supports partial hijacking. rack.hijack:: See below, if present, an object responding to +call+ that is used to perform a full hijack. Additional environment specifications have approved to standardized middleware APIs. None of these are required to be implemented by the server. rack.session:: A hash-like interface for storing request session data. The store must implement: store(key, value) (aliased as []=); fetch(key, default = nil) (aliased as []); delete(key); clear; to_hash (returning unfrozen Hash instance); rack.logger:: A common object interface for logging messages. The object must implement: info(message, &block) debug(message, &block) warn(message, &block) error(message, &block) fatal(message, &block) rack.multipart.buffer_size:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes. rack.multipart.tempfile_factory:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile. The server or the application can store their own data in the environment, too. The keys must contain at least one dot, and should be prefixed uniquely. The prefix rack. is reserved for use with the Rack core distribution and other accepted specifications and must not be used otherwise. The SERVER_PORT must be an Integer if set. The SERVER_NAME must be a valid authority as defined by RFC7540. The HTTP_HOST must be a valid authority as defined by RFC7540. The SERVER_PROTOCOL must match the regexp HTTP/\d(\.\d)?. If the HTTP_VERSION is present, it must equal the SERVER_PROTOCOL. The environment must not contain the keys HTTP_CONTENT_TYPE or HTTP_CONTENT_LENGTH (use the versions without HTTP_). The CGI keys (named without a period) must have String values. If the string values for CGI keys contain non-ASCII characters, they should use ASCII-8BIT encoding. There are the following restrictions: * rack.url_scheme must either be +http+ or +https+. * There must be a valid input stream in rack.input. * There must be a valid error stream in rack.errors. * There may be a valid hijack callback in rack.hijack * The REQUEST_METHOD must be a valid token. * The SCRIPT_NAME, if non-empty, must start with / * The PATH_INFO, if non-empty, must start with / * The CONTENT_LENGTH, if given, must consist of digits only. * One of SCRIPT_NAME or PATH_INFO must be set. PATH_INFO should be / if SCRIPT_NAME is empty. SCRIPT_NAME never should be /, but instead be empty. rack.response_finished:: An array of callables run by the server after the response has been processed. This would typically be invoked after sending the response to the client, but it could also be invoked if an error occurs while generating the response or sending the response; in that case, the error argument will be a subclass of +Exception+. The callables are invoked with +env, status, headers, error+ arguments and should not raise any exceptions. They should be invoked in reverse order of registration. === The Input Stream The input stream is an IO-like object which contains the raw HTTP POST data. When applicable, its external encoding must be "ASCII-8BIT" and it must be opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond to +gets+, +each+, and +read+. * +gets+ must be called without arguments and return a string, or +nil+ on EOF. * +read+ behaves like IO#read. Its signature is read([length, [buffer]]). If given, +length+ must be a non-negative Integer (>= 0) or +nil+, and +buffer+ must be a String and may not be nil. If +length+ is given and not nil, then this method reads at most +length+ bytes from the input stream. If +length+ is not given or nil, then this method reads all data until EOF. When EOF is reached, this method returns nil if +length+ is given and not nil, or "" if +length+ is not given or is nil. If +buffer+ is given, then the read data will be placed into +buffer+ instead of a newly created String object. * +each+ must be called without arguments and only yield Strings. * +close+ can be called on the input stream to indicate that the any remaining input is not needed. === The Error Stream The error stream must respond to +puts+, +write+ and +flush+. * +puts+ must be called with a single argument that responds to +to_s+. * +write+ must be called with a single argument that is a String. * +flush+ must be called without arguments and must be called in order to make the error appear for sure. * +close+ must never be called on the error stream. === Hijacking The hijacking interfaces provides a means for an application to take control of the HTTP connection. There are two distinct hijack interfaces: full hijacking where the application takes over the raw connection, and partial hijacking where the application takes over just the response body stream. In both cases, the application is responsible for closing the hijacked stream. Full hijacking only works with HTTP/1. Partial hijacking is functionally equivalent to streaming bodies, and is still optionally supported for backwards compatibility with older Rack versions. ==== Full Hijack Full hijack is used to completely take over an HTTP/1 connection. It occurs before any headers are written and causes the request to ignores any response generated by the application. It is intended to be used when applications need access to raw HTTP/1 connection. If +rack.hijack+ is present in +env+, it must respond to +call+ and return an +IO+ instance which can be used to read and write to the underlying connection using HTTP/1 semantics and formatting. ==== Partial Hijack Partial hijack is used for bi-directional streaming of the request and response body. It occurs after the status and headers are written by the server and causes the server to ignore the Body of the response. It is intended to be used when applications need bi-directional streaming. If +rack.hijack?+ is present in +env+ and truthy, an application may set the special response header +rack.hijack+ to an object that responds to +call+, accepting a +stream+ argument. After the response status and headers have been sent, this hijack callback will be invoked with a +stream+ argument which follows the same interface as outlined in "Streaming Body". Servers must ignore the +body+ part of the response tuple when the +rack.hijack+ response header is present. Using an empty +Array+ instance is recommended. The special response header +rack.hijack+ must only be set if the request +env+ has a truthy +rack.hijack?+. == The Response === The Status This is an HTTP status. It must be an Integer greater than or equal to 100. === The Headers The headers must be a unfrozen Hash. The header keys must be Strings. Special headers starting "rack." are for communicating with the server, and must not be sent back to the client. The header must not contain a +Status+ key. Header keys must conform to RFC7230 token specification, i.e. cannot contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}". Header keys must not contain uppercase ASCII characters (A-Z). Header values must be either a String instance, or an Array of String instances, such that each String instance must not contain characters below 037. === The content-type There must not be a content-type header key when the +Status+ is 1xx, 204, or 304. === The content-length There must not be a content-length header key when the +Status+ is 1xx, 204, or 304. === The Body The Body is typically an +Array+ of +String+ instances, an enumerable that yields +String+ instances, a +Proc+ instance, or a File-like object. The Body must respond to +each+ or +call+. It may optionally respond to +to_path+ or +to_ary+. A Body that responds to +each+ is considered to be an Enumerable Body. A Body that responds to +call+ is considered to be a Streaming Body. A Body that responds to both +each+ and +call+ must be treated as an Enumerable Body, not a Streaming Body. If it responds to +each+, you must call +each+ and not +call+. If the Body doesn't respond to +each+, then you can assume it responds to +call+. The Body must either be consumed or returned. The Body is consumed by optionally calling either +each+ or +call+. Then, if the Body responds to +close+, it must be called to release any resources associated with the generation of the body. In other words, +close+ must always be called at least once; typically after the web server has sent the response to the client, but also in cases where the Rack application makes internal/virtual requests and discards the response. After calling +close+, the Body is considered closed and should not be consumed again. If the original Body is replaced by a new Body, the new Body must also consume the original Body by calling +close+ if possible. If the Body responds to +to_path+, it must return a +String+ path for the local file system whose contents are identical to that produced by calling +each+; this may be used by the server as an alternative, possibly more efficient way to transport the response. The +to_path+ method does not consume the body. ==== Enumerable Body The Enumerable Body must respond to +each+. It must only be called once. It must not be called after being closed. and must only yield String values. The Body itself should not be an instance of String, as this will break in Ruby 1.9. Middleware must not call +each+ directly on the Body. Instead, middleware can return a new Body that calls +each+ on the original Body, yielding at least once per iteration. If the Body responds to +to_ary+, it must return an +Array+ whose contents are identical to that produced by calling +each+. Middleware may call +to_ary+ directly on the Body and return a new Body in its place. In other words, middleware can only process the Body directly if it responds to +to_ary+. If the Body responds to both +to_ary+ and +close+, its implementation of +to_ary+ must call +close+. ==== Streaming Body The Streaming Body must respond to +call+. It must only be called once. It must not be called after being closed. It takes a +stream+ argument. The +stream+ argument must implement: read, write, <<, flush, close, close_read, close_write, closed? The semantics of these IO methods must be a best effort match to those of a normal Ruby IO or Socket object, using standard arguments and raising standard exceptions. Servers are encouraged to simply pass on real IO objects, although it is recognized that this approach is not directly compatible with HTTP/2. == Thanks Some parts of this specification are adopted from {PEP 333 – Python Web Server Gateway Interface v1.0}[https://peps.python.org/pep-0333/] I'd like to thank everyone involved in that effort. PK!_* * CONTRIBUTING.mdnu[# Contributing to Rack Rack is work of [hundreds of contributors](https://github.com/rack/rack/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/rack/rack/pulls) and [propose features and discuss issues](https://github.com/rack/rack/issues). ## Fork the Project Fork the [project on GitHub](https://github.com/rack/rack) and check out your copy. ``` git clone https://github.com/(your-github-username)/rack.git cd rack git remote add upstream https://github.com/rack/rack.git ``` ## Create a Topic Branch Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. ``` git checkout main git pull upstream main git checkout -b my-feature-branch ``` ## Bundle Install and Quick Test Ensure that you can build the project and run quick tests. ``` bundle install --without extra bundle exec rake test ``` ## Running All Tests Install all dependencies. ``` bundle install ``` Run all tests. ``` rake test ``` ## Write Tests Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. ## Write Code Implement your feature or bug fix. Make sure that all tests pass: ``` bundle exec rake test ``` ## Write Documentation Document any external behavior in the [README](README.md). ## Update Changelog Add a line to [CHANGELOG](CHANGELOG.md). ## Commit Changes Make sure git knows your name and email address: ``` git config --global user.name "Your Name" git config --global user.email "contributor@example.com" ``` Writing good commit logs is important. A commit log should describe what changed and why. ``` git add ... git commit ``` ## Push ``` git push origin my-feature-branch ``` ## Make a Pull Request Go to your fork of rack on GitHub and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. ## Rebase If you've been working on a change for a while, rebase with upstream/main. ``` git fetch upstream git rebase upstream/main git push origin my-feature-branch -f ``` ## Make Required Changes Amend your previous commit and force push the changes. ``` git commit --amend git push origin my-feature-branch -f ``` ## Check on Your Pull Request Go back to your pull request after a few minutes and see whether it passed tests with GitHub Actions. Everything should look green, otherwise fix issues and amend your commit as described above. ## Be Patient It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang in there! ## Thank You Please do know that we really appreciate and value your time and work. We love you, really. PK!t2 CHANGELOG.mdnu[PK!tz,, -README.mdnu[PK!G TT MIT-LICENSEnu[PK!um lib/rack.rbnu[PK!|Хlib/rack/files.rbnu[PK!ɸHOrlib/rack/version.rbnu[PK!3slib/rack/method_override.rbnu[PK!4 4 #lib/rack/chunked.rbnu[PK!440lib/rack/events.rbnu[PK!HSGbGbsDlib/rack/request.rbnu[PK![Jlib/rack/deflater.rbnu[PK!;`ffGlib/rack/runtime.rbnu[PK!_llib/rack/null_logger.rbnu[PK!dNNlib/rack/show_status.rbnu[PK!ӎ cTcTlib/rack/utils.rbnu[PK!ѝo  -)lib/rack/head.rbnu[PK!/{{y+lib/rack/mock_request.rbnu[PK!ASf?"?"plib/rack/builder.rbnu[PK!ilib/rack/auth/digest/md5.rbnu[PK!ѓlib/rack/auth/digest/request.rbnu[PK!=lib/rack/auth/digest/nonce.rbnu[PK!lib/rack/auth/digest/params.rbnu[PK!  lib/rack/auth/digest.rbnu[PK!clib/rack/auth/basic.rbnu[PK!,UU!Zlib/rack/auth/abstract/request.rbnu[PK!lGRR!lib/rack/auth/abstract/handler.rbnu[PK!՝[[lib/rack/lint.rbnuȯPK!&{!{!>Jlib/rack/query_parser.rbnu[PK!:}= = llib/rack/urlmap.rbnu[PK!Oi wlib/rack/headers.rbnu[PK!М}lib/rack/file.rbnu[PK!N4q~lib/rack/content_type.rbnu[PK!K?33}lib/rack/body_proxy.rbnu[PK!i\lib/rack/config.rbnu[PK!gGҎlib/rack/media_type.rbnu[PK!>==lib/rack/lock.rbnu[PK!iZ&&.lib/rack/content_length.rbnu[PK!$**lib/rack/response.rbnu[PK!?6/lib/rack/mime.rbnu[PK!== HSPEC.rdocnu[PK!_* * CONTRIBUTING.mdnu[PK==Pm