pax_global_header00006660000000000000000000000064147636537500014532gustar00rootroot0000000000000052 comment=e8f47608668d507e0f231a932fa37c9ca551c0a5 rack-3.1.12/000077500000000000000000000000001476365375000125365ustar00rootroot00000000000000rack-3.1.12/.editorconfig000066400000000000000000000001631476365375000152130ustar00rootroot00000000000000root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true rack-3.1.12/.github/000077500000000000000000000000001476365375000140765ustar00rootroot00000000000000rack-3.1.12/.github/dependabot.yml000066400000000000000000000001661476365375000167310ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" rack-3.1.12/.github/workflows/000077500000000000000000000000001476365375000161335ustar00rootroot00000000000000rack-3.1.12/.github/workflows/depsreview.yaml000066400000000000000000000004501476365375000211730ustar00rootroot00000000000000name: 'Dependency Review' on: [pull_request] permissions: contents: read jobs: dependency-review: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' uses: actions/checkout@v4 - name: 'Dependency Review' uses: actions/dependency-review-action@v4 rack-3.1.12/.github/workflows/test-external.yaml000066400000000000000000000011741476365375000216210ustar00rootroot00000000000000name: Test External on: [push, pull_request] permissions: contents: read jobs: test: strategy: fail-fast: false matrix: os: [ubuntu-latest] ruby: ['3.2', '3.3', '3.4'] runs-on: ${{matrix.os}} env: CI: external steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby-pkgs@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true apt-get: _update_ libfcgi-dev libmemcached-dev brew: fcgi libmemcached - name: Change permissions run: chmod -R o-w /opt/hostedtoolcache/Ruby - run: bundle exec bake test:external rack-3.1.12/.github/workflows/test.yaml000066400000000000000000000015621476365375000200020ustar00rootroot00000000000000name: Test on: [push, pull_request] permissions: contents: read jobs: test: strategy: fail-fast: false matrix: os: - ubuntu-latest ruby: - '2.4' - '2.5' - '2.6' - '2.7' - '3.0' - '3.1' - '3.2' - '3.3' - jruby-head - truffleruby-head include: - os: macos-latest ruby: '3.1' runs-on: ${{matrix.os}} env: CI: spec steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true continue-on-error: ${{ startsWith(matrix.ruby, '2.4') || startsWith(matrix.ruby, '2.5') }} - run: bundle exec rake continue-on-error: ${{ startsWith(matrix.ruby, '2.4') || startsWith(matrix.ruby, '2.5') }} rack-3.1.12/.gitignore000066400000000000000000000001661476365375000145310ustar00rootroot00000000000000RDOX ChangeLog *.gem lighttpd.errors *.rbc stage *.tar.gz Gemfile.lock .rbx doc /.bundle /.yardoc /coverage /external rack-3.1.12/.mailmap000066400000000000000000000013641476365375000141630ustar00rootroot00000000000000Leah Neukirchen Mickaël Riga James Tucker Ravil Bayramgalin Pavel Rosicky Yuichiro Kaneko Yoshiyuki Hirano Dima Fatko Yudai Suzuki <3280467rec@gmail.com> Marc-André Cournoyer Megan Batty Megan Batty Richard Schneeman Richard Schneeman Wyatt Pan Kazuya Hotta Julik Tarkhanov rack-3.1.12/.rubocop.yml000066400000000000000000000020601476365375000150060ustar00rootroot00000000000000require: - rubocop-packaging AllCops: TargetRubyVersion: 2.4 DisabledByDefault: true Exclude: - '**/vendor/**/*' Style/FrozenStringLiteralComment: Enabled: true EnforcedStyle: always Exclude: - 'test/builder/bom.ru' # Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. Style/HashSyntax: Enabled: true Style/MethodDefParentheses: Enabled: true Layout/EmptyLineAfterMagicComment: Enabled: true Layout/LeadingCommentSpace: Enabled: true Exclude: - 'test/builder/options.ru' Layout/SpaceAfterColon: Enabled: true Layout/SpaceAfterComma: Enabled: true Layout/SpaceAroundEqualsInParameterDefault: Enabled: true Layout/SpaceAroundKeyword: Enabled: true Layout/SpaceAroundOperators: Enabled: true Layout/SpaceBeforeComma: Enabled: true Layout/SpaceBeforeFirstArg: Enabled: true # Use `{ a: 1 }` not `{a:1}`. Layout/SpaceInsideHashLiteralBraces: Enabled: true Layout/IndentationStyle: Enabled: true Layout/TrailingWhitespace: Enabled: true Lint/DeprecatedOpenSSLConstant: Enabled: true rack-3.1.12/.yardopts000066400000000000000000000000141476365375000143770ustar00rootroot00000000000000- SPEC.rdoc rack-3.1.12/CHANGELOG.md000066400000000000000000001741531476365375000143620ustar00rootroot00000000000000# 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.1.12] - 2025-03-11 ### Security - [CVE-2025-27610](https://github.com/rack/rack/security/advisories/GHSA-7wqh-767x-r66v) Local file inclusion in `Rack::Static`. ## [3.1.11] - 2025-03-04 ### Security - [CVE-2025-27111](https://github.com/rack/rack/security/advisories/GHSA-8cgq-6mh2-7j6v) Possible Log Injection in `Rack::Sendfile`. ## [3.1.10] - 2025-02-12 ### Security - [CVE-2025-25184](https://github.com/rack/rack/security/advisories/GHSA-7g2v-jj9q-g3rg) Possible Log Injection in `Rack::CommonLogger`. ## [3.1.9] - 2025-01-31 ### Fixed - `Rack::MediaType#params` now handles parameters without values. ([#2263](https://github.com/rack/rack/pull/2263), [@AllyMarthaJ](https://github.com/AllyMarthaJ)) ## [3.1.8] - 2024-10-14 ### Fixed - Resolve deprecation warnings about uri `DEFAULT_PARSER`. ([#2249](https://github.com/rack/rack/pull/2249), [@earlopain]) ## [3.1.7] - 2024-07-11 ### Fixed - Do not remove escaped opening/closing quotes for content-disposition filenames. ([#2229](https://github.com/rack/rack/pull/2229), [@jeremyevans]) - Fix encoding setting for non-binary IO-like objects in MockRequest#env_for. ([#2227](https://github.com/rack/rack/pull/2227), [@jeremyevans]) - `Rack::Response` should not generate invalid `content-length` header. ([#2219](https://github.com/rack/rack/pull/2219), [@ioquatix]) - Allow empty PATH_INFO. ([#2214](https://github.com/rack/rack/pull/2214), [@ioquatix]) ## [3.1.6] - 2024-07-03 ### Fixed - Fix several edge cases in `Rack::Request#parse_http_accept_header`'s implementation. ([#2226](https://github.com/rack/rack/pull/2226), [@ioquatix]) ## [3.1.5] - 2024-07-02 ### Security - Fix potential ReDoS attack in `Rack::Request#parse_http_accept_header`. ([GHSA-cj83-2ww7-mvq7](https://github.com/rack/rack/security/advisories/GHSA-cj83-2ww7-mvq7), [@dwisiswant0](https://github.com/dwisiswant0)) ## [3.1.4] - 2024-06-22 ### Fixed - Fix `Rack::Lint` matching some paths incorrectly as authority form. ([#2220](https://github.com/rack/rack/pull/2220), [@ioquatix]) ## [3.1.3] - 2024-06-12 ### Fixed - Fix passing non-strings to `Rack::Utils.escape_html`. ([#2202](https://github.com/rack/rack/pull/2202), [@earlopain]) - `Rack::MockResponse` gracefully handles empty cookies ([#2203](https://github.com/rack/rack/pull/2203) [@wynksaiddestroy]) ## [3.1.2] - 2024-06-11 - `Rack::Response` will take in to consideration chunked encoding responses ([#2204](https://github.com/rack/rack/pull/2204), [@tenderlove]) ## [3.1.1] - 2024-06-11 - Oops! I shouldn't have shipped that ## [3.1.0] - 2024-06-11 :warning: **This release includes several breaking changes.** Refer to the **Removed** section below for the list of deprecated methods that have been removed in this release. Rack v3.1 is primarily a maintenance release that removes features deprecated in Rack v3.0. Alongside these removals, there are several improvements to the Rack SPEC, mainly focused on enhancing input and output handling. These changes aim to make Rack more efficient and align better with the requirements of server implementations and relevant HTTP specifications. ### SPEC Changes - `rack.input` is now optional. ([#1997](https://github.com/rack/rack/pull/1997), [#2018](https://github.com/rack/rack/pull/2018), [@ioquatix]) - `PATH_INFO` is now validated according to the HTTP/1.1 specification. ([#2117](https://github.com/rack/rack/pull/2117), [#2181](https://github.com/rack/rack/pull/2181), [@ioquatix]) - `OPTIONS *` is now accepted. ([#2114](https://github.com/rack/rack/pull/2114), [@doriantaylor](https://github.com/doriantaylor)) - Introduce optional `rack.protocol` request and response header for handling connection upgrades. ([#1954](https://github.com/rack/rack/pull/1954), [@ioquatix]) ### Added - Introduce `Rack::Multipart::MissingInputError` for improved handling of missing input in `#parse_multipart`. ([#2018](https://github.com/rack/rack/pull/2018), [@ioquatix]) - Introduce `module Rack::BadRequest` which is included in multipart and query parser errors. ([#2019](https://github.com/rack/rack/pull/2019), [@ioquatix]) - Add `.mjs` MIME type ([#2057](https://github.com/rack/rack/pull/2057), [@axilleas](https://github.com/axilleas)) - `set_cookie_header` utility now supports the `partitioned` cookie attribute. This is required by Chrome in some embedded contexts. ([#2131](https://github.com/rack/rack/pull/2131), [@flavio-b](https://github.com/flavio-b)) - Introduce `rack.early_hints` for sending `103 Early Hints` informational responses. ([#1831](https://github.com/rack/rack/pull/1831), [@casperisfine](https://github.com/casperisfine), [@jeremyevans]) ### Changed - MIME type for JavaScript files (`.js`) changed from `application/javascript` to `text/javascript` ([`1bd0f15`](https://github.com/rack/rack/commit/1bd0f1597d8f4a90d47115f3e156a8ce7870c9c8), [@ioquatix]) - Update MIME types associated to `.ttf`, `.woff`, `.woff2` and `.otf` extensions to use mondern `font/*` types. ([#2065](https://github.com/rack/rack/pull/2065), [@davidstosik]) - `Rack::Utils.escape_html` is now delegated to `CGI.escapeHTML`. `'` is escaped to `#39;` instead of `#x27;`. (decimal vs hexadecimal) ([#2099](https://github.com/rack/rack/pull/2099), [@JunichiIto](https://github.com/JunichiIto)) - Clarify use of `@buffered` and only update `content-length` when `Rack::Response#finish` is invoked. ([#2149](https://github.com/rack/rack/pull/2149), [@ioquatix]) ### Deprecated - Deprecate automatic cache invalidation in `Request#{GET,POST}` ([#2073](https://github.com/rack/rack/pull/2073), [@jeremyevans]) - Only cookie keys that are not valid according to the HTTP specifications are escaped. We are planning to deprecate this behaviour, so now a deprecation message will be emitted in this case. In the future, invalid cookie keys may not be accepted. ([#2191](https://github.com/rack/rack/pull/2191), [@ioquatix]) - `Rack::Logger` is deprecated. ([#2197](https://github.com/rack/rack/pull/2197), [@ioquatix]) - Add fallback lookup and deprecation warning for obsolete status symbols. ([#2137](https://github.com/rack/rack/pull/2137), [@wtn](https://github.com/wtn)) - Deprecate `Rack::Request#values_at`, use `request.params.values_at` instead ([#2183](https://github.com/rack/rack/pull/2183), [@ioquatix]) ### Removed - Remove deprecated `Rack::Auth::Digest` with no replacement. ([#1966](https://github.com/rack/rack/pull/1966), [@ioquatix]) - Remove deprecated `Rack::Cascade::NotFound` with no replacement. ([#1966](https://github.com/rack/rack/pull/1966), [@ioquatix]) - Remove deprecated `Rack::Chunked` with no replacement. ([#1966](https://github.com/rack/rack/pull/1966), [@ioquatix]) - Remove deprecated `Rack::File`, use `Rack::Files` instead. ([#1966](https://github.com/rack/rack/pull/1966), [@ioquatix]) - Remove deprecated `Rack::QueryParser` `key_space_limit` parameter with no replacement. ([#1966](https://github.com/rack/rack/pull/1966), [@ioquatix]) - Remove deprecated `Rack::Response#header`, use `Rack::Response#headers` instead. ([#1966](https://github.com/rack/rack/pull/1966), [@ioquatix]) - Remove deprecated cookie methods from `Rack::Utils`: `add_cookie_to_header`, `make_delete_cookie_header`, `add_remove_cookie_to_header`. ([#1966](https://github.com/rack/rack/pull/1966), [@ioquatix]) - Remove deprecated `Rack::Utils::HeaderHash`. ([#1966](https://github.com/rack/rack/pull/1966), [@ioquatix]) - Remove deprecated `Rack::VERSION`, `Rack::VERSION_STRING`, `Rack.version`, use `Rack.release` instead. ([#1966](https://github.com/rack/rack/pull/1966), [@ioquatix]) - Remove non-standard status codes 306, 509, & 510 and update descriptions for 413, 422, & 451. ([#2137](https://github.com/rack/rack/pull/2137), [@wtn](https://github.com/wtn)) - Remove any dependency on `transfer-encoding: chunked`. ([#2195](https://github.com/rack/rack/pull/2195), [@ioquatix]) - Remove deprecated `Rack::Request#[]`, use `request.params[key]` instead ([#2183](https://github.com/rack/rack/pull/2183), [@ioquatix]) ### Fixed - In `Rack::Files`, ignore the `Range` header if served file is 0 bytes. ([#2159](https://github.com/rack/rack/pull/2159), [@zarqman]) ## [3.0.14] - 2025-03-11 ### Security - [CVE-2025-27610](https://github.com/rack/rack/security/advisories/GHSA-7wqh-767x-r66v) Local file inclusion in `Rack::Static`. ## [3.0.13] - 2025-03-04 ### Security - [CVE-2025-27111](https://github.com/rack/rack/security/advisories/GHSA-8cgq-6mh2-7j6v) Possible Log Injection in `Rack::Sendfile`. ## [3.0.12] - 2025-02-12 ### Security - [CVE-2025-25184](https://github.com/rack/rack/security/advisories/GHSA-7g2v-jj9q-g3rg) Possible Log Injection in `Rack::CommonLogger`. ## [3.0.11] - 2024-05-10 - Backport #2062 to 3-0-stable: Do not allow `BodyProxy` to respond to `to_str`, make `to_ary` call close . ([#2062](https://github.com/rack/rack/pull/2062), [@jeremyevans](https://github.com/jeremyevans)) ## [3.0.10] - 2024-03-21 - Backport #2104 to 3-0-stable: Return empty when parsing a multi-part POST with only one end delimiter. ([#2164](https://github.com/rack/rack/pull/2164), [@JoeDupuis](https://github.com/JoeDupuis)) ## [3.0.9.1] - 2024-02-21 ### Security * [CVE-2024-26146] Fixed ReDoS in Accept header parsing * [CVE-2024-25126] Fixed ReDoS in Content Type header parsing * [CVE-2024-26141] Reject Range headers which are too large [CVE-2024-26146]: https://github.com/advisories/GHSA-54rr-7fvw-6x8f [CVE-2024-25126]: https://github.com/advisories/GHSA-22f2-v57c-j9cx [CVE-2024-26141]: https://github.com/advisories/GHSA-xj5v-6v4g-jfw6 ## [3.0.9] - 2024-01-31 - Fix incorrect content-length header that was emitted when `Rack::Response#write` was used in some situations. ([#2150](https://github.com/rack/rack/pull/2150), [@mattbrictson](https://github.com/mattbrictson)) ## [3.0.8] - 2023-06-14 - Fix some unused variable verbose warnings. ([#2084](https://github.com/rack/rack/pull/2084), [@jeremyevans], [@skipkayhil](https://github.com/skipkayhil)) ## [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 ### Security - [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.2] - 2023-03-02 ### Security - [CVE-2023-27530] Introduce multipart_total_part_limit to limit total parts ## [3.0.4.1] - 2023-01-17 ### Security - [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`, `Rack::Lobster` 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.13] - 2025-03-11 ### Security - [CVE-2025-27610](https://github.com/rack/rack/security/advisories/GHSA-7wqh-767x-r66v) Local file inclusion in `Rack::Static`. ## [2.2.12] - 2025-03-04 ### Security - [CVE-2025-27111](https://github.com/rack/rack/security/advisories/GHSA-8cgq-6mh2-7j6v) Possible Log Injection in `Rack::Sendfile`. ## [2.2.11] - 2025-02-12 ### Security - [CVE-2025-25184](https://github.com/rack/rack/security/advisories/GHSA-7g2v-jj9q-g3rg) Possible Log Injection in `Rack::CommonLogger`. ## [2.2.10] - 2024-10-14 - Fix compatibility issues with Ruby v3.4.0. ([#2248](https://github.com/rack/rack/pull/2248), [@byroot](https://github.com/byroot)) ## [2.2.9] - 2023-03-21 - Return empty when parsing a multi-part POST with only one end delimiter. ([#2104](https://github.com/rack/rack/pull/2104), [@alpaca-tc]) ## [2.2.8] - 2023-07-31 - Regenerate SPEC ([#2102](https://github.com/rack/rack/pull/2102), [@skipkayhil](https://github.com/skipkayhil)) - Limit file extension length of multipart tempfiles ([#2015](https://github.com/rack/rack/pull/2015), [@dentarg](https://github.com/dentarg)) - Fix "undefined method DelegateClass for Rack::Session::Cookie:Class" ([#2092](https://github.com/rack/rack/pull/2092), [@onigra](https://github.com/onigra) [@dchandekstark](https://github.com/dchandekstark)) ## [2.2.7] - 2023-03-13 - Correct the year number in the changelog ([#2015](https://github.com/rack/rack/pull/2015), [@kimulab](https://github.com/kimulab)) - Support underscore in host names for Rack 2.2 (Fixes [#2070](https://github.com/rack/rack/issues/2070)) ([#2015](https://github.com/rack/rack/pull/2071), [@jeremyevans](https://github.com/jeremyevans)) ## [2.2.6.4] - 2023-03-13 - [CVE-2023-27539] Avoid ReDoS in header parsing ## [2.2.6.3] - 2023-03-02 - [CVE-2023-27530] Introduce multipart_total_part_limit to limit total parts ## [2.2.6.2] - 2023-01-17 - [CVE-2022-44570] Fix ReDoS in Rack::Utils.get_byte_ranges ## [2.2.6.1] - 2023-01-17 - [CVE-2022-44571] Fix ReDoS vulnerability in multipart parser - [CVE-2022-44572] Forbid control characters in attributes (also ReDoS) ## [2.2.6] - 2023-01-17 - Extend `Rack::MethodOverride` to handle `QueryParser::ParamsTooDeepError` error. ([#2011](https://github.com/rack/rack/pull/2011), [@byroot](https://github.com/byroot)) ## [2.2.5] - 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)) ## [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 ### Security - [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" [@davidstosik]: https://github.com/davidstosik "David Stosik" rack-3.1.12/CONTRIBUTING.md000066400000000000000000000057121476365375000147740ustar00rootroot00000000000000# 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). ## Backports Only security patches are ideal for backporting to non-main release versions. If you're not sure if your bug fix is backportable, you should open a discussion to discuss it first. The [Security Policy] documents which release versions will receive security backports. ## 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 ``` ## 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. [Security Policy]: SECURITY.md rack-3.1.12/Gemfile000066400000000000000000000005331476365375000140320ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec group :maintenance, optional: true do gem "rubocop", require: false gem "rubocop-packaging", require: false end unless ENV['CI'] group :doc do gem 'rdoc' end end group :test do gem "webrick" unless ENV['CI'] == 'spec' gem 'bake-test-external' end end rack-3.1.12/MIT-LICENSE000066400000000000000000000021241476365375000141710ustar00rootroot00000000000000The 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. rack-3.1.12/README.md000066400000000000000000000273661476365375000140330ustar00rootroot00000000000000# ![Rack](contrib/logo.webp) 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. ## Version support | Version | Support | |----------|------------------------------------| | 3.0.x | Bug fixes and security patches. | | 2.2.x | Security patches only. | | <= 2.1.x | End of support. | Please see the [Security Policy] for more information. ## Rack 3.0 This is the latest version of Rack. It contains API improvements but also some breaking changes. Please check the [Upgrade Guide](UPGRADE-GUIDE.md) for more details about migrating servers, middlewares and applications designed for Rack 2 to Rack 3. For detailed information on specific changes, check the [Change Log](CHANGELOG.md). ## Rack 2.2 This version of Rack is receiving security patches only, and effort should be made to move to Rack 3. Starting in Ruby 3.4 the `base64` dependency will no longer be a default gem, and may cause a warning or error about `base64` being missing. To correct this, add `base64` as a dependency to your project. ## 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 # or, add it to your current application gemfile: $ bundle add rack ``` 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) * [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/) * [Ramaze](https://github.com/ramaze/ramaze) * [Padrino](https://padrinorb.com/) * [Roda](https://github.com/jeremyevans/roda) * [Ruby on Rails](https://rubyonrails.org/) * [Rum](https://github.com/leahneukirchen/rum) * [Sinatra](https://sinatrarb.com/) * [Utopia](https://github.com/socketry/utopia) * [WABuR](https://github.com/ohler55/wabur) ## 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 [Security Policy]: SECURITY.md rack-3.1.12/Rakefile000066400000000000000000000071751476365375000142150ustar00rootroot00000000000000# frozen_string_literal: true require "bundler/gem_tasks" require "rake/testtask" desc "Run all the tests" task default: :test desc "Install gem dependencies" task :deps do require 'rubygems' spec = Gem::Specification.load('rack.gemspec') spec.dependencies.each do |dep| reqs = dep.requirements_list reqs = (["-v"] * reqs.size).zip(reqs).flatten # Use system over sh, because we want to ignore errors! system Gem.ruby, "-S", "gem", "install", '--conservative', dep.name, *reqs end end desc "Make an archive as .tar.gz" task dist: %w[chmod changelog spec rdoc] do sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar" sh "pax -waf #{release}.tar -s ':^:#{release}/:' SPEC.rdoc ChangeLog doc rack.gemspec" sh "gzip -f -9 #{release}.tar" end desc "Make an official release" task :officialrelease do puts "Official build for #{release}..." sh "rm -rf stage" sh "git clone --shared . stage" sh "cd stage && rake officialrelease_really" sh "mv stage/#{release}.tar.gz stage/#{release}.gem ." end task officialrelease_really: %w[spec dist gem] do sh "shasum #{release}.tar.gz #{release}.gem" end def release "rack-" + File.read('lib/rack/version.rb')[/RELEASE += +([\"\'])([\d][\w\.]+)\1/, 2] end desc "Make binaries executable" task :chmod do Dir["bin/*"].each { |binary| File.chmod(0755, binary) } Dir["test/cgi/test*"].each { |binary| File.chmod(0755, binary) } end desc "Generate a ChangeLog" task changelog: "ChangeLog" file '.git/index' file "ChangeLog" => '.git/index' do File.open("ChangeLog", "w") { |out| log = `git log -z` log.force_encoding(Encoding::BINARY) log.split("\0").map { |chunk| author = chunk[/Author: (.*)/, 1].strip date = chunk[/Date: (.*)/, 1].strip desc, detail = $'.strip.split("\n", 2) detail ||= "" detail = detail.gsub(/.*darcs-hash:.*/, '') detail.rstrip! out.puts "#{date} #{author}" out.puts " * #{desc.strip}" out.puts detail unless detail.empty? out.puts } } end desc "Generate Rack Specification" task spec: "SPEC.rdoc" file 'lib/rack/lint.rb' file "SPEC.rdoc" => 'lib/rack/lint.rb' do File.open("SPEC.rdoc", "wb") { |file| IO.foreach("lib/rack/lint.rb") { |line| if line =~ /^\s*## ?(.*)/ file.puts $1 end } } end Rake::TestTask.new("test:regular") do |t| t.libs << "test" t.test_files = FileList["test/**/*_test.rb", "test/**/spec_*.rb", "test/gemloader.rb"] t.warning = false t.verbose = true end desc "Run tests with coverage" task "test_cov" do ENV['COVERAGE'] = '1' Rake::Task['test:regular'].invoke end desc "Run separate tests for each test file, to test directly requiring components" task "test:separate" do fails = [] FileList["test/**/spec_*.rb"].each do |file| puts "#{FileUtils::RUBY} -w #{file}" fails << file unless system({'SEPARATE'=>'1'}, FileUtils::RUBY, '-w', file) end if fails.empty? puts 'All test files passed' else puts "Failures in the following test files:" puts fails raise "At least one separate test failed" end end desc "Run all the fast + platform agnostic tests" task test: %w[spec test:regular test:separate] desc "Run all the tests we run on CI" task ci: :test task gem: :spec do sh "gem build rack.gemspec" end task doc: :rdoc desc "Generate RDoc documentation" task rdoc: %w[changelog spec] do sh(*%w{rdoc --line-numbers --main README.rdoc --title 'Rack\ Documentation' --charset utf-8 -U -o doc} + %w{README.rdoc KNOWN-ISSUES SPEC.rdoc ChangeLog} + `git ls-files lib/\*\*/\*.rb`.strip.split) cp "contrib/rdoc.css", "doc/rdoc.css" end rack-3.1.12/SECURITY.md000066400000000000000000000035051476365375000143320ustar00rootroot00000000000000# Security Policy ## Supported versions The current release series and the next most recent one (by major-minor version) will receive patches and new versions in case of a security issue. ### Unsupported Release Series When a release series is no longer supported, it’s your own responsibility to deal with bugs and security issues. If you are not comfortable maintaining your own versions, you should upgrade to a supported version. ## Reporting a security issue Contact the current security coordinator [Aaron Patterson](mailto:tenderlove@ruby-lang.org) directly. If you do not get a response within 7 days, create an issue on the relevant project without any specific details and mention the project maintainers. ## Disclosure Policy 1. Security report received and is assigned a primary handler. This person will coordinate the fix and release process. 2. Problem is confirmed and, a list of all affected versions is determined. Code is audited to find any potential similar problems. 3. Fixes are prepared for all releases which are still supported. These fixes are not committed to the public repository but rather held locally pending the announcement. 4. A suggested embargo date for this vulnerability is chosen and distros@openwall is notified. This notification will include patches for all versions still under support and a contact address for packagers who need advice back-porting patches to older versions. 5. On the embargo date, the changes are pushed to the public repository and new gems released to rubygems. This process can take some time, especially when coordination is required with maintainers of other projects. Every effort will be made to handle the bug in as timely a manner as possible, however it’s important that we follow the release process above to ensure that the disclosure is handled in a consistent manner. rack-3.1.12/SPEC.rdoc000066400000000000000000000411621476365375000141450ustar00rootroot00000000000000This 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. rack.protocol:: An optional +Array+ of +String+, containing the protocols advertised by the client in the +upgrade+ header (HTTP/1) or the +:protocol+ pseudo-header (HTTP/2). 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)?. 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 may 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 * There may be a valid early hints callback in rack.early_hints * The REQUEST_METHOD must be a valid token. * The SCRIPT_NAME, if non-empty, must start with / * The PATH_INFO, if provided, must be a valid request target or an empty string. * Only OPTIONS requests may have PATH_INFO set to * (asterisk-form). * Only CONNECT requests may have PATH_INFO set to an authority (authority-form). Note that in HTTP/2+, the authority-form is not a valid request target. * CONNECT and OPTIONS requests must not have PATH_INFO set to a URI (absolute-form). * Otherwise, PATH_INFO must start with a / and must not include a fragment part starting with '#' (origin-form). * 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. 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 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?+. === Early Hints The application or any middleware may call the rack.early_hints with an object which would be valid as the headers of a Rack response. If rack.early_hints is present, it must respond to #call. If rack.early_hints is called, it must be called with valid Rack response headers. == 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+ Header There must not be a content-type header key when the +Status+ is 1xx, 204, or 304. ==== The +content-length+ Header There must not be a content-length header key when the +Status+ is 1xx, 204, or 304. ==== The +rack.protocol+ Header If the +rack.protocol+ header is present, it must be a +String+, and must be one of the values from the +rack.protocol+ array from the environment. Setting this value informs the server that it should perform a connection upgrade. In HTTP/1, this is done using the +upgrade+ header. In HTTP/2, this is done by accepting the request. === 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. 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. rack-3.1.12/UPGRADE-GUIDE.md000066400000000000000000000257571476365375000150220ustar00rootroot00000000000000# Rack 3 Upgrade Guide This document is a work in progress, but outlines some of the key changes in Rack 3 which you should be aware of in order to update your server, middleware and/or applications. ## Interface Changes ### Rack 2 & Rack 3 compatibility Most applications can be compatible with Rack 2 and 3 by following the strict intersection of the Rack Specifications, notably: - 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. - `rack.input` is no longer required to be rewindable. - `rack.multithread`/`rack.multiprocess`/`rack.run_once`/`rack.version` are no longer required environment keys. - `rack.hijack?` (partial hijack) and `rack.hijack` (full hijack) are now independently optional. - `rack.hijack_io` has been removed completely. - `SERVER_PROTOCOL` is now a required key, matching the HTTP protocol used in the request. - Middleware must no longer call `#each` on the body, but they can call `#to_ary` on the body if it responds to `#to_ary`. There is one changed feature in Rack 3 which is not backwards compatible: - Response header values can be an `Array` to handle multiple values (and no longer supports `\n` encoded headers). You can achieve compatibility by using `Rack::Response#add_header` which provides an interface for adding headers without concern for the underlying format. There is one new feature in Rack 3 which is not directly backwards compatible: - Response body can now respond to `#call` (streaming body) instead of `#each` (enumerable body), for the equivalent of response hijacking in previous versions. If supported by your server, you can use partial rack hijack instead (or wrap this behaviour in a middleware). ### `config.ru` `Rack::Builder#run` now accepts block Previously, `Rack::Builder#run` method would only accept a callable argument: ```ruby run lambda{|env| [200, {}, ["Hello World"]]} ``` This can be rewritten more simply: ```ruby run do |env| [200, {}, ["Hello World"]] end ``` ### Response bodies can be used for bi-directional streaming Previously, the `rack.hijack` response header could be used for implementing bi-directional streaming (e.g. WebSockets). ```ruby def call(env) stream_callback = proc do |stream| stream.read(...) stream.write(...) ensure stream.close(...) end return [200, {'rack.hijack' => stream_callback}, []] end ``` This feature was optional and tricky to use correctly. You can now achieve the same thing by giving `stream_callback` as the response body: ```ruby def call(env) stream_callback = proc do |stream| stream.read(...) stream.write(...) ensure stream.close(...) end return [200, {}, stream_callback] end ``` ### `Rack::Session` was moved to a separate gem. Previously, `Rack::Session` was part of the `rack` gem. Not every application needs it, and it increases the security surface area of the `rack`, so it was decided to extract it into its own gem `rack-session` which can be updated independently. Applications that make use of `rack-session` will need to add that gem as a dependency: ```ruby gem 'rack-session' ``` This provides all the previously available functionality. ### `bin/rackup`, `Rack::Server`, `Rack::Handler`and `Rack::Lobster` were moved to a separate gem. Previously, the `rackup` executable was included with Rack. Because WEBrick is no longer a default gem with Ruby, we had to make a decision: either `rack` should depend on `webrick` or we should move that functionality into a separate gem. We chose the latter which will hopefully allow us to innovate more rapidly on the design and implementation of `rackup` separately from "rack the interface". In Rack 3, you will need to include: ```ruby gem 'rackup' ``` This provides all the previously available functionality. The classes `Rack::Server`, `Rack::Handler` and `Rack::Lobster` have been moved to the rackup gem too and renamed to `Rackup::Server`, `Rackup::Handler` and `Rackup::Lobster` respectively. To start an app with `Rackup::Server` with Rack 3 : ```ruby require 'rackup' Rackup::Server.start app: app, Port: 3000 ``` #### `config.ru` autoloading is disabled unless `require 'rack'` Previously, rack modules like `rack/directory` were autoloaded because `rackup` did require 'rack'. In Rack 3, you will need to write `require 'rack'` or require specific module explicitly. ```diff +require 'rack' run Rack::Directory.new '.' ``` or ```diff +require 'rack/directory' run Rack::Directory.new '.' ``` ## Request Changes ### `rack.version` is no longer required Previously, the "rack protocol version" was available in `rack.version` but it was not practically useful, so it has been removed as a requirement. ### `rack.multithread`/`rack.multiprocess`/`rack.run_once` are no longer required Previously, servers tried to provide these keys to reflect the execution environment. These come too late to be useful, so they have been removed as a requirement. ### `rack.hijack?` now only applies to partial hijack Previously, both full and partial hijiack were controlled by the presence and value of `rack.hijack?`. Now, it only applies to partial hijack (which now can be replaced by streaming bodies). ### `rack.hijack` alone indicates that you can execute a full hijack Previously, `rack.hijack?` had to be truthy, as well as having `rack.hijack` present in the request environment. Now, the presence of the `rack.hijack` callback is enough. ### `rack.hijack_io` is removed Previously, the server would try to set `rack.hijack_io` into the request environment when `rack.hijack` was invoked for a full hijack. This was often impossible if a middleware had called `env.dup`, so this requirement has been dropped entirely. ### `rack.input` is no longer required to be rewindable Previously, `rack.input` was required to be rewindable, i.e. `io.seek(0)` but this was only generally possible with a file based backing, which prevented efficient streaming of request bodies. Now, `rack.input` is not required to be rewindable. ### `rack.input` is no longer rewound after consuming form and multipart data Previously `.rewind` was called after consuming form and multipart data. Use `Rack::RewindableInput::Middleware` to make the body rewindable, and call `.rewind` explicitly to match this behavior. ## Response Changes ### Response must be mutable Rack 3 requires the response Array `[status, headers, body]` to be mutable. Existing code that uses a frozen response will need to be changed: ```ruby NOT_FOUND = [404, {}, ["Not Found"]].freeze def call(env) ... return NOT_FOUND end ``` should be rewritten as: ```ruby def not_found [404, {}, ["Not Found"]] end def call(env) ... return not_found end ``` Note there is a subtle bug in the former version: the headers hash is mutable and can be modified, and these modifications can leak into subsequent requests. ### Response headers must be a mutable hash Rack 3 requires response headers to be a mutable hash. Previously it could be any object that would respond to `#each` and yield `key`/`value` pairs. Previously, the following was acceptable: ```ruby def call(env) return [200, [['content-type', 'text/plain']], ["Hello World"]] end ``` Now you must use a hash instance: ```ruby def call(env) return [200, {'content-type' => 'text/plain'}, ["Hello World"]] end ``` This ensures middleware can predictably update headers as needed. ### Response Headers must be lower case Rack 3 requires all response headers to be lower case. This is to simplify fetching and updating response headers. Previously you had to use something like `Rack::HeadersHash` ```ruby def call(env) response = @app.call(env) # HeaderHash must allocate internal objects and compute lower case keys: headers = Rack::Utils::HeaderHash[response[1]] cache_response(headers['ETag'], response) ... end ``` but now you must just use the normal form for HTTP header: ```ruby def call(env) response = @app.call(env) # A plain hash with lower case keys: headers = response[1] cache_response(headers['etag'], response) ... end ``` If you want your code to work with Rack 3 without having to manually lowercase each header key used, instead of using a plain hash for headers, you can use `Rack::Headers` on Rack 3. ```ruby headers = defined?(Rack::Headers) ? Rack::Headers.new : {} ``` `Rack::Headers` is a subclass of Hash that will automatically lowercase keys: ```ruby headers = Rack::Headers.new headers['Foo'] = 'bar' headers['FOO'] # => 'bar' headers.keys # => ['foo'] ``` ### Multiple response header values are encoded using an `Array` Response header values can be an Array to handle multiple values (and no longer supports `\n` encoded headers). If you use `Rack::Response`, you don't need to do anything, but if manually append values to response headers, you will need to promote them to an Array, e.g. ```ruby 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 ``` ### Response body might not respond to `#each` Rack 3 has more strict requirements on response bodies. Previously, response body would only need to respond to `#each` and optionally `#close`. In addition, there was no way to determine whether it was safe to call `#each` and buffer the response. ### Response bodies can be buffered if they expose `#to_ary` If your body responds to `#to_ary` then it must return an `Array` whose contents are identical to that produced by calling `#each`. If the body responds to both `#to_ary` and `#close` then its implementation of `#to_ary` must also call `#close`. Previously, it was not possible to determine whether a response body was immediately available (could be buffered) or was streaming chunks. This case is now unambiguously exposed by `#to_ary`: ```ruby def call(env) status, headers, body = @app.call(env) # Check if we can buffer the body into an Array, so we can compute a digest: if body.respond_to?(:to_ary) body = body.to_ary digest = digest_body(body) headers[ETAG_STRING] = %(W/"#{digest}") if digest end return [status, headers, body] end ``` ### Middleware should not directly modify the response body Be aware that the response body might not respond to `#each` and you must now check if the body responds to `#each` or not to determine if it is an enumerable or streaming body. You must not call `#each` directly on the body and instead you should return a new body that calls `#each` on the original body. ### Status needs to be an `Integer` The response status is now required to be an `Integer` with a value greater or equal to 100. Previously any object that responded to `#to_i` was allowed, so a response like `["200", {}, ""]` will need to be replaced with `[200, {}, ""]` and so on. This can be done by calling `#to_i` on the status object yourself. rack-3.1.12/config/000077500000000000000000000000001476365375000140035ustar00rootroot00000000000000rack-3.1.12/config/external.yaml000066400000000000000000000006741476365375000165200ustar00rootroot00000000000000protocol-rack: url: https://github.com/socketry/protocol-rack command: bundle exec bake test rails: url: https://github.com/rails/rails command: bash -c "cd actionpack && bundle exec rake test" roda: url: https://github.com/jeremyevans/roda command: bundle exec rake spec spec_lint gemfile: .ci.gemfile grape: url: https://github.com/ruby-grape/grape command: bundle exec rspec --exclude-pattern=spec/integration/**/*_spec.rb rack-3.1.12/contrib/000077500000000000000000000000001476365375000141765ustar00rootroot00000000000000rack-3.1.12/contrib/LICENSE.md000066400000000000000000000003601476365375000156010ustar00rootroot00000000000000# Contributed Materials ## Logo Copyright, 2022, by Malene Laugesen. This work is licensed under a [Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License](https://creativecommons.org/licenses/by-nc-nd/4.0/). rack-3.1.12/contrib/logo.webp000066400000000000000000021151301476365375000160200ustar00rootroot00000000000000RIFFPWEBPVP8X 0/ ICCPlcmsmntrRGB XYZ   acspAPPLAPPL-lcms%M8 desc0cprt,#wtptPrXYZdgXYZxbXYZrTRC chad,bTRC gTRC mluc Display P3textCopyright Apple Inc., 2017XYZ QXYZ =XYZ J7 XYZ (8 ȹparaff Y [sf32 B&nALPHb'$HxkDYI KI16:4TĺmZ @eI|8\8F:cDuZ[/̱l)Ds#0)CDHr5}%AZI$(C BFG=j]&?DR\{,?$[f*1Z,ڳ yג`qټ.F@BB.AB:7gvgfۈOeiMsb7wm;#7͖ٲllk5ת:_ a=9Z6Mڶm۶mnSMifͣy>83gvs^};@ `R2s_m.ݹEwwv-bwa "!ݝۻ/=os}WD'kD?#ۭSA w{qЃp]ZJqi)Rw߮$߃$dfg3SʃO?]9ɶm{jz%޻`DD" ҫt *x(HGAC=좤^=q(VTjH2ͬ=wM3`Dr#I ôhf;f*;ɝs03‰sa̘cƵ3G9WkVLDwj֒Ci ҖRRRJ]+5\`5.k3syM|_߈Om ٶm۶nٶw϶m[۶`13"cuW9f+?ERkuwwwwwwjfװ q!'z nY]Vg?ÿ??S??????> _ݗ_NcDϧ)EkW R??????????)?SO??-w*)?#yO㓹~Ԣ̔ Nܩ |ReT?P E)AofEU"\4 F=WO@NtIs+JCW|Jvo4mSxٓo/* 2Q*5l) YY=%ž<{ծ5Sc'i9hLNVJ573J,T.С#XJyO(2ooAAobmˆcRs3 )%s}`}ʈR;WK#dttgK+ߚϮ?eSK2J8iջ~}cxRKvmcyb`w36#xRK6gnqOpj߿]3@N/!ʐc//H`RyWNlI/mRGOzހFb̂嫾|a}_n#/|^F­x嶓dd=;R/=fPzKpM匘>Alx{g)E.;;k4FF7kO)՗0~Pɴ=?rP5P1ʂ+19r\zrg+5FF+vƇv{m_ ˔-6]!0u={g++B aX}gZ r޶ Ted4kzIzB`*xNGQc`Qiz%i r w} 02YlGO>RuuW@H#,Ӈmnc.}>FV=fR c/ S+7䴣;7`qԲp3 htx [-E8ٖyشgctS+N3@JM gASmkJGU9M߮lOMϸ-u*neU_wIrjl-C3g|Jct|ک:!xzwk:w\b _/(d1ca?͛NSBȴ4?vy521 ^pjvOّ"$<9* -t 2ANa&|?Q=ƘѰZWv!-, [< 4M Z'/=3]J KM}ZFTUQi|)Ǵi-S}O{*##5PHQA0 FWUc|uEcd̲`n8h4RB :گVTŋ߿A`KYJ[u|۫KF&aػŻOݑb[ny-. 22k݋޺nj0'lw14Ԭë& l ݅ťC}s_f|RKj^{Ȅnھn9E&LG~#37{6GjKc/~|΀F?R [>vjd.W6($ [~?:p܂JfvelɘґZ :oVL5q' )pdOz્U*#v̺G:VrNΐnqmBӨLȆ-rGTFf~V4Ly( ;t>WYY[S´|iE336DTɨ\)5!mK+CTyr Bkfy?\h|Yrw)ODN*-Q,Mevx! ΟR$Y7`y5l>ⵚ5:(xZkE6ǰr^qպCΙx_5":8- B)Eb9"bK߿_[zP)"u]rcɮ/|s #ӽZ;܏ihTo742`mUqIP}k_z Y4) t[dd^3_xN zEOVQ9R*;c_}>dj'>]g ʷ'ʖ Q;:Qj3o$Kh,cC{}xC"OIV OgVw}*B3P:5 Oˣ.H}˿ZYGfq +48-'2ϻ+ ]"#购[:!jOU5W]=jBm3VVXO7wwlck|g[?|‚B~+5NST(zw+}q@&\v>-) 1K ;QRJ5YPKQהZUXscrTiPi5"c,͒Ra/106'wKY~?PNi&{YҖLFid=ua[gJ2rȚ,ޙzLd]V^ɞZL3ڬn=u̓gm{ZJ02mY'eb/cdf!JFjV )eNdd|gD:ߌŔ[޷gdݮaRZKKdxf=u,;뙭)` d W=]>YC+SZy}%kURKsm=|%]zotL9%{Ⱥ3ȃ)JQܸ^#;+{{Pz(._RCDT٨\)9s %nm.sv𓏗cQg')8'R"u3;k^ZZ1RhOĖs—JlkSVڵqݒ-^&PDN.fR4}%v.4U4k2(8R%-^A1MȚ\:-/"kI!O%뿺֮i B3:"^ivnFG d{nƔF֪tDAv>Eg١7d&"M9DG&,ym[J=/tBʶCZh{=1ӑ)H#^)# _ wӰqHs?SkLpF/<"1&DpLǨ# lV tG{z<|1N) 0v :TG:~Ɂ1:r]0}ǎl0AK4ςs;SWma #Ä%gzǁ~FgSPF4a„R28?:xu q0}"h~o̞?3:7 =l!|ښŻV~wa[|"<*E.,_xek=\ɎP~C8~J5'&."R(>ouWYWY[^/p2QLK{l">L"|憺ꚺƖ:qK `ɱWmVᏌ;RR榦xJ҃o y`Mr~>c:++)u5L&SiM2589t+.lw"й*ԘYꚦ _ݽOYuo q|brט* Nnec)\6ͺn@G;rGPq-;\DwGXD8f)SAtsʟcaǥr]qCep2>8B0b1D쥌(Iڽ%cwDߺNR_;F,5K7R,J}[n')`o]26 (kSt[/ c ]ZoQSS([e&S1s,~߶F!‚x1؉yZBW:S=*/W88櫤F BRYm0e 1'#9>[w>xV߯,;?E\lQkuIQ{Y~Q`|Ŋgolx"”$=׳p ޭ8KjG~)IĞрva>S{ϱM=Kٍ| C(T#JoNs6/a4~ii(!Ldc{l"鹈Q"svzO!T%RU4M N '`!|&Z94vK8[rT@:5L䶛$}A)D~q/EI+MeFfkNlGC(̮C߯gGpRk701)qj/1E)qrTG=KT7=5e5]rTZ^ 5#$ ^ @EA[ɓ6lGt=[sAn ݞG@,"f)>!|DpKNJaF2މ. D"@ xTG԰?vaTBDزɨ b7RzP~)a4De$Du.!1+%1Ѭ+Dz$( ˍ B'?/J^4 Q7/h֯AQ%lVb'WC/ItֈH[[m?QWmCh쳕I"^FDlZf|QBNaO컥% Q/q{H~Ye|Ύ!|D"ب<>$DD{/j?ˬZl3j)WLIBD ֟=A)oI!t`%IBD 0ȈhFJ63IpFӥ Qk) Sk[dN_T$ &Km,wќs^ j̬ևJsԀ>KߒUcJ)a5VWU1sF"ԇ1P`L}|2T.P~D>8LP(3b4/dؤR K 0{N>"JRTQ2e]!J)2}laֿxa 3IDܾpS{i]2꧿ N8s2v5[.v~* Np.B=zpG|Xv_]r t&N h [O/3q;,PiIfeul[{!$U+&Lgݜ e9IAA7sBnV893-(AmJ#rE"IXcq֥"'887ɘPrC읈|!&:( 9WtX \c[}}:I|igy/9H8o󋬌Z8. ^7DN{h2֝9q#q_Jm C2?`$}ÿZCʺ o_,`ۑ%U τ2 0%I2Uݖw2 QN:BCW4͗ɠ+zqj#ɜگgFˆA & Nq?ڀ0dz @$UryNgo#PŖ̕ X=;`HC}@$Ulq/Hz!8Ovm `'Ll9nC4 w0SS׬ +<9kC[[$ډ;IUe㧋NAPK+e2ފd;.ZSyT^Hd=i^G~wqʒ1o3  $7>Dj C?ݛ-b6iBaHMnū~lx䛶2mU1oJXT՗~/Aw˪`p#s IqG3\*glxU43z|(}CjpCTֈgJpgͽ-qݦq @0*>קVc&a)/S:dB)F}SOi>b40$I>4GɥskF b>$s IUDE,^%C %7a] ;>ujjfgPf ˳^n}On!M_*9[AHq7O1tsDR%ά_׽wf(&a3iǦ6jliƙ͏l;'T/4TQ壔rmCDԲomP ;H؀_W5 gM 9qt[ߐ%ox'[(`4qu&? ǃ _=|H@ kG,K %aSDG\drtzI0_-"HU`oI~1r*жFXil̻-|O97!;HZs| J{R6q%.BƔdeÿWR7I*_Ȟp~xG/kYr@_s$)ʔܹ69#dբuݖnزHQr6CP촺1&?`~F GzzkUGOS%/Tޮr&"޽bTr@ *ϱ#UZŃ %aSDݱ %MV^fKy==aE?zpMq/ϟHe(D$lcZ>$s Izk#ՖM-oX#p܄MQ4$-[văB?uD+32T+[.䠙h8i?$_ b'"bOz ; VFO5mjP%_?#$7aS)ɛ4lkE v9Nw<egu!Mo΀.Hn#+;g nۮ]j[%/~t N)B=|~vz٣"uptٷj#Ԉi\{dy}Iyn[M[Y>$s If[//.RK75%.P*$ ":Ⓗ=d>J[n@ZATmJ%಼گẆd"iov_Wt-كɺ]$ hJv ugF8ʂt([S<ё^Yڃ؍tФ";Hg/(RWqU7~v&*vtzdϷEvg#| Fv1ށŁmI{†IҸs$v _;:+7V^$sBR)z:ً K=WDArvCz}~غO r#pzwS]i[kj i6ԁqڍ;{vr@w/^)MeOn®nq+Z%gi`[wrD 9NX J|MA' S0E?uZەd E2w:|״RT]R6M}trxIٻ Z\"n/Nqbq~zaHS5U U ڛةQ";wVVR5]4րW[Dz;#!5`_ LiZv׎GQDDMqZˋdBR%7%`Hk:{2Q# 쓛+"NzYRrѩA~3-=qkC{x S ;[??+w!N|}Y_*uɞlZEbM/vuP~X@f/<gަ'E4)i/łO."|aeʋd!߲R%{a;' &7a[)Ii˧ajo%Fmc`Z(ͽsJ5q4t~MI|_PDDg}xٖUܹcyRJlúlzstOV 0 4(#"[s |Ik#6' i6}^7e$}Phoسs̋dBҪ) %{R)Q.`܄mG}U= 3Dno#G`_k;N|X0S#z yK(w!ioqŸ%_nx6%McJ 죲"sZWi% kS-ZnATyo^a";AHg^(`\mN؏ǚAO> ^Ww_'lz7dH~%ܾApWnڱE2w$U|Q/ZWK®>չھvXrā 4U RJn3+y X14l!*l+ۦ;%ӽG)94:>ic EVز DmWQBҪ[zGv-Kn~>۬2頕Y@Tjgwd M^q LϸY/_YJC5ER[ mݳE2w$U٦|"ׄT[Ɩ$Wa?w9ׅ}غsp wTEI"M|¨ϠR&#Wt"' L*y Iqk 5hUC l/b.vus\giJGx.YiNBF)E޳;wˬ.ٰRyo4VMr]2%9gQrY7}3㐓7d0* -,@u3:I-v-y@2},+@˼׏N~an8&S (Ik5N> -٭}kY.YwMBe=7QFj\0#qWp=bܹHzǼzڳ__1@: "k4HsBF 圷4c,~92 ֶo6K'`vG#"38q_4_ӼK}PolYRmе];GW.y6#iMIX&`܄"4 `1 <[zyo%u"v)Mrevb6вMDٯ:LyJk~ytUrKZ*90ozERO{du2#`fqZ{@aQ)]{Pl@sNDtvWnSmyfĠ#YbmP˚.JAdJslٟ"bM"rJ:dK>Si@ x>]c$mF2s-__}VM8zs5[ۈdG9?ҶG襤=,¢F I hi&nE2uΟWI~ErȀ_ %B<9=[ Z^ԼinPd",FN؜f=fZ /U\<`FҪqٵ-v',`\`?Iq@]F#"v+^^=od5 bϧlM˿uL]̃d0#FNsړT7HItT\gĬUY*"̓h{/ӯ RrNp@ M +@!"aP.yJ?ņkCZ|* w=M:گB6YDGPNw8_w[Fy}EX,q~sX?~ "?_>{Xr;\2)pRɪ) 5dG @ǰ/|`L.ZIIY@aQse}rVE2u_Wh}*[&ftKgZ\PVT7~",?Cr.qh<? dGɮ}{\<`FҪ嶉km߷菜IޏLV967I_azͧzz `^=6;EGF~ȹqʰ /H#"NïϞ2(\$mDRUly-k٩?r 'Ek8ֿhG=dKf<ԝUQN~o 0W޼Ar7J#u;c7R.*כ׬ Hu5)|:.9# yzn/HҧK#6S^|/d"̅łD$t;w\$;1丧W.,+}S8sbu\|JU[[Vu ]1=MgyFYDܹj97d"d'?~X˝H{5o18苜P}"9ʆ_O(XYFU>@Y҃]qWS(E+r.m:*-MDo3\Hۈd~Rptߘ/r 'E /K2ռձ9%&%st`E҃Ye8-zU~Jo-@a:sj *ȎKe#超nܪ"HZU;Y[[IOzd}S8*b/nu\rRMq&Flto?'e=$شt ŔldOc3Q˝^{H#|nk,eZs:E_*%;*YJu?]JlOrP;J+,tεe&tQ HϗNTF᧽Xն7VFNn/m8@K=]7oacdN 4ܡ MnP]d?O 0sCȉh"~|fy6"{SsJU ?xrEo䔡3?^__oV8F?i9O^ZHE8'PŌADtfw%"Hƻu)5g J(z# Um}kSjJIaC\ R @K4Ӛ'WPRn*@aR\O #naU_RgSDNRSTRMv{8I2`sg|<Ωl!Wy}Ī.] `"@m{RxBWmqgZER?3LHob.Ȓԙ CnjZ:U#tYKV5]K{ @Uyoj,kjd BTQ߫]KGQ%F?Qz+O$1~KI!Hw-Lߧ0Yq'¦Rtރ%lJ7|؛~cن˓ i+O ?Cbr]E/ktFȻ-fe98*`"D2'~m%?o钰A*+OxW]XD+\mH:DuIk uH.:ΝU$aHÑ#?mYC-ԺoݶX DddnJgK(_.a}*+۝j{QĿ#h/ pZ>6`%ljȉa9w6E1MK~НM3=x@=iBҪORS}tZkYUWr=>F"dDSSW$swG%;fߝ$aHKZBMs^9wD LHїTWzl!9!\8Fp|]r"CIFG.Ns̎?2AWJG_{9G z|ٯ.m.U* g \&Ή$鯶2+QMو3&=oW^pT_b-T 2""Du\awxȌ }c#*]m?V]Wxvcp]WrN\PEOZ Ն9o?, Ag2'U|{rD]YJtB$KܖH\}zTɍ3W;B݅$ _l iRcje_^1P LH&zSDͿk@]YΛR+[=J9.}(6VW:?/sAÛ(?%Jnt߷2'޳sC/0!|C_9Hݝ"BTQg7Ti$[R#k!Dc5Y]8ή$ Cw|D˿|o`tݿ/;$IU;"Gչl"Du\ݮio݉7!0_*?ᚇ[xj'Iv6+s\k3k?9{xT 3/TUxXQ=U[ ?xi /xa Q0e~.RС7tHUϗy8=f֓i]>7`Ɉ][.<Օ A|"˻:!9Ee s", 7!k+̩f?*:츄}>~q!}{Ow$6^l]k/oܱ֢;/T5q[[TM_Lm'#BTQw{ѩשׂx\Av }tY~6aOH7_*MRjetz#*7TJ75][rv2&#h?QhׇⰙ;*o#nMuf9IjomG$#-1MzGDt묗N^ ~Af&Jf>G][ }A2,ߗ"T#5уPM9kRsk^T$_{2 m5OP{ǝ][UYt^'<7v"Qzwn_((3ብ Z.!xPsGm=*nx\JlRcZϢf!ѫk.1XCNG}׌E%{9ocdp^!N!" Ng9'otM ~pI5/mȲ'Ttۏ??϶E}gP@? 02<}gE ݭ Q" z<#@z lc?}U7%:+mF7)"6וֹ6Tt?jk>ll ru2?T%C{4D2qb!kgm%ٿN+UH)tzED2KݲSEw^sW(n^e\E_^^QZ5qj 9숈CveuN Ee^Ɗ$aHߞ4H5""{X8ydՑ ]; @O6Ukdn6wLd,V8CT\;a<_ov!+QxOrG}k鏗Z\ѝn?'_UM7 KKV E$u?{̐"ɼCR_=܄l{&dDOr˕'uv()b~8 t<-*]{± aHo["dCw#+ &DNlR=$Sا 7a]g'N{Ƭ &$S1<ƜzoJ_&/SAw-?gD#醦޽mΫg,W$Dd@lyo5+~kIM"ܪے5S.٪Po[?g}SRu);ts$9y9="{ɿj@:?)Fty5=O4Kt?9%\]Eyi@ ERfo{dN{w5o2 r x[5s"`W;"DNJ+8'>oDFHe"$Dd@Dɿq]{@&jAj{w ҷ^=:75ncSDWl)@ZhJIOӷ˭%׏4w<)ADFDҶ} ȴ—,ݣ*ʐ~Y^³}A֭CiNV}ht}l"6Iëgh(&7a7W%oo_ce0"yzIUs A aKYpE7[qT+΀긷Hfلwbd^daWtﭵ*ö s%N?Pu=y(t4~i=Oo5腕wojG >ms] `;yp5ۤ!M_f Cz̑yB2bYZny+G:?t/?)P Vb=n>V@SvX~n?}coHۢOnܺ"Y0pTngJG)N)[ٚ],tL }T OoŻR ->o̪ܷg$?tVZ`*Brq#zVUx =#* CzWYZDn&v ޺^Hnsݭ"N)*f]s?.б3_YC[ ~r"[~xŦՊd0 \yz/boYR:OYh׍_YV{&g[":ð?7<dN߮wLbrw)"icPwzeΛ;Dr%}#}77 0$sz[ۼ(EC#DĎQ`;ys+łv(uDjs@GJ.KO{k!ӻ% u?LUu. EDɲVɛ׺F sSDῷ15S/-![ }z ek-st>ef?wHwp^YWGm kVAlQW:b&vdNaeewbfuG`*ѿTn'f/sBNөs$'t2FÂ:Ø? N1jNyDz֢MA1"lhp˛1.+Hg?Ltxdok 2dz$:/VC[5CBШc}m g?\C2q?h)J%#q7JYTf*gD ݭ 9Gwq\?:TirQ!jسH3$X"1V~*Cdj&g>bw7IF 1[ 9CmlJJXxEнBx_3Ęg[|7^l18r =HӇðh _"qSxwFT kZUDc.1ø˸{$P}qw [ic "qv 4 {03"\u[sDrXf`L0T g$+*ɯA`r=Kv/@&(FOܿQtR> 7v\ٽ>0&s/Ju!YUqć0A1)A cLueֱ6gG9`L0#߫q8C-qc*,?iE`d;k\YxBxiGHlPc1S#c]ҳfVdihVaVH<V`Y%לkDEOkn!`3I[u|_:.Vpzy0&sxfY"/S4\]AVa&uWF-oOCxZ֨m-*O,Q#ɺtTLkgq9 Ip9! *+̛+X#,EmZ wX1'u۾2 d [gqz E ],0Qw+FbE7#ZK,U#~L]8Cbߒ٣M:3 c2A Oe#t^B~3"La3v}#[akQ?jC)5;Fauſ ޚ欭?Fa=Z wОձcX#c{"Ec^sasD~e :=v-0%Sz~W ]Yod66OkD^iiow4EnQ8GY:~)gk*0&s6mbQBVa]4O3C` jj{yB.Pd,"l]|/9 nm]>C%DVa=t|vR-jF V7F 18G3W]&t(A=K?N”La(|XmѨ^Ŧq˖[ԣ%V`ı|Ҟ[&l7U\ 3't ,vjsӇԹm^P8,fSl7p{=a%Z4gI JzBJ]NaЮ9;gXS2qWq[!AUa }\Iiӆ[5° 76gMf+! DNX$ix@(?8+K)0t)n׏4'nN0u1f[u]Ƽda]J+x9㌠h౜qD\6&Ԕj?)u{UfŒgzaXql*g Y SSXWV ^pbֈ |gh 61'@y_u&[RXiBm) ՆUJCCʶzC،_|v`7%e{ץ˅B6źP,|o c2ayqWABs$#71h6On)k=KnF0`NpxR.L4^d+V_ʸ%3-)#S xe0_FN< }CE٣eV`.<5HbU'A99w{ovW`J0 C FRF3Co{՟.~}k-~nOp5c9޶ %V}tC-6:U~ƞ_dw9 ;;*i2mW47CX#= @~sAT7;k.=:;1^3N̑)}mAUU5EVC=m҂!nemN*o fPd_ry;|ɤo^V0%S^S@.HGLa~x$PڞVyVÔb:dּ v\"P>#Wf8>7}d~::h{0%sd?ovē ywvLWŴSk?v@;9l77Lz[˞”LaXxT퉕} 9ns@ ]SqMzy(aDZ2а0udfzC$ z[eh|*!mс0%Sdoņ Y׶ҥ`ȀfZ*{@JKNX"<ӂuj"ffT][!iQ=o@Q)t$=aMs;chTOuN!Jej=(%x ,KѶYY@L$38 ]H^ O z\S' C]cQIo La呅ʐUnU0v[R`YKV`cV ]u HSLdÿ\y/)Ɔ *xa@WhەTUL~c2E}5c0ylU{e & j,QZL;r{܎8,ל/e'0|ǎTZcZ 92.RyR2xOo2`Ul;,S ϖ[5Q;8mɈ szW}t3*o]1)2Ő6p[)Vߙb=2!p>VKKI;\667^]<#7n *<#4nQ92MP+BaJsYn=-pr`PDCpqNAFR :_^zYC\>DζٿwC2aǖpVChMs+0w]VW#QuswRZÃpxI0[¨hA]uY]cdLe/rP}hΗ4K]Z%DžN_jVG*c' &Q QtaVw#z3^92!sPVcgh)RZFC禐?vq$׆(P-:UrKUٮ>)2Ű+]swԽf0fm)6b+F7;픦DDZuyʘ y'G =#8+D%;|z+Rtp+FvTݺO0'SdFNr\CeO`̀]KIouX" !b1(1+;(T94}2L[u3VI/? YžwO9ݞX% 71*TZbK (l9~6NX?+DEC{6߲?9"SndȲul1m)/s21*n ]lV0=s%Ru ˦S+FˤC2V,vhORqC2F 0'SdFOqh`!j[3`WHA֒s+Fb?ۭ7r}ˌ浗T\!ĥ-JV~ΝeHrn/O2h+pRe:ek(kAߵWѓRN]+DhvcDZ!< s2E n/h֮`Ls$)zVVKS۱lt߀aw}P]"zŋ}`LYz܎FruKߑ)"\ysN6,JoRώ@S3dXy2w(:@ǗbH F_?Y 8Ъej/%]V'o#!v1d0/ QV%:齖W9"3zDU ڣUM SQ |zK+ފX" 'AAeRx<~Y hmeH ngi(gUØ߫Rڴ`fk$Xe s#cEYJH24Ii)H$hȿi 4W}$5 RVX#l=#޴6;UFǔ W{vמt(+pr-)]boX$Sd/L3JZ<1JMYaxL\n5t[sg;%v\n:NL$6zi`wwȹؐZ. 8{tN&<ՈF`[KKt0xZBjsɫDn.{iCECdIb+Fdۈ˜+ґj6h&oΔ?|q;$НZ@>U54/-٪I?O_wR1U'tZHfʄFuV.6:%h0/=< QMc^`[բ!|I2) W뒇rdN/'iF~bKǫMDLhQk !V6QxRN$N`/›~pR>g45SC\ +ܓ&45d<9G ^{q;*9eRŎ;I$6}e8ZJRDwiR2˜cHwy i|TT>0C bcp(3Mvϩ z-{a!|IZBul)*Evi%N3`k$WkD#PYg#UtC"$Z͗gtfj—͑p[)>)1v{^rO#շ, mɏ/W=`E[;؂aElf[Ѣ!|I2) ^ު4}$Gtv$p7!Lh&ϙU AF#>!wBۍOd:!,e%z$Yw˯nʌRzl5Vݘ9.$ZLG h#{%/P~uo;4D%;k;2ACj U4ZJsb%c=U"s˄F3A*塡!wB=vݱ_QHu3Kh_,?k-:t$=MFQM13`s$r3c&ᷩ1ՆY w*<'ΎpYMMKNRJݜ6 J_cQh2#_m*ayĜ|vL-d]NECdO8ZJ[[EXd鶓 cAoIDmֈF`}%H(O*nePŨt)|I?~GKP? g1fIdm|u1:Ѣ,|/Ivk"`90f opˈF m%aoA.S瘥~9"dJ&)U/ψȽܘYz0c#{ #=_h( gẻWB/M־6@*d?j)Yoɏ!323`TiD#g,WܣY;2i*T=TJ{ZE(nسR0fH yĕm%dm5И;*̊켌>N*LyVJ="4^Ǎ 3%wNwJ'#hPcҡ,*[dLSJ"{piy+iHx2!M@dAr#YʠW?4d )q63}0W2 F|\*^"5ژ@Q.QJ3"9dطaN!%;2Rɍ/@ Þ].oέ0TVc/Y,_/p$8!lXE3oP "'~nK m c\F)#jIN{Rê7GGCr{Q=1VݺTčh>(UTky y:ϋ˹=fbdØ{\/EnMxDqC#: twqnYj`LsD[^j#5dJ[ !<F:YN:qۍ0ˏR ۯt7X>vi)b'F_kn)~[ 2`_8vjD񣧕 ~wD'+u `c b0eʫIglfFcrɁ}-Gл{uJn[(aShF{)#ju"7S*^#tЏ ng?#J_gTۊ0(#aKi!tFpKϻ@GPvnk1I=N+F4>Q_RP΋@vڪG)븽ls 0eJ񝽯҈F`- a#F];`ر_2'"g#"{kg2`6)jD [KU}#?%c^^; #Ѐh XsSF4aKVoCN.}DtKT9`OaaI\y.d6M@wN`}vˍ8j;uJDlq"k0_W0nj+T),tEPx3>Q tԪ`.wCl9*+ Pʑ>uAMv{D-ne޼O."ӎ[m=㦇_\Nl7! 0q)3wu2X}O'E0`M󧼲oY:b%:;_Uyj$\ X}J)v3"M )!CM&O{Q­~n3zcd)~cOG2kƙLm]:=`3nٛR%)唇n3~9SQ?e4lhF[wj҃d=۶_؈lyеF-9z1F3n)ͭ~hME^ŐaC=7B([ߟ>śǶ< > 8ehrG⮼Kh0@;uӪ[Cd@eaʀU7-byMv~\aZPEŮ<4o!N0eOghߖ0"Ms<7WPQOQL4!lL}2\ {WJj3EܟZ1Ro!pVxiis?=aSS[Lt2 [+uD!wys—}f aȀ#}cˈ&z.Ҵg1w 1go悃"Ku C,MJwTC(Ҕ;0s4cuXe|QLP>0+ĮZ2`HIr#vq8'鮐a2b:QlR7Ԍl>o4W_ѥ iƈ4XHNi23&vɑ]W[*\m-}]h_P-3 :N/b?0uD#Of4'--ad@_ܢKSOQVaK܍0dĥRjM@¢kuNuT'Dr!t >:%,{5V SAJS75 3t+ .@.Jat VlH<Q*A{@%L߻R:M@.GqQgJϼ}Ϟe!l#s,2 \m[VJk1# @ֽ)Rê ;z \;ܺCazGD,s`JsRbMhl1u?Fp@<5JM 5E?Yt4GNn"}CL\cf4SH.U #Y9~0@nʈ76鬧6;mpY^8Yo[;tv-aȀ5w,tM@ujC8)èC JYK,}qH :>7m+Dk!0e/$|Ռ&cnT5<u2[S\P|4Juݢ"qud ֏ۖjT' WF4fI6 ~3Rsn!r@lU}sZD^m%=#P6T0WRü49hW> 7e:[k?tXʘWקdZ0T汁!&SJKdbhYFY[ mB OWjlYy'uhռ"U.L2`ōmR?̌;VwDfSA1"N7HM}m!ϵZ;Ⱥ+aʀ]oJ3W6?2vZ8qvGnPpzd]Y qS9Li_JZ3W;+Rck+ \N*pBwKem$Li) ~M^  ݌L 7ڒ Q7H77D%S[= WQ hHX9Č4ըNQ8?VŨ78&\DWiA."WMouJ`]9"|!j+L0yUZJz4ϝb7!AS"gۇT%/< Xf))/S`לb"^>'Cǣ"GLleԱnZ<4[ؽ 8f)182 '[<qq'?pZx7cZ-qDO6NjY)90nF0vIKѲ) Ćӗ/w@ ~Rg嫽:2+>3"|Tt' h>OJ-(3 [VwGPpY(.LnI&}֥M-"! 6" 9Rv2# @Ӝ"AfcE/p0r@y㒝_u؛<duCd ͊Sj CtR)Y% @nkq"7F* W\>`bd*Pu:fDU3 ;EfΞI2`7R2!9@d#Sj1om@?[Tn=~Li6/%m(eFڕ\&_HrJ]PoTXѥ"$.γ,)v_ud;c̜|{LʣltJG 3yi8Linǯh~;E,Gm P$CEioPǶM)7>=RYwNdHT<2w)N%5jseF@uy.Wr=K ?H""M9E:" y}=<PiN. ,WDp4\ Xv#IYf4+oI x]$CʖoٓP+M8^yu'óӺ$<-x%LC)/:!nFW 0|EӇWDO,؛3nO/N0Ku hundTuLpGmRO_ @{apA-̨h:E@USQ~AV^?Ҍ[EftgW'Λ`JsÜӋqQ$>ĎU3!uamڌ[xvlvWcK>L49rS@ 9ohDvI}Y:`{kHJ3SbdrkNU De=`ʀecH ?5aXJ&jc?U$r@xNgRgG7@֬ui݌Y{bd5:Q* )=\Jm v6gCp2I"DG֫`NcGguJNuu}kJӥqo鮘U5;ߩmw}*ZfXsU- 5$ … vPp@wfWTMDN|>-)P"K_+gb0;Y 8DeJ8[] )T$>ID^z*ޒfg?LlySRu)ēU>T}9;Ī,E\*,2`lek*Ox#G0Hg5TDIZ[_R)vvP([S.&'dq߂.X"Q6Wv)%LSm}i=LzH"$Uj쬔nGPû^P3.ˑI? kaʀ5w5×{*#'߂WFrU0X^I!I֍%ÎKζJoSJӝymUvɅ}b#WLiz(k 1E Ga˧GV$'cjuxQΈ,<Ȳ%1@xOSߗZ^f'_MD$5Z^~2:u !i{Y<lK+\ `8p^LinmtVBNi]#K`f?)!IkgNݒ:捁oץuDkH24沟ۜR]5 CJui='B6ٖk0QI䐤u6i uЋckҤtJU4$ۜN-էu)VCZ5UrzOCp+_!A9$s4Jl9:FH{cÄ# @7lsJ%;D6j;Mߧ QGRz.4@,a#@:'RI$MX)S:=~yJΉhv2 CH5i]̰rsdrSagde˻}CČuARђgv"IO-t}ʗ/ߠJ邻3ǔHkgPYmf6LjyYY9a=Jy=+E0Vpf~"IR |O[^OhNhD#:[ ۽)V -qԎ,w3n`Ȁetxh(;1hGr̹}$rHRuzJF=߃Ƿҁ2DT*?2$,z]<52>k8 X~JYaJUs#-$0ږUl'IO'~ݪ6yJXgVgF" OoX_trō=.F^Lj׆x۬j\u?r}(9{Cw@3S& {,U.X1DK6Ewy!r@ay>:j}Tʆl$$R @nY>lO9s)9n!aLj6 yTKp1|ij:Ly2v}*:mm#Ee&]T^C @zlY=54-=á#DOm/yltm)A̵ttYHubsdӉmȖU좺AMsvoh&`V  v[&->ت* ݷlèF|ծuD3qEm5Wƅ9kNC;}am#""n[|痻9 b9/WaտPAG6W5%+tv@@h$gy:hVV#ꌋ{B ]oX$7pZ6 bRop>dϏ"1KvJ1*]{#ya]aogE+D_)%ΰ8e{~)Aov{{"zNG d7S^E @tcU~hfxɩku0xl"Cmr[mǯñz蓺6I-L/@.@ys(hvxMC{"wZuk<ޜ&F踺nm +"Op.慭  r_M}|a(}0.=~B%"D$A;-!\0YFk60ø P0#lf#~9y^/粡yT=Ŭ-8OLnɩv r"𥞳9b@މ4+(IL=yd^;7>S6Sv{Yc.a׈p|!xQR$At^>`@}/e)!ke: Ǒ=84WcMooS{ܹ\m7y~@u#ڮ@v~<a轲m|z yud,?1^~ys̓"{D޶(NӐ2> >g!\0j:{/2<w˻G7i! <n7WKcmy!YУ M \*:<8 d⼶='lޖ ߳* 5.:3O߷Px%;] fz5ذY.F?.Μ`1*%pR&-0edb8a;03ybx {z{-q! @yf !/=:;S ʖoH6ۆ ~f .#{?Rb6zg##9e{GQ~4#|{kpba gZ[mss{6oNaUCYsokXXN֊9Z&X<2c'L߲ZBs;?f sÀ{D"{Vuk8hw \ _2S*S d QV1R3/Lr=i@ɕ>iYV £jb%r I6abWO:)2,SWE ]׷)w@F{xhFz=^D(pf KHʯΆp1B t8{E0<%eac6\– z޿&>=79eW,b=ƀ0<@&Vd g3_ftx{dXn!fr%͏Kaмuwt<@-M8ŕupBS:9/Ȱ@5l6 SLrj˟w%X.?+xEF=c|8/ \ _7;%"ȁ/e#C6Ľj >rdTbd2t:n<@^=!Sm{4:,z".wBv綠>qd~ϭاe,ӯVFRM]F-9.Fh!D=ɐ@VZG!6_৭|r<ٯSY?d1G ="=%㗻5P9= =3t\H2FUmL_a YpꤸdVs4όݖʬcp YTOMmp1¢Opm, H0zur}mN\s\}ig;OGR^o!\r^_yE!DlY⿟ZLrʘ4汳}'<iE|6b{{ظ"p@vz0ɏB6ҳ&GR3Ψ)_ {G[=)*[ \no&Fhx7)S$ȲWut0.hrD [%y9Ь>,:/];u]b:+ d|9z=lֽ:.<2GMмs+!N1[Dʕ돶AakpCݡcKaC\!d(2qdbdgo:m{xJg.fc"Ʀmv?;K=3:||+P@;َyo;OjiI$Ǒ)\c}F7W^@-^Bv2ca\  kk<ϥI#Sten}h:Nl fxz1diߧt"nS}; !\1}/??"CdyǵȄ-rEgNK곡y%$}< Lr]UΝs{A3~*}C<:zR.:/3kdn9cFĥ[X.Rm<j#1ֿN+E&9'$GAԇ&̼ ;$.rEsKJv>ug7AV1\yՌL H5轎eI'5.qF$9kkZdEj4c{E GJri%%;pnYŰ3 y鲄dǛ:d|Vі?H G5:eë}V .'$U;gtN WdZHV1\*3OU @Vݼ9j@#׽M82EhSc]8-a,ߞ]$,Hw=@Ÿ.q9y0YpV329b8 g0!L{DI#S8?9t\"7rᬔY=rHZ[<:# _:;9RU W>ck̏S+CiDŽl .MPLr&9RzY?f";&I Uv )2kf!p@ )`|h"04~_ <2{}(9gن:^x&q?rUoE ɊjSmpHkd B{.D&Ώz+d W8;Z&^ Cr跪t΍ D~FHs8Qn\:ԵTB-7tYUtD#S篛+d9+} q "I#SzhMsxIQFկDJHOmqQd\ѦlRjx9²ɳpEဴW귷}齃2q^UrWIo[3I2wIt.WVeRZ%/ا²O}}$me SpO9^*Lr#'5HYYv1HZ@r ucvI;.&,";gH2QkAZFWQd Rrn+D]";dlWd#y\uY%&Tv\{m0* %z\=:{d8 t!YJ带O(!9L1c .m"$eYmqkIJ=ݴQg[FVaY(k/LAd=NsUNKL@&9Ҝ[uԽĆ!"?_UH2'2]U#%%;^sf PJOsmIKuy_1!'I#)qɹ^Y\!<HoC)u-_q`YeNH2gџ6v]ںPI#3s|?u9DD$Ys&' H [-R5츜u²P|'I2_y#`|*?= CrʖVklE{%"';$^>Չ*J )t;5dV﷖OwVd8 ՈO Kttnj <&Apvr7@Rg^ZGRÖfZb\O>dP .h{*dBCS)!9-*,LjE2wIk2:lj Rj|"Ɏ^PNVaQT<ߚs^te"sdl7Vdt8\*5lRVrvQ s!i:gHlcwv1QMZI}}t $5vĨ.5xS5CCdB*oRA2$jbjY44~o <Ks`\ u%#"$;$u-)*;z/*LBB]Sw%H5]>4O:<2Ξ^"YqH椟9@zKZ|4T!S96`!wZ kƸܑqUWLr"$98//@m37' F2wINd^m-g/# *LB);e%I)2$Hh99Wȥ$@aRuu$M"H$z3## S7r S!L? F2,ieq DŽ`Hc4(7vW$s5_s:X/J{Hjɍq0YB(]<ڦ+$aCq!9i ;Geפr4>$sË:,Lk/iuSh c!PO[y3$ @ZOko-ys`o\ر0돌Qs|zd%$Yo|2=.GgǛ+0JG8$Hkl0o%]Y \_5Isx<#su@dLb$1- Kv| d"3wb;C}n`>"T4Ǣ!sHËI ɊZtp*v\-DVa( yٳI^ȶ6J-D{V8è;.e4>";$kΚthksn4׿&0JU+8_IwlGDZ0$ sюQչLD~";|mNmg'q~ YT]n%Im?lo.!ç4KQ9-MknH Iƙ)UAT뻓ed 6_s>ܝ0@2v"6voiÐ\eW5:ą巫MDO<I I5)AzZDjd7^LVa( c!La^r^p8adRYP_! wj.ȂGF(H*DIꌦ,J^߅,#SF]*9_rؽ{6|SaHn#߬9ϥo "kH$i]4h%CfMSsVY0,ErG]\ $BInky)>qEWCr@Qu@ β;{dȺnmJ$U{ Xgƒ *24Ob 8&N+|E_r@CrJ/\é@D"3h~Ơ8#/"mUd Ce䏒vV$[OtJJ)t%!^ZFnoa-ذiAa_|t M 5sJ#;-"KƖ"caeTe{u" gn9/ orufM\aG6MCrE M@T4?W1J?:@^s@"c"lȓ^ [vh!YE0.3%9O9V/ KW8ӿt1&0^RK9^p{oCr@tE`]ɩBD^أ"biLz{c"S*=^Ipa$ V8U/]m(oIOVNuNkFɁwp}{ҁBZ`VZnwAV) jwH2  Y&,iًkde击aHnP|Zao}dI}~fI ȾtMnnOy)uAV) 7<IA`dgYOgybux\DG֦9tN&hؿ\<"M!f.Pd°:ymW% [$֩gPv¸ك:d/8D[ׯ hp G/ΚJ* aߟ%C?Tb~QwO;! v.n2";{­—13vULeך$I;0@N5UȒ㈺,ZpCrm&.uBc= "z?*܉n6F/HAVa(2#V7`[Ew tVt!J]L7WW\ $"Ίod owe1–CeH= d":[.@}u)},]̒D[Emqrl:r)-Ot a^u^쉖KAVa(g&] ѿfczV~rMEgMKv^ErpL_GNP-?^:\맞r/rER c!\\Kγ^pg F_Ҽ 2A,H͸! DI uSXD$=s^]kO[ꁙ*OwI{^[|CrPZcӖ$)[QŎ՛Q "-!.˓B&#*n+`ߵѹu}zM U@}bM](9Pؕ:k,W"m&+n_ c!DJ]<Ϸ<`D/Xآ6:͵ 6}O*D [EMˆ ;GZH."DDOwG{l+@Dxåk;XQ&)9obGV85,2 *]k"@ufI2}S~G(4x"DDOD[l/Gݩ>T1dNRr>w؄`k|1ւJ$! vjdթ_Kr(~e!Q3z>Ce 7n۫Te﹒5-"#;غB*LI n%`Hb++4vQ.xƁ.xU!Ϗkm(IVP1:ۄ*L^o{;}I 5 ܅vdS?}R'9x6FW^"_@D4_Sk9.x˝j YJs~`@Vi!Ɏ7~sHU rM]gZ,Ա<1M(DD#a0JCnSUXT9y+ΊPNY/mhCyY7s>>PFge{R_h~`DȻP "maؽ :lB f71^uOle60ah<}]Cr3k5v3u:ҟFP{>#":pkDD˻ACB'o[E*,\"9FEĈ-n#K @5 :U:#= JD{oPX#1kGpAVarsKJlqEzmU v) "ꈾ~?3 @w|WE^sVbL?o' `tzoQ2ʍ/˜Eۘ5Y*F* oDӝ9.v; fQYab}! }1[֙dmdsP crjoЈW'eh;g4s:Y"M`nb.:ؘfRr.Gq*SlB\7y~~V(1 vW!՞k Gf4rgiUnQԴ;q{#ܞ.ԨmB2/<%삣␛+=qQb0&Q~4Z3s~]Gf-w8-T;MyԺI"PhԸd_~K;N:w?fU9=^.bLIfW1َC+0?vV w=WeҖ7B naJa:n#|e^" DT~ź'F*0&wFnjfĻ%mpFA v=?-5tujğy@XŏǥaRd%sQtۄaL.oU&+_·#1nw-Ydg ˂j'Ti~hdT5rB =”´]^Da ezhM]%D>:_0)\&\WB {a)g[ux΃V0й} D)Lb/fGQr傸d7.pE >#u#GtD@{*3y&Q vu kvS &7JvlQ#-®d*ٮT%Yo^pfg+nc:Gx0۝dתKʸ7Ivշˈ %;Ѹ ΓKL$ޖɜt6~57yY'.zK|_\jl8"vUgezg[2 }|&zA*gm?nU9p3ʶ/ۮWf.YVcnl$7KZiF=GTOS%(Gb 5:ɜt'M7kv#)3)Cťe%U@PQ5Ƙ+ӧ$$ɼ2GR)SEuY*<@ DDT|S4q_V3oaRXRoI?qWI:yq7H l9YK~~LW~f@ⓞ(f3Nr2g'섳dxݥ}$ԡrͬǫ  zZcWqqpEϞ=Sbfk{ F\xW^>+yMD<\T]/ℱz 9緔02yO#C=]bW?D<]{>9G.Lּ0'O<؈5?\G6Ȝt>wK`ͻ閎JR_{e@]UTF<ܝ{[Ө137|r7͞`֓8D X[2T+?Xc\T8UC2g֐etÂH7"z@!M 9^zW:aiRgJ)VEl'?]X[WM$dc>( !"JiG?D[BY&~Y)U֙u#$vqTo]Q_ XѨEuY6T*lj,8"3oh'e)4Yp,e3zBcW&9ho8B슁Ti2JUyqm>5d/h%9I~$sНY}o%@`JE7KRr஝#"vi-&,k' (@E\'R[59.?[D_J2g=jd&˞)]\/Ña;Ǐ:ֲ0z˜V[g]^jif{>IsYsMNfR-Y[!IH# ¶Ɔdԓ/Qe;}__ v!ol#ɜ..WM9U+Dv`Ȼ5:+9ۅN5|\fES4v7+ܗ8OdI [ؙyҭO PVv؁;}ߵǾm{޶s[v훶mܲM7o\y5Vo\]zݚkW]v?W%?-~w?}|w?yg_~1~^{GwaMݕ/Ӈpb4iu̓B@luU=_rc'aVn2y~~`Ģ1i᫚ڸ**  CP~8"p( ՅBںںښꚪڪʊʲʲ#Gʏ9r?оۻo]vݱg;wl߱uoضa6lXߍ7Ϻ֬\rŪeZ6o(.Zn+-_|+co+-e/.mϋ_/ۯ곯>/?>x>Xko민k/ns3szyG|G{[onkoƫꫯ/j]1ӧ_8}M?.8=39S|IgtSN9O=cO8y'N<Ǎ;nq'8zG3zQc=d̐CG =jaC 6`xaܫO޽zֻ[﮽ңs]tکk.tܾcA۷/h߮]A^ܼܼܼܼܜ쬬Ymdi&MFffFfzfZfjFjFjzjzjjZJZrZRjbjbJR?ON'~Vz'uvWJ5lۢ5d o`3Ste߇E`GDt+ goܽ%޿kźe_勗/\_<т?9?f͜Ӭf~_}ן|駟~{;[[[/ϿK?3?=̣O=xxƃ3x侇y{;厛nnk˯􊋯iɴ]py{ ~)c=yԤQG3zȱ3hQCF 6dЁC3>ׯoWگoI=JJJz(,^ҭkanݺvڭK.:wإCvоCm۵mצ]vmڶiӺuV-[j6mF6ii)iԔ䔤d_/O'%%%&ya "_I:3ׯe2c^pBC>n$R\pa2=~wezK}  MO{则ivYHߵህ_r\+S+) J[H6e[`3. *"wv-{..:fFK;R+{Li9wP27Xݮբ㯏+,}:a;1iAW%+5Ɂk2Ȅ/<ߠJ2QJ:{^G t# ? :O^w=uvY983GpDB8Xtܔ.Sd(=gFYg2d#'EnU(n"uUNI˚1ҕ}G$}eǩ-:xN!d:A@,S5h ]qQ`ƫPOuW$y9J4cp$B=U%tY7~K3ϼ܋/+[o~'~G_~7?~_?cל͛? ,\EK/^dWz+׭Yn6nزi˖۷n߶m];9|ȱGO;yDɒSe5UյUu55 u u MMMḾ`0 Bp0)p$#JX(jDS-)hjt@ᇺS͘H߃8ሂp)~*'|^7e?;#Sú25I=a,~9d )c&$"޳n4e~@lw]pNw$%YFmft:]Nv4Idgdfdefegd(hѢEZl۪m۶vرK]tҳG=KKJ{է_0lCG 3fԄ1'N8vұ;O:ԓO;Ϝ>λࢋ.˯ꫯn[n{{~~Ǟzgy^y^}7gwޟ|g~է_/n/s˗/[|ժUW[x 6iϿ[ݾeܵcϮ={߷CcO=s>/민~|'~7_}W?|/Ͽf={ܹ͟`%-]|٪+VXz֭߰~͛7mݲmwعkwڷ{=8ȁÇ*+;\y. CP8#h4bQ+V1ad "K,A "AHkd@8cG C^؇7\Փ~±" Š'k%3sKJ% `6Ayڦ6$ O.Yt嫶!EcL ޴y-[nݱmǎw޵{{whCJ()-/*+*^~  A%BjPU-) S4U%UTHc#bĈ#ӭ:m`'U˾j GׯMK &wׂME:b̲ϽpdF?= A"kJ?g%W #Dp0qqH~^7i9GKff}GCKG" kEB24_b@sZlMdKے{AE6q yY,_O9\gf^rOxyP Di~n`ku`Q`kiZr[#' /ؐl庿S& <.)uv)Yo~B2_aBjtgg0'׳ͦܦT.#Dp07-:S7#)%|,2QqטYj_lXB<'&8Xz3GOD&.Ykr^%#}#髻$)#8 YK6Unc;Z?IWM:$g}mef=UFv?VKO3Z!KxU#mqC D]gYsX㵪R̬{yҀ(zYVb)Yֿ8H s_tDdEwQۙU`$c]6r'$t zb@~ /mLyLnxhx̬gݒaG!rW,1U@\ܦatpTF`-ժ' gb_uO1uwm5sͧ%3Z=G8G쏙66qE`^ mz[ӒKd`?VqlvbQ57x[_׺K@G#Ģc0ϯ}1Io1kK?l)fəu[o H÷0ey)<k-+w%'s[t !_4hAGV=Vlx/To1ky%WZ忧# %CX5A |%B65-m7X"hURV)Η^O\sQ}0iyEf/,KM6 PBp4ﭒ\=,S^c[bNNwRcGBB!]7/Ɇ!ޖZn$[)2ΰLyN=S=ƜX+}XӜa 9 ^cCd{xPӗ%6Q`a_0%zJ〫5}PxVc̾G9@ $:~R"ݩ {-Xi%t7&8覿&`'Mk9#m!)@:Rp3`j} Rlo ->fbC{ni/yB6Bvd!p)XUybʘ5*9;Qzx :Y#m rfxЀtgЬ7k8,mzA{6zxmv}uZr,);WH@Ylbn0%QFIN]WW)>|ABr&%`JyN=&ȠAs1<qebþ/ .Sy3mZ[aPګ:a*Cn8=ydPt΂A|ꄆÃzϔI!g6mm;U֩޸$%9x7[!`91>M"'ZK [uΨ5M K[6ޱ:%+sRxNa9p7-)vS@xOkAZAA41^'FU݆v֋z[@Vzjz|&٫2(_1H99I bJٷ ZՏBC뺣8|7/,~9\8mtObW,NHH[t”FAoM"hyl=QOxЀKlt Wu١nlHV:{)VϐjS/#r Pƽ[D%5/h ؽ"C8l7',c5ۜ3@lͦd+yj1>V|dfVUʄ",ˈ Nآ }+3ZTJVxЀ 35^"F-͆~1o!Aw1XiS Թix43X%cD٧wU1.N`AڗIj+C%PZl+,g5\ɴe5-3 -s|@0cn%'33t],)lygDNAsoʒAAѳrÃBA6 Kʦ֤*%{}W3c y-X]%/JNYӒX") zf-XоcU,<u|ąj'XLsWXlNRJK5[P 3y#Ѹi̬'fU%?mJ2UG#r I=PP̼.7hٓExЀtsOw.] GP|ͼ NG}3ȍ!Z/zS̘k ۠,mH0L5UD43L-VɶI٪lXGXQ'=&Lu;OݮkqB vKo՞0x]MXA_N~Yj TE;b=&:eZ23Ur0#|&+Ew~4eʜlذn*WL'V O$(0@AB%%ptn _q -.I>4%뒙Y][$eؾqȇBt'gD2jZ:H6[`b?lq$"Kԛ~7,Gڬ3:+yQ}/bD"u)%J)uM׵)guk;fV DտV)տbҩ5B3[ nEqf=dϓ+m-QZ_\*UNn(|6-iI5)ٮ[6tÊ`AT\ S Z;ϮN6 7n{n{V<3%JKi0/G?S D-B9n&mnns Vv`jDjBH#c(/'dOʆFD\#ru,B*Zq+j "9p׳#yR;o^bN|8TFF5`1#hVohBB٬l{s=: !v|#Z=% RLɮ7ʑ'k{kDŗ"@A?ÂpD9Mm_ % Djxw7(a]n!Fyһ7zkDgd Z2!-ƒl0Sag4ncnuj Zԛ~JEr^7nd,hs ]#re4Xpݽ Z9rv=OAVD՗r$R)`Rvd]OXD{%ފkDgMnтZi-oZ-"Ӛ mְ3h&gLmI#f`ki2]}G'8DE=ByS;dzyC]^Ar~rV7/r[l|z$ ($IKr^ot QKV?%`GU5ERʛ>(kX|R{l&X 2U6s8e:Łsfgp3pSãH.ʏ/ٓ#S&9ȰkDc7AHBw$hq_2K[1 RY%9p$YHx2mZLL"9mE)wFΩ,bCw-MɠAz z+ o0 HWMfܦ,9ITJGTk.`!nbUH$5rC?ޠgJ Z~/2%V@V#6Ep ,$fq$RE`tEYƜb׈LIխ*#D O8$T>ÂLe3ΘܖTbN“AQ8erHCl=>MRLy{9\ř0 ZqO/W.9S&Y-cťrg71Fd-;>bjFόA @O`*@ #aTpY2a)AX8S>C\^8=aQ!`}jl0k@A{*@ MMɼ5)"\D0o;%evh[tZ ϶ "96xeoeٺWDGoQhezs} D $EWPoUSיANG¸tX2asҒ֢sL8-䢔1jFq[e&Y[/IS6ll0A{Ah=&d pDOz~R)0E|O¸{dʄS'%`FL^PwX$o9hrMORP0!f޶2l56@Xo}|]'ߙAN/ e(֟6f6ԚJEr-<('ztݗYg{')`B;c|}V3pݦE%2RƓSČoIջ3 DD-:?I|53Y'D`\eX^/z1Lxc' _Єg r"XJ]:1%q`?z̄ji3ez]`\\R|+7ezP#d<ӫS^ f#6Zy& %2u_^v"L3f2Q Zňk4S8SjMߎQ`N.u{[MX& W 4̩GbV /Dtޖ~F٥${ǀE-Ui׍3e|ґX$c2wj6b]x=,̭+n*B#vtBD2ok!$̲$A9@Dp0rqo:g+E) U/W$6_1UՍ K2H?ۜݴ52wkUfҹ%=DD/^II%8SU 0'E#Lsь5$"QLh<#`V}9 <E+u=i-ڛY$죎`F&7$^}J6['9Pű+4nݻ1WAAPZ*ͻ)@\gZDRn "8VɁW~aΙ2]yc99Q8$Ű7Le)}CjWbҴAjչ-d(`sH vXEw4Ɂ՟Ҝ)Mo0'yc={nHo 0eO2Wܛ6:Xa:ka!Kd Nnr^."]Q#gL"E/NJ_Ȇz}#0'7C-etUݭ>Crx=E4:=oASz;_F5m{)Dh9= ebɕEH.*;fOk:[{! ٝR-xlt7\.h+%2uyWef5UBӣdשIgJ䢈=ԐOӭӤ=Hl0 ޴N4:U.8Z ZqC@s=j$Z- !'XIDpIAWnxf6[9PH4.ΗZmShy-x3;h56g9A |`亼c 3D6nt7=.9jso2ņz_(0'7#].\%gCצYgm*iYx DJp`V W d>ՌH]B^*RnAe7]kPO:f;[M[}S(8{G zG_[*AK# J%2@EyTfD /dͫӒnW,)ڧz"6z_L0mwKQFe<)NAH.$-D+Jah>(7&flF¬ l`9 :xf-}"ſlsr^g,=歶)Kbqe<(z<8-QQr8/Ⱦ/ I}Aޔ@<@@*N3rnPŐ4oϽV6sO)9Qo1.[gL]"s1iXeo6j2h=^ _& Kd ./0vvvvwfrX9%bH H]Cnc/NKm(7z6jE y#=6 RA:$fh @gluű{Ϳ׾W]pʼn|9l&`K3#G=WhPUfh9q}[gl~璩:j*0'7n(٧˗M\)Nb x :?<8_&8A@׹D8.پ dC% S:> a 1)=ӠIk>N9`NnJ_NwT'f(`_l(RV/Ex@'b߷wa;C.Cdb.LHk>)yw5"(^lfn~"_OV<@iu2pn*+Dt\ 7Z"U5e '?4mpѭ WWֿtɆZd\&+j8@tRCDUK[9Akd_+ap j2#cd/pE0ÎXzRp\ʆz|Q{̔eݜ\ lu,t R]9@>Sn 02A$6*oSfFC<%9j/z ejmaNnp)um69g`*`{^#}i#D-wJa Z E&\p veFzlJ1mn8V>rJ6ZG|};> LݝATv:J_rׯ8ApIE3!;[" _@f!Q@0kVhiGW%LPorh"Be'/Γ]5 Yz羟b~#-~+Ġ~- eF&qޡ"\&[pnz;2r"[ `n#Dc>UY62#D 0[p!{Z"Zf[Δ=aPA\ZФqy Vsr~pQh3yu Q^u?EfL.-'|.whٲ\)3kRL*32'Z9ts$ꭿ9HrԞwo?Uj(r#kY, t!R-hт|"["tu/f;̑E;0uV`eb)m\my?P_^srSm9Ur;d,9(;}T:fZ~B,vR8hт'5. ["qfKRʌ,{ʎxZT=ږ+7{Ia Zm^/>AlyN]̥h+,RL_sgt0}=+2 zZ `ZbZ"QeFV<#-FTiKZ"n◗*XoKjsan(DuD@T97 "\a Zή?nY8~G%}ޅپK$/К#'{͎D\}su6 :A\w!U<֫İW2hK/, ~gKd1kVםHkDÊE~l%/~MLW?KO4pn-0/kʈU"{[H!Z BUHD|iGYq⬔^#lD癋%ViQIiJ]Xs3V-=fd紒Kˎ͑9UP~ZwcLHIν ^r &,2ʈ OY\f k" }> I#=^ J: e Pw~ђhF~#\ՙ`69 Uu+3ZB8{?߽Dg_)u6: J8'd)dJ{ETDs  4*=p{i'5hTޓY"qZk>F% #u*L'9| !&ت%t٨qjSZ?jkXh))B_.97wtòSDd7?:Ć 2"Gf5Ats ?`Ǖ;'Z.Jt>0D55гgy:lzbQP:cd9́! EtuD@tyek V{h{B%2CŊ Unvw0/X{´^ydzjɆ2 (N}]`ڛ2D"VpQ۝ꈜؔ _wCN4t#5  ! C'7M)ZQC_`_h)՟.T'%*J Lh;Wd 0Uag8pKPpz b H!Z-<~`ךI[TWqnVYck) j2C0%W8IhJ0cQD#n^Dj݂sa Zv^-%^j43` _Hd]ajn:oZZK!n~~l(SKn9v+@Foa3UI5"YD(t @~*8oaU ` ML #unSdӞp/<fG q|bR_-P oV`Jn6pN@ŋv=DN~hR[K!ZS((&|Ҟ^%֚cdP5Y-89{~)[ץXo|@&NΖvp{ paVBDw0Q{-.j$ōmQ`r9R O8urJKNO__Jeje0%WQ84H Mc̬~W9\#rXA(T[([|_UrK2hV4##Ql^dPkoZ)%iMN(`Jn"\`kG>5p?Q =\kG X"We$'-j)rB}S56[+SrsgIYn/'bu xJG~oNo%Z /<4q/Z p|ʾ)ꋊaJ*~ xN3kN y Dj9AbÄf@kwl)Uel,^+`JnȠj9-*wq9v9(;I DtA=_a pB*gf$aHQ"v3d.7h2դaP[&9*e<kv v8j7]-hu6_0k>(@={cA"Z2dk ÛNos$J92Bh4έ, :dg}e G =R ;Ij;Lۈ$ H4_ /|in іh+d0'7Els͜k3yN3S#rP|S)z૞r8& ZsCDdnIE3,ӍÂ'j3%J5!XkqLMDreQ2msӓ[E#rOC l{ O q%D1gp0$ן,̴5KOx2ǔVGS:30%7U\4~[z ŃQgB+yur -$vG:ڸ?}}:ek2Lo|1ؠ8PO<]=="'e;Axf9D d҇qj 0b}I\7rr Jl hi6Z~V)tk dsY`N6@I2p;_m!h]6oi"~=OF+H~;.m“nUq]&X&>)Sr5T_:L 0os#9]9xZ/!^%2@y:!t0 4T!iTu6껺J9p(r| KzD RghY;/ч{ 7y9YAF1-@@wleDgS1*}_.9pjdLdn1>u]:ezsU>́ӜVZM,#rSkSX Ela>6d Ps`vlv}ڳr%"HXo&tP.h M/\EӅK$sќ])NnB;Pb{HEVu!9<N;A^& C#Ge0wsE@f"kvU$} LL:R)Ⱦ-(="6"D"Z.8G0j P yX/t~W"v`2&6bmlX465)*"Cm$_6*s =@N@su9 m{فAzH^Cz#PD旓uMv0&WEl-}ǹ/4>R9u;y7ܔcCV,e$aHu~sҞ$6hI`F vTW,|PHn8x/ʗkfN:*r?9bAVΚ->o9oHb>k{5Q4 -Kj$I`DUo&"Į]UEaLJ[:ً\(@UA8F9Dk}NJX!TL#Ͳ'IHy8X=`N➤HF` 7iݘ?h;2rCrW)f+&6uY_tIGbm+wm)GigsNL8K|6 [ҥ'5$YMH4Jj$K'~@b1Ԫ&`Lpp מ6W <@JbAbIΈ-<:]a+d%o6' C+tBbR@0@U3NsZ<ǧ1 h˺9lg<8r6vvDG9D1 ^"b(TT53~"K)!j$9" gL,*D7w9hf ?ۆ @LjqQ`R$2ޒX!Gy")~(NiRߵ2IvZW<@گH4 =ِjKh[9{'m 6r9R+ Yq;"DZ[\ aƼU̧n C~͚\X=RBH? ~DLd&DE˭HOn:$Sw~9ŨDyϛ5 V 9"D [r2 8YIҺ1Ju59YNiܢ B+IuV?&u4ك29bJ9LS{xV+^!@t9gDꮷZJ!Zt\crte `j6b%:G, or,<d\E vn6I#Bݗjó ]zۋ6}vK="Pʿ w!-2@Zqn(jy*,}g$ g:::L/g  p~@A H'Tv_ocrJ#fg3ܮj=0;Dޟ6?Djm!Zr}3҉1+d~+=5 O]ʬb.IfK AI:7@4#R\9ØE/˺8Mni 7x@t16--69_X:=ݐ;ˬY5$ xȉJCXUF$^ כH1 @3cn2\"Dۚ U(ݎ_"Mfz[', RWH]pAȼU"+dTag23%*/&Y+ĝ}"ƒH\p`J嵵a BJlϵfzr1(7Q㶡D& sǃ@dc~R(T0 m Z([2 =Tӊb>l?[3XH >8NP5^T^Uw0 ~:BPbE0&wEtģMu}AdsnYݗ.AB[ID<OY! ^mkX5HOK`-t~&[9Doj|[LVy11'HHD˳׊;+%s פc[ lf8m|Uy 2DyTk2uQY+d>K-nZ혭>C!˟s9Ikv5XIuU TŨ>e"r Dk=(Nz3<aVܷD<{qDھO:I: Zd Od0쉫ٶ61וA^$u$QnJ̠z]o<*I`UMK&I#X=Dz^9Vabn~aDRc;[͖t@H-eRl POimf (0dN G({+Ldu[DolT}/[Hdvp@ЕIKݮPJ].4$Mq_ZG̕wD(unjNqh跜x֞8#WtsJ'dTGE}Z[ ɪ+$rw^8@ Q08w]snLxLk<ŕAL=cY=$_(B(5"֢VO0dQd`-f 9qC-xQ<_~d0Bfuf}Ѷ(zYy'>ȡq`Y=E8/mr%7A%9脃"t"ֿuF[Rrl:LƞAD+Qd""w^ !FmYW3J,i0M0LJ9D{b.RWfuY1'}|$B7-6'E~.miA$x{XQ>]ȝ` k;Jrt%bQdeIc(r !XW_MXV=_8y/D?;%RO'c,[ZŤoxs8b(r2`}Ǡ{%`u~Jt?aBTRrhxtW%_,!Yϸ \@7\$;&ZJeEvIնt6CQ衬x+mQek|ּUCjrg^mtI ̩/RZ쎓ښ,DǺ~`+B*o6 EXF5DW/LqC]<-.^]ܲX;N2_ &BZa: ƖwbvEtfɄ4 9~Ύ`LhQP kvDg#bC)DOG݂9QYGĜgKC*[_'ll;5`AԠ6hL2Tx{S\_Bl0chm}H!O[cs&.}Ylː#)'=ӝM &[&Pí< "6 "pߴQF^=e!l@;V,`lLsr}݅RGMN;Ŷ%H4C"+G}.u4+D1 '~@ p fN5`n'Qץ~.EpH] V[Rr8, }%ETw_+JaO?3 }=5BMt jJ˪ 9o xBMACcKv ʶu%e&W4IcGCmbLu}vט)@b) @1B[0KUf}+h%!X[qGC׭dEƸ=0|ӬIrȓ"eXQ k^'LXVk~deY-K4ވS5Cb-Y29b'Iv(_Ory!udl`?p[5Rrv#E6-?0c8uνу367 7∪?bh !=?0ryvtNh奘PgEx-Xa۳M~ʰF样kegÌxzN5) Γxӯ!x#|EXR]P?(`TC泾|s!upKЙp-Rb4`d%AO*3N]PWrĽfj{WIɔ:ܼlǦhΘ{0KʅNr1D}"׍=@ͅK,{I>ԑ`f]FeՑH]mxߞ>o *$oD$i A̺feq'`=!7:RgsGeNQFC1,P O޷ut 8-@;//ky@ 10 a֤/0?Fqr^8$e`>dԩdžH;!Х*0cJF½&_6ٻ `> d h>Zш#yFjrhܺEXOK_`\HO:J(Pl}yhw *"V@\fT&Dܙ{4Ǽw8"s =uf]f寳 @u 5]$'u$ԽJY =2r+"s])h //r7WFQH!&-,,/0>IqZ;f$BM7z}c:u?l{fc$[Y`mA'~jNOvs$5B̼bS0K`N{pR3+'vS@Hi-qAOM%v5"s|GT!2t+S=8"kŔTlsAh)ƊD־q tO? 9ȪutaJIAfp57㟈hžowefYfVHPcI  >2~DFCRB?{g =4 1GTB/C)ČKn|0'>>H8h!i{:V5>?l`ۥdm'[RCD) yk1 |لgm8;yYD4tUjrh}loܼR'#1_ &mv:-׉ }Yc%P)|4:='"d5 d-`ƿYPb\GVxޱ3֘#2GuRޡG,k'q#RuGVmq$[6 S +y\@k%$f< XřdžbQ\sg7Hsh@ 1킥oU|lu0d1`;ݖ5̘2FWL{- VpfaADT3~Ƙhmj|0'ܔ+hEuSwMNdu 쵊+#XajrQS }{l\l? yu/)RYi^-G.E_'Z:293KB <`n\zS&s`\~"D5E",ZMt\QuMUϗym̍NYDD_gjGz;j z~z( |+.w끖軜s7eNdt .(7Zu@YTkc0S G"cL-g;i:bkFZ6 !;ֶI(gwPM5g 2GGz`^O FVJ_"nңx<"˚1=m6t@ ȾgMjt |N+=9|v #*9*ca .2,VDL{! Lz}(;# E3A; ]? "ͷ =-:5k"D_l B0@H 2G2 Vg[cxH+{8zVzX+ILpG河PDVHDVp>r?_r ILZߖΑ9VVgMZC 8_BC.1KBD=Պw%@mu>7t)ׇD(X^OIDxN& nL zƖC&Y,j\)"դ!IH}ǧYZ±`~I <,~WGTt3C)4m5>7ߗ Zދ*Dl z;"Djg@v\xdacDFÑI˿Aז`SBG#WIz繀"1;r@Օ"@ѹS3t)Ĕsn|/hp}g`F~rF¤/ֿ.8aZԲ8KY9L;uBķI s#(|c2"@T^WRh4|gR"λ %=-qQ ϴ\'@4d@#,Vr ,ϰ{'+\jq,0.W09To4m8t{ 7U ĪHUXYGVFt%ٲqQPNc40yBVi5^72!nM !! 5A,8~VAH)nyNC&L0u"W+v<N !&3M"ξAXl m~`D] Q1:DVJ4;k)5a!Z5~vH(aMxB[6'Xa"\ -+]uPȼ`.RI=Zy*{ (U>N@.Qi!,l@c&4CdoɖWL2G_9֫[HVXqU͗|rz  QQ?4M b%5 Z5`N~pDcM[hA̎9[`9+NXK-9AVӉ;HCWN"09H;_ bs& e4=o@ Ѣvb9ld+D&_Hrvܢ۴c=q-澅.23ry3~IbċUT:h6ޘ!CКMh)`8kQ;@=֫Hsx0%x鿑l&"@o2tA\ @]c%4U.<@Ė h'IC!Aa^A֚3:NjALUID+f˸$[c RĎ%'Jo39в!I d56`A.<۩ n_&m apvč -dԸfF V;B(O2ـ>KRaFM˂Bb:/O/H)n B: 8w-g-/L2ǀ@[呌 HJ}yY){Iă <XfL[d:فtbzc L 2Ω.wDZl!I=ܞ "u##C]@߽B7ǩUOfKr!!yodmV3=,TAV@ν!NpRo%[^2+X1ĕ캈U4i7T&i*ϓ%c! KH{Q44 wtf:X~kR3N1G [S QO}#"bWGzLE™BViSCFŠELˌƄ v!A:2םnO.I W:J Z)0>CHDat^Kڠ -7V Ⱦ'W |hgɖg E&cB{ #[{vpQP1 `*Rmǖ¡X$*HZ+VT"z?ۮ Qk: ߔZYr=XX8ɥERf'mwZ9m]?rL\_r,qlՄL},,tcgi6ѐ Q9ABXczJ}׵i@d(dq\a DʁOz:!Z# ]%VeɖL2Ǹ4[U$+ |ԥ*` ޮ !/K/ [E .۔_ D)( Y^ϭKi,|[ɓtBDfq JK;gA[VEÛ@s9M&K/.48b 9,.e"4B{-? D(8D6?/'X)N 8xw Md%[dpМ[ YozTn\vh'e W(HSq h8Z1K Lo/1IOK"H [&:D@v7qg@A/hP#sr~\#D^ׂ2Xo${/}"pn 䆁2jw.I ){HJmh8FzZ  {4 u M2P);_h!hOQwD[dA&c0k5f@$ze*[6Nv8˘jty׈v,xn:Xg 2vH4C[|RD6.զ|,|k/K wGCpN} vNu0B˖ÛO|Z1Nz⹥{,<1To*Y&xf(P6 Q lLm[3,H+{M'DxNhz+=Is5KA; | kv2;Y}g Hfꃙ`6L> b"P2rWUI^B=PSvN -X+uÅNm-` D›$Ug)_' rAMCdٓʩi^aWA(c[~!F$V @a|]Z&$3tB8n `b^D!Gd˿wA&c,!MO$X-H9Zf]joY=9^ ̣9(-. P"a ZqУV?O@dg@AV}]'Dk9rѮo6m>MZXVA9{',x$"b [21 X1٧ D6Y*$үGuBR982'őevX~Qb@8nShAÛ@wqpsS9`ZGlp!g+4-˞D7zJ:!ZP#;s0$39 L/ޏgt2X)~*#Vkȧ>IMah\GL[c|vM]O "k2J?{)Hyݑ/h@p6Oh=Kꡐqo* 2 Zy#T׿eS0GgW谯ܖxe*8tkԁtUeM/YbnAEBˀvr%@i 獂!{A _N#ƯQ8GBO\4[2߅$-s^aO?'@6 <2\yQ?Q'm:!Z~qv.;t0PoYe@kkZ:zV=9J1"n?gW3VR\{# k,RpЪg9Z9Aqj"/YQ_TJsN;y -tBRol?Lǻ+dk¥5xh"yr~R]s ַF@d {rQ?[,y] @+Lhrk]C2r1}-O_x3ť*XqzM :=v5є2П 2 *%^$, "k@ŋ{ H|6s#z+)̳-7 ;Z`>}ˀeG|H sz|nxK-l0.M=Ev€j<$~^a9 o3 +1 ;?_@Lq{IDүmD4S]!o_h/|f[KL4H^3;LgR7-rXZ`?ՏR1ň`N~O=?SY.=kst bGĂ ۛ=ɴqKUNe5n=ˀ+Z"ԥIvU.9`N5Ѫz }k,Up N(~A []Jcف!/YJ/"Y17 b,ԚyS:E^svtJh<}]'!ﴜ`ͩf{—_ӎDS3 !g<|MMsЪ4 ƱkUw ՠ`OY$scF=D1}%}~z E_rkӶ%.͗ ;dO_2`9DU7/S~=&w0x;V4ǦUq ũ @i~Y2x.4̕׏ﳔ?#gH P圀{k˄0$Wqi &{z×F5ZL{/sh-͗- WY\`9jYu&{q0 Wv,5Z5g5@Z7K'4ig9:\}+jϐq|a' qܬ(+?}l299.`Y LT2i+J} {xDG$_;⊽# U|5\ԇ<< Ѹ{]aH{ܿy _>gaʑuA[<y[Ѹlji.;o/[GHO/<3 "K'U*s[x:1c (6>f>亽u$q;˹v깯t3}ˀ]lqrILn( Fjkeu?e j89h}z`N߮^ k@?578'%!1H[~UN&>?C%$).bbq8n=ˀzD[yaX&b2G| pBVia8ЊSy=@- zD%u֖Lxf1DJwc:~lpf$@4y(j0Eѿa'| _/h-i'dzمL.#,7].ZO@Cţ lkPRN#&N,W_DkTFǎ6cGt{ID0B-),_-lx͐XدEh/|mDIZd_:sXD c*<+Z< FJ,߳k$(ď2abVxC'UjD~ub X pYN=s`}yDk얙s`7|0+-H+*yi9b 0&CME|@pkd "zRK^"1E'Mc˜%wo3b[zs1ies;tB4빇')5m\^YK%#6弲S,Oo\ zrIVKS*eua[omL+Y DÓdxk41qb_ D lJ.oN]B b%оw:Ih|I{Lұky .p$G_2`+ӎD[nzU&x(7fm{+Sx֤EDÛ2fi ./([ "K761wْ><~(`rid~l-x핶`7|06GZMU(焰3LX/; oZ*-zcXSYCLlC!c,N<}KhRBDD(H*J}wiJ_tDx. e%ZпTqq$B9|fo?g!R,6MI怳-gQ<$-;~N37|T{j! *o{ =n6IH+AV& _S.9oZ ]1anJ<|n&h %pV6wrKs@ß;(֎sg}!D;VƙyC?WDh/)@av [\/ϡ>n;OseM`km킖4 cvxtBb<,=Zد|N#JZ־LgQ}ߵ}:1nRHeyw"}FϾHܒlɑ*[?(0/Mn.`z'iV~ Zۥv馆Ү|Mж["1@坸(FZ߼{@αΐ-,#KgϮт~iC@O d =mu+ ;ˀgLOpC_85 킖f+wn;A9$ә`u҈Ϫ[om &i+U[^*˜r(dd [wep`V/(sk<3#آFԈ:+ _ =El]@sՌ9ui:F[8 1k,X_7dGm*j:Y/V&6ejkÄ3@~_u_{rVV ;RRNS;Os1FU?_}QA)Ȱbf1Y==#Tn@|# 8e8CbYͽ:YԠ1k6hYO̻!ىWniI-CkQ#2꒚6#L/UŵU@DgO ]#mܯf%3',qn<f?;j z;ܶ]"xwth -,_z$զ8 O7X^=.d>I3;ˀ΋r4dƥ. d6dF}@M͓MQ9YZG5E >zR=G w47%~D hA x2̺(Sկt"}Qq0##SCZQ_)MjK&6zßֶ5Z`؋vAWl(̰f_܎t]6 ^n lh}4M~AJ D{xbAn+@,iRY@_~k7 Dkbi;,/vrQ2?4?U;ۚ}ZKOߜ>1vAWh{dꨙVk-yvHж,uCl6dh؛ÂZ1^ fXG}Ī ,W͈,덓O*tȣiﭘv|~:lDx/]uz×;]>#Hčq%h4|0VgkX]2~'1W :3TNֶ KӡA{\7CT%S k^\,szӻۆdfcaJJ5zßlu$Z;ԃ6AKzzFߡ+Bg}W< @6#Sc G_m@d m2X0pysBtQcfdK'CFd [kuDܚ'/vAGdLZhQe89wnty9}tDvg[GDz!.+>LCD[8IfԴ,qTľ:Y'$s˼D189Sw@G1"S|LI^604?q_:=@#)ov]AojJeӊ yRR`Ft3ͭ$>%ffC}D#E1rI,8W8ȲtZ}RD :߷<@XN=G{\c8북s[7;m7PfxЄFO3`hb0&/!"Qbec[zE|ŝh&X#ೡ{D+"I_jkZ?KM$dld-mY_@&dgKm{#2ŧ䵸 3-Hԭu1Ihe 2ܘj/c|QܗtpX$z|lϓET~$gEZ "3L|Y"Lejme! P;{pLjnjq#%ctNo<ݛͶkrY-=C?gLGW^bwXlvwlHԚ5 ;{⧿`}OR6Z\Sp,jSmQ([k:Y(bIk`֛qBɚVr3GfňL|(iU* oj-2&hi~k֮˓biqW7 nfh*J[c}ORXd!nmqT\8^ #2/hɊ ?v$F_~q6AG4{~%ŇN LQt ^E|`XP3. は]9e}w< -P|¦ufSswfzV:>.dFF~~3l%Yh.'ir|%Mw^@gU|( D|͵tTweDn'[[ڳ흰B lhg`uUe{D&s6kRkI:z.*aD-Ymb39=π^u+߸cmKRn.k1b這(32mpFAn(86v:hQ-OY;Z!MvF`[321&18IW`"7i-%F zz&}&i=Fٷ_-`wX'Tr= $w"'Ŏj5?18 5IfKKل8#ԒIxw-.Fd*^rT&04OM#Qt=h4%! xMXh=oYs:McgK-4|uk D ܷ&< ;䳉G ոt@UlFFd]agU7Ld?զ7Zn M-\/HCfǯ#"4tc@潚t`Ny  oj Xi-!7G vm1?Gm-QWpzzӎ-FZ6^}rDPQ) Pd[)> 9RǙGɘS^ "+@_ܲ Մ Xji'Z h Qs0"S#='iONoXuό+WrPs?]EgL\]|)ǒ0VQlTt)G[Ȑj z]߳iA 8zloYtܤ_d.CmLW5]fsj݄#@~ș[mAB 2[V椲`' iܻu冧"`z)>V WH:/L]I{djQ~T<<`TDVM,%giS ,䧉Q|,u a 1?\i'i%iG"oG"h;?Cr㉱z:`Ru8# 8"N}D0-q20:ƲNPeiYD`BO,wM.R_lozŸɃks$jn:ɓɘ$'Z?p춤)DD^ :Jcf(1C rE ×ðX/g}cNO oz`z0!4E 愼5fFFl†aLod. C[=dkr 0'oIɡd.ƓVoG yfs Xv.EĢᅶ{|#Dԣqɯ(y2p0Ĝ &y_cq[ϋl[9xE{E7 i=,%ƿ|b0ɛkv!rYrir8bDDڅ N/o9O6F}/@D0<܉-73̣r̍/ '@|v<_YAcI32B6;8s7=O (E'cAΝ-gi,vu8gdoN#D5j}-#XGX"O@*sh{Ar,+QC9Rϴ,~EEf5|Y/$H,Y$P=ͣw̭oX "+]Xz!,䟉t'RHҺ2=1ڃnҼ3Z"oz]"y=',s Ǯ=Ź4\=nJƞ@(QC1Xud֬{{*լVOo_{P" @ K]:SzBᏛ Vc2Nܻz W7)%%OK<132nފ:%?!;dku/߿s&YA.K5q "f_+wf1SN@ҠcD2S i!SMwzG_38SZ/Ythmw!L~^0vLb7iwιuf!ՈwZ"og/(9yԗ}FAnk܏UzŹԩRrX0 P>@јG1V;l~K ] :NU Ox ~c2HdJu5 _&cXNv޴@{9b朣(6=Oc岅-Wyɛ޴QG2Q LL{+ ,LǾJ }IyIb.=O{jh\w}o zZ`w 䗉X-^o;refI0qQ_rwi8Ln9'[g`钻ļˊy m;']>\#ATX /紕AM\Gl86s.lO3.)=AdtV0knhE/"|Č0O1!9gQ;٤E7 i~r%690'oU\,9^]Xv?ᚮگ9h`YP?ࣘ8w1E0ݦ^I|̭laoL g]}䕉Oc;U޷d,Ź8S605Y-70'O d'{~Ĕ|lod_BĪE 6ðSː ,w *[e%!fD0W[ ׍j`f)(lPUROJr32BE|3a2=5Kl)𧱪ȫͽB0'&{\u.C=mUl-c7[%g 2Ԝ=Oe˝.k. >?/>d(lz\-..D_c;tl)7l ;E}F ]'݌EOҸ٭D w{rɣ+w r8q7Kbܒpq@}D{p[]E8aia ?tiɠex`N}KDV <2%Q>7tR:t6##t]/XȄLIEg0a CMhDޖ}ɣFmdx#/rIۀrAn=dQí :tI`~vaIhhm-7  ׵j`Neb@r)l,u'RG [ţa_ۂ`4VnYy}r\M# r9 s(r0tAbL)[a.ve  ʅn:wHs5k{PpV=x0_]!DZlKfd).hȄL/J9A|G銞0qͅSD^{OVm?2\ǡ ɮTUЪz,iGLy%š' 2ܒ* \%2Y/6vDЌO}{ݿCa|6ۗ,h4Uy圫;@]w \xMLj:A['[5<^P>(>ߒ 8;yPM>rgJt2./6"X.<7 D]So _M8|ay.}bBWihts50q )?jPȽ̔"I'$u`o|nm2d<5>vNDsbIx#6 De 2p΅W'#*3##>7w0tL`S50qlZ-ڑox}S򨈎{,nQy t:$h{얍}|䢢 D~{E7WH_/8mQvRpD;~N#!OYMAy5<5] \~)M1VHs:TtÐ֐fj^0'o"u?d t w/YL̴50{5/l{_H]Zmi$n{j yD饋[S,Dh~ٌ}a"9tÔ]n_vPM|mS&DJoܪtQnļӺko!>J\,j?=t%ArFۅ*caHTUq8M?d^X -D󿸦i9,dKܧo&dTҷ[m7M̰-qޖY1$om0K S/j)٬)9++.MNW\M <(-Η-vsR18ΰQ孅2Xߛ5|dS-70pMZ,W!K%αGq@BDw͟vF%N$rzݙ:wکmAKlRO> 2?K Ƭ!OL~,n32)VoG vbj}z00C CWsƑZ #SxE`J~5?Ǹ`9N:!1c_堥#s) |T.CnlShA<4|0>*2F ]z^m3ijD0?5 NE \wI5E>T`FFأLH1dkOs!c&69Zk)Z \u݋v)yNϗ"5CR* eDbKgVS|Th%ET}^@ûۂxKw "WM0R YMN++6 Ü`!n/Lꁂ0Qaa+l7[vzq `ܐg)Mo7fZf<S&DḏSV t?Rmalpn7%Gy?32]|H(^_[HSXbܩ=ܹ֞Wߺc׮=KkO{vg9[ͨ0"GDfi$XltÄoq[nl/ӯ&d?ignTu|aFKp[ . _;< S(l`6پ(sZ]Iv=ϐ3y)*%`!t D5ڂx;X5TM£maR;YuiU 55V7gO/lYЈ`־ۭd3I ,g kFQld]`Fhe_ffىnҸSwDuS5"^'!$fzdlŧĩ jΕ(yV"<5Q2kWIK,7Z`i1ZЈ`NmDep`Nt$5v]`gHL+03 C5Ցږzɣ;}g UQ"nj_Hƻ/>$NQB$$4&ABј|W;s2Q|&c:=e 8Ō Zp`Yǀ䃉ట +FP>lBԄ *ݗ~ff)Mo5Zg%jʹu`Qys3V9J |6_c`'Y>;F k~/=1˅%_UMӺ$dIt @5Ϊx+ oRAŶ)v93ցdL0s#tn}/^YSj$(G9v$'D/M˧?|Kh]QT HhPwU Eqόikv @!#YaK P 3-|0,5W2B^3x\с1n0qyiGk[:5Sn~Pa5΍ >`$/tKbnHlZ і:Û80&*=ĹRD>dIum!rt]d&X?]-1*c@`Q Kdδ$6??JRlx.oH\~`xC~уs} L9&d]K5Xy\z?Y$aJSkm--^T<*.\.9D>`IUuvrZ@SS5r@BqŶ9UCgrA[+YA,UkRE{S :|yqޓ-7NCbHn}Oz g ! {s O-b|0]c -n|0"GjH4{nYVKmxlǞ$`L^ 9'%d81 uk&AǴgԽS4"EG!$HX7.#@!R+`jD0߳+qЂ˹{͐F[.ilmZHQm&d)ݹ*%6=~8$cR;{E$AUYQ>{H̷,!ӀċRE'n 2B \* Pz|IQi)REUJ^LMӜD̿eF|֐`n!/ZX< l|05aBFo>vvjv ;H6$Li[KMM-~1yJlf0 sZKIK:8!rBm=VD"x&rZ>d{[Ő`YǥC @ b;@k*2C>[v”C+5w fc)MWZn}B0&piz rohn1) fl$'(3Ɇ旝 ):|%Alp&1rQp=RldMȌfYe^ dB k,'A?k 2B4=cz7g$aJN,Hk=v`L^Ec Cj/9y07XCGr*C/Csn;YX#i=F2$+ʀ(:k]*8GFM&S tۡ揆)?aWʝI”ƥ-Qw]JGepsilw;d $aJS|bdq~Ur[ vac 'OCedR]fIZ q 2ʔNC$].nb,wa</͌+ N;DGQx&<2!Z"sC⤈ D>L4$SI”UMhʋ}U(_*91TV51Sh~c9hBTuG h& .u&\uIА`Q9A=ya3##li0%l`"&I4͎Dޚ+]C0&m&hdfվR KHK;,#Q frEזKE-a\.ΏҶ_3}JfHwd{dd` f>7m_Rە['g=`&ITmfcVKտ]/,1y~/r{tG]'9AGH2YOhX+Ia mM΋"crC92!Lؐx032kڕV )Gb'_+v01 UdDަzuޛ ɫwd@h;总6l+ļv,I+ !,׃hKOC~$vLngbψ#bη t4hH0o3\# vP_ uQ[j;~b뀤~;H4<#W;1yU)4Dz+_T]]G+YG$ꆫ.d&!pxMSQx" |"v ̐`J\#ʀRp tb0*|Hr+32B%H fa)A26fJ%~ݮY_Sc{# M[˓L̂S=`-']b$ .D5W /?,A'\*d_ĶM `ˇ* ׆GK9X / =>@S`K\焭T߫<$aLojZ"n|oy?ɫu>~ H8V-݂5wrǭ$ d I |$Fm;'P\H8 7N;;ΈJ?,7UqWL\#L)~e\c"I|파k/~z燿9 C(iARQ!ǢuڪA.ibޝ ҄ԍ#Z0@ƠoM ݧv,7Rg9fD0kN(L@[b0yy.Wl[ۉTcoS0I”X~6GNϼ,1yU y+ Cmx1 t 摘jﰒHK#1LBz>'P4~ dDw#1+O|%H2X%OI~*SNK Ċ_?6c ̐`^~02cQШ¨6- IT/Lɸp2|d;IګNzwPAC򬈍x\s E焁Zi=e6+Oȋ(1XbXЃ,6 Se܊sr)F;<\mL e.Q^\xva䅡E<-Yooc>̿$ S7ՑօnI^EHZ"Jc%iY4 $\[Gl- G9;%a#燐!xwp2܆@s F4;ڥ&dz? GfמK71 \ckNy-zg^U..=܍$k lo(1[RPt~:CBT9-b ̐`^uIOBP>_-b0fᰃ>Nf_07⌖Ԛ)'Wғ<(At|\t"f]`-uOprϫP@#.$wRB[ H5&8^1W(+u2|b0kIf4q uK07V$HٶٷvWWE 9\M4fڗi3@G)s6%hbB~'_mqhUeD@ʀObd+7-P`͙1 U5$+o e@xQV-7B^X 뷁 6G~vNxCVGKvݭ=Pse{1ah *G}b|"%I:T|K :˄ YgMM0n4)vJpӭ]!f8qFAߐ=|-ေӺcȄf__ ^07@kċ˳Zζ,i(uB!rkJ0/82 A \OyaTw\L[m6tŰs5,01ǿVgDamyhkk9=rZ.uJ\j"Oh61h藨%W9O4̷k-?G%n'dB ߗ~ow%7rv&d\:}A3IҴSZ"ize3PQ %XNbq;JDZXN~'2@-6 ]uT s@IV?JA F|#1Z aRG*;龶]0a|wm-n{j=J_8=2I/\[m7$,r |*rSz9x>R}4n_%dTN+fK dS2.~^r,wI˜j Y-I/y+C/c{bd5A : M'ߪxp4:&o97< R7a̗qX(L9+&di61['~M;$ S]0%kM,z"_zܿ:gX"i 3k%* E(W'jt&։ 59he?t=i|M7 ># [}%Xald\}\}uI˜eLH9ZqtGztWm?NsO P"spXR:1coe#>=\.\,I0|[y\>%d0ts2B׏HΖ~]W0钄1 c[}lk׆E-t:=鉼E睍ų,0 ?xQy4ܪ. J'KHHg[n;ݐgϠͣ0-qw}¨o|cfaB Ǘvs.IPך<+oAV:l]چpi+$gI]:HT--(&>* 5>V<-@kQֿa{2q|2[wՈI \(}lH)$aJCU}䌖<+>_Kt: z"oC%{rP\mBDV,==[&1_/[Q ,Qn;&(7tNWRaM2-^MRaB&sz&|_pI˜jl-W~‘\ olэE %"=o쎲Ω)f5>ʼnϴ\eR V<>1DI$ĘH>qu՗DL; 䇅n5n]16ETdFƝoO K4Tݯ[ՒzMҡ; %|([pΓi,BQܙ${PtDQ"+\:8pG(6nx;}Pe`^pH;D=lsǏQk_v%ߧ-Ζ| oI˜Ugr$O[Vҡyp9dY8:FxL{= ,~@4H"RkAB8mIg?CF?%PxH% \-xx|V{-I0o-tإV]Gu x"-[|$Bb^eM ='2Q0Ѩ(!u*=sW 'pM޻qJXtF8ޠ MMqzY'_$P8|(܌浖RgVs2ĹҷjNsmH˜jl-XO> ;>X̌Uurʮ٤ eE.5/}K)[]ײ_+-IPuόh,ӫ/$!א`G8LH_BI׍O5>B;1݅2A/.' /'Au6B7^*l&so.O8xъR>Gg H*a(PsV2SA9A%Rܽth[AQ2iIP x`IVK42ɳ=?OX /[wL|Oݪ" bjD ]$z?R#Dʸkigg¬ w(vrU4+.&Ln>Wu$dVӵSLK4|㲴#Xj$:)&1 j,Q5bVU><]m nKQMKZ SQJE~I{/řd+3xP%i8 EKqťc`Nz$i8pr#Y/?:L=*C<|3l.4yUe%Od!! rQ(uR ZD$3 #HUGC#2qN2.Fϧ5viD}4dj>lY46{ez ȻJ菽$փ<5 |r}cZV~@%:ۊ}] ȃZd(Bx{b5# _P\>=/fI_}bEVK4ZC2ɳ"<$k>]HoOmf0&IxT(HD !< !$4J@S#W~AsW/˓+6<ѩ|ߠn %2VjcRygx{>`NJ2n>6WxJ{EʰKns25ҟ*Ns,I$ wlSh,ko@&yE#?KsֺPuyt rUXWWVB4qQ$ѫ8(䂌gog"Bd9)x" ү ٲ˳N"ybO rs:cIc]YR}a@&y7znZ S|w%]\3IwYX^ۖ t6R/c .x8^@'*浗t!vܴeeעuDf&)V/jfɲh#SշW i=,--UvyYewŷOt4ZtUZPBJZg`% dg8ۣ! *wNJ0_i*}K {/m? ̰$B۷^UiS2AȻ"<*n[1Ux%n~heHW#e I_{w}|rzL(գ Y\D$ 0]kSv(a4 C6ژAԁkJ7[؏+ DOW *,qc`1Lj٢/L,G-F%#[.򍚘I++c"Q gMBկ q]$zO+:7G&0`GߔarL( pR[W8~%)}aN>Mki7=x˅-+{ej $h/*{X0i.S*ZDb(_8NE#'S7U`huQhLpꆯ",; ߃Fs "97䕁:1Ǭ*e\#l #v`%UK xm:ZH~`]>mgN{olȧ,ty:X[t,kf;hB^V]>IDəPcVj|!nRxfp⦈dd2ȹ3RT|ϖ2&9*wl&^ܡKDERXLX,R."/]&Wu; zb$[ڭ-bX"ewCޮ&[$Wb e^|>nSxBI^6 X} 2@AdSώAw-j܋AEe*?Zf+D/ߵ.nD&_GZyI12ɻJߥMmݕRfÙmQ_iOe6Nh#~!BFox3ͪ%߇$y5Jd+'ē|yo!' h2w>" Rz,s2Tb.oy^<PbʝRm?DyWDml425']O31+괸8NF IBvMQ-qGJ/̴ycIj9M7S-{1 N{MOܥ_3u]RCTUhPZC~~jk?n*_ZnzoPVsۺ#吒иn.rvrEZ\݀&@C\Û6RY>1ENwX}vx`{zR,% |>-ń0'C~_I;hIԌ6iTV &;}鬧v$S.(B&yW8Y6حi,|rQ*]wXIKXUB}f YK#SD'Vc<2&2X}2Bd2g Nq'xZ?G?`ۇYg`Kky*;bM;k>8q>;Ņ}HA$o$-nݖʲC1]$ % ݊'v::FX' 7{ȇuvC!y0.$2ZjW'qielu>fpꊰdAS2Wf2J2m|ԅm% OEJ$To_"],@*kDDO4dWIZg.._-9H rTINY2X]M&HGВs!/kGAҲjJF(x[v#Z#/d#\7o͟2]ɹ^S)4akXO@3MP_9) DdI1@*=HvGڣL{~w"4oNcYK3QL|[KElc"""7{g!.µ@1 C8@F(9bAJQ<͠d N̔ ~9g]fHڅ)Ddqq@Rxn/TO@OrLnMeGtQ!fS`yg;BW_ _֨/äodoi]%2AjK +r*cNjvu=vQJ^3+j}Ŋ ړu=]&HڙmȪ.T^0:wÝ/ȁ;&ü)e辛ļb‰Mhhx3=Q$Q85`.ՉddnO;! }6oJ Ra1/T]/rHu.JtSǠ]P P~ܜs2/rkMND] oȯB7]=1{yT}i3nVHX (:ceZ3č.>bK U  ɞ-msH7UImýw­ލs,ry~ ^Y>Un":GZlhF|&aО"Ny'V]O zP8yU2AEk{!  [Vbc-(y8UK&9@P n*]?T+"\A\/&͖6 [{ySR6Utғ.*.}8 &7|wu_Yi_̂{kq TಋRs>BogGg6rj`H֝3&"zaqTrO ናNp-\م~&f%ԅcnH,d1e[4RY9mQT )@7_.oYbRUEMD[)Ie90H 7ry-Ϸ\׵tqNSŬn(b;s58/JG5~2l[R7JtEܥjCLB$yW <%cd>unk`nue10!Y{V[d?݊pJT{"s Q3unOҡе/5ps  "O#Fw2Vh W~j\Ruߕ{1 dy}D_qnjN)MDo$ܤR./-QBo<_ܮh^3-%]b0 @ǣvĞgc Fw_Kr1WM#H-\;5Y8|cNEtRdv bXXlm/{ٗڎc;wJ TG5/MDJjڏ  />敩,sxk;$˘n[ lVxF*6hOYh+F8˱fBNZ~GESG@7+N?&Y>Ք[1%ΐ:]P_Z {ǁo׃묜&$jk{ IJYɊ>Bksj ÑΗ,չ xɓt: [Vx I䒵$Xzp\iD = d.=BheYB8mXWK `N]ԜݿA9`BUyg͎66evNYYb/ +E|G otuDF-[I;UZ9/}2 4#J>ň2~Lg_P9 HVuD u?ka^iflyXUÁ_]ʑo5-2^J|Q])vi*ǧ IlY6/W,tᘧ_>> q>gI.{;xN:Xf<<`1B|#s<,<8ʍ5nY9]~s5 ,9I;;],$"AgK=@6s/:ݩeںS, Y߸z[H~U[bÈtnv8Nt^# 16Ė]4OZm { S ItjTqr"nϕRrMEzK8{G?4(fuz.J^$YCHNAT_#`c(ӜD/FKHIB7_qY1٥;LNľJ" _ R&!t2>bk7l&# "vjKGDr~IaD^R$0~Ps{|&f954 'r k /fH\ /.UV Ww,`z.ZGAEA2-d+2,`'fo2uX搖%:*?7-cXÀc{U(/s~v4o n%dz'_ vW/xqw"g. r\FdbHy|W~vt8`o1 LUĬfb#zyiImo@ǟ{1R[xnq\cpzo݁ʺpvw,`-"o ^x35֛]t\),a7}(Oʓx.2ԏ9ƶ|jMVIq曈 Yc$wm(#yf*:rR!'ClD1a:Nztc;Kh|>nqL8:拭%U^E~kryku`!a9AVm.;'=7(ZS x}dݰQEn32퇍m"&,"ߡt:o[geiC^'f3bKxHڞa Y1[^x Pfax!n2֌Ki'|ƧQY@ (k{  ys>SK2,="1#_x DL{fd|'9&2i~* |/ѳjm|EY$ d"ڃlD1ˎbK{x_gr㟈cs/kh%u,`չ@wn M{Ƽc]Z\v_kRlڷ4Yr"x6Dt/6I,/ hB9#ku H聘B6s AMntxIo)Fݣظkl _RRWX +S5<6hqd<ԁME`dFgGmGƤKnh2oT.Tu:f"mTN=Bݏ-=' "̵?';ɑI_̕,89PHz~=sB=xh{/3eڀ&X-qj.h2o䎸6?=ϖ4[M||.6/,aoia#;=ԓD&$4.!\T*v]  dX#{:USDTzmȊ͔@?#k$Z;Md=GOfȋٰ2gP\{# tmmF)$v`'->k,(뤁pxv3xoWM@+NrE>gKs y-ެR(IdpKȪ, W֎6B}Rm! +L+<*(f\qCPt^ c Ծrŝ)r>W=f_2\A_2{K:|/\u .*eh?E2,,)vO"2+pD`P{2cysӶk>&xzӿ&1֜k@$U(ތ8)șXz1c88i8~ĊػR6}ָ|#k3I{JOmѼ%+ȼc4?Zk'pj/s{qd=7ĆƇ 79 DM7mW# З˜s"6 =q4K+N㜻XLه&V#"r?WvD永\T:?U=ɰQ9~M?rH˅ #iHl̝gRV&=v2=-=n; yt莏@ѕD72ɥ/RUEe}o4gc5my_+~Y Ap~dvr/utSq'gq"0D2gZ.F"?oSAD;%,w܎t< K[.Qloݪse` 2x3',q4B;+7JAk+LGYȵ96H{r>T8މYLqD:N,+ =0"nRsHrR#K$kY`.$~p2?b&&Zc뎫SȽ{KU /JTJS܌+8UzEԾ =`dzkH㾖 #ƣF@UDcl+ەMk1UvYvGN-A|j}oEnZq*֏QŊ"+ر*yn ]An ׊F eRY'_PȜc.NkDR)&iA qrx gV>hW+At"oƀ_P|]$BWN ӛ{TJ,(*D /mV/wE2;ge.DeMK//W 0% $plPG)LB9GKN ?3%H,U/oZK#|0m <k5>MxFW[dEX7n vHR)x\+Q[T֭ٝIkBnDdMvg&:L&Nm!s<)<9Y"#/ -ϓ;ဢ={="pڭHH245͟GwOB~A+@7bC_JPtw }9#fUEΖE]$"K+ߗzR (DJ^vtF/%g0hҭ䎲 F,\HR)e)>'CV(SfYQQ+^d۵R/[@­\8G ql>y21O+Z8t?KZ@8&AbŘb9Dov-sU O5yO oh6D1!sMpU0dPPEa?ݩ@f"2OֈY?"@U5mskڣBc@`tAb$O\G㕢+鶠h?Q~94|ك(n-ԺZ n1d)@Ps(}u)z6Av*^ r %: }cHR)eXl YL45g)PE&ɄeH`<1\Rb",##xF9MJP GViGRԄv"?Gdd"fH=PS41fX  ߗj % h$~i8!!g/A՗J$uq" ڶO礥oRu6h]p}-̗>kS@zh2aW=)=>8:Ŗ2ŠIPD#TڿxH'>[! J^[pZVE_U^{aq '$G9=NѰ|lټ`kAysOxdeYK ^^f- ytR#naD1ެc~E9,"N..B&<@O5qZо `bi/>T$2j:H&9(xU8L~lB[%P8I˦)`#᢫[ES*4MmE6O<!<PTHhI U-n%nVN,TP&Cƒ   ǠMNb ç%ؾ6`EF5-lӜ ^.m-V^s8D庮qAʍv`]07JqDX~/a/Gd.=o `I?ϗ dJ)elfPŰ8Ar`J>qjz$]&vP]{J rdƆ&TJnڃ eղH{H>݂M*48<Ɨ4( #)#a0kx& +=Л1f0ݟ׆8k.RZ.NMv0*ʦxA&~(`ˋ NU`[?#C,0re^Nm. ^9NyV WpupP&( ssq-6"a5cJ +  5Ork^RZ׺'dsbwmkbر_ 6¤i ֔c{߷fv0=ls&{cÓ+%FP)ڃ e.VXYz&!X7XVy t 7KrQPƘ' .7Ukwj9i[X !fvP]qPXvET?y2;K:(U,[=frnܽ E 7Ғq?h!TB$b{hvB/r$}Mg?n7uvBŽ'\+CĦ).J8z,uo %XX!%+Ʌ,'`?rN?iϙjQU|ڍ eOB߄!VpvIZHY(jk5pNԅHD]lDu1"'+I"9Pe.Z<_-J<1kS>镇a]QcRg6A%ȷlYQأҪӔ@wk/JB䷥ t'ATG$"k.E%I9PN"'Lb 6>HAJ&#G~%y  7젾(l yU.U3z?jˆ3lY;g[(nb8CvL[w<{t(]DO@W^N~%x>{mN^'A} E⮒ZtGI*Q t/<j(eUl?23f:eʞ^Hg͆)/6瑧J2GSڭS'շS˜4 c4=IW[h =|Yov0l\[.ATqmXBeU8Rp~@xqW+d bHU;vUvpձ&EtvPq3|:IeH~ 9[[N*]S@ј?xEg;_\ӆиq[z}$!{kYҫ= ހH{J͈ц,£PaF1đ@ڵ$p!v'ؾ0D$i Ψ*@ehGݰJድlIY x7>~,*j_~ N=Gl&ARSU|گ('YUy$"t;P4b ہY(5&o~82SzY#_f,@l5YK 0im}L_iz=\-gZyӀJ)sj^,d^Zcvoq$D]6rBV$!I/xD" vK+e]h PQTZ,bb$Obh%6֯=[$ێ%QD ;+2ƹu['c]l`2#hWO? tRe>qި,Izm|ux(ɇm_%-ls!՟(}VhمZ i'Ly1A٩9(bmoW0(^cgS, Nި1ƴ\켅+$˷R6>9Kii5j#{m"BQ9)JXSgdEO!mЗFj@S]$j0P/F8*FvJvxY]˺M\gre1X6BK4vV]pv 6uۙ6sxq< >1;ȲB+,i9&YK-ۿfX 1Z|jaaea;ׄ(wpZ.􄓈ݗ&(G{wPܛmR؜<_#Pt;f%3ֶmu!JXGDTtk 7l]&;ijܓTJq:ME[rv7+y$=Xl,.̨rr5ɯH]S"d>F9<"9?@G$KX,Wnqv8('{! bl-B؂~wDDꙧCvty9Z/RJ_ e.ԳC?%@aBa3.`ɹiO-#BJz襤*ZYUx^2F"d@W.A]$j0PVDY)t6MsщЦl);|R+YadgNd^f;h!*HkjsV/IR%mS_/|+#)??ˆ㶲d]Y%VQ6WUJNy2`"~1ߒPHFqTh?b1&n'iE a-e R8FI*Զk"X Rr+Cc}&NP"w _+ ?͎4Z_sh;wrIq*9G/Ւ\7e-U-y2ܧ <"Q5#6PrN !1ACmS*|3$DlY5PE1:=Yòmk`ni8>)$?fRʇTZ<3)"(Vss Su8Cxv!jM";;IaX_k-}\9}6vQ %\VSP5F]}8b;.NI?I(BE1Vyɫ#!KEg-KHvX=20P { 96tǻy;𝰲8P);&Q>|EmlkJY -w JV<#C/ÒW*Oģ{% y!q=j ]h9eNj09Cծ_b[5:;-o\(u"w8e|-cL?u7ql[{.*'+ʌޤgń"OY-a]ո㡺 D6HsԤUɯ&1J_T5Fɝ}n^%B+"k9%Pv^XQt~e?eYIPÅcHV awX\gtA/VKϪn8A2Auu*Z}ӝ.l\+'4yomR?!;(A,s|KolDH("Yt (G+Kv`!U{@J}5[^)gc48a/{k5,F'q(¤XEMCe"{ ݌;DQ&TJ5RL(3r Q^"MF6ZroCx.HEE !qijI_I҅f zC(ɕDTEf؁Z*ʹMthJ1X)oT=W0=f@0Mce )e Jw9ٮW-Myq{Wx:;dA9 W#иZо*Ev@uvRQٗ!$Tt|[MNդE yƒ]`ĽNyݫЈu ;EӾP52\ibj4GB $ $;-`` d  ԭhǛzh?ss{i/fBTwmwRf Hc~H;Su^diO` NJ*CG+c7˖% jE1@4vEze4<;CBcn<ʎk Q@  ~6&nCډ| .*e&$ _Y ;qlZl-1ċ`>0 Tqi/=+6"մH. UQWKqz^JR#Xz%Dχk5^vq X6H=ƂsV9'[F0aݛ:-L(jzv `0Z>g|aΣIMY<3bC}jO@Pv#fXB*F@[|!W "<51DTge@xl,y9E|= " |D)UHXr1wcM9B( Au`<-ԓCU1Oc5H%#] ~oңEi(B}jIr]vQWNq֋Ο}qC)XIeo7m h"6&\R6ggG{oVKlğ4:'(>0; pPݵ*Dub&~н.'ag_5Ldc:a-!d"ǬG%j١]!bhz"څ[Y.L=o9K)3 =`kce()xL4v {o#eɈ"'DUJwd:4Årɶ<=x9^UdHʪ+HSCJՙW+W5RE2:fq2YY GqS00 wSkϻDb'M䬕w7Ԭ5^pkPm[l3Tᐋ~Vd1+I dDm3WB}hO{)Hkw@6!o PEo ]j1A.2-9JL\ gPcm'fӻ,eQzM&=`1૆v,dihOþPB,QPV[|h`si"/;>W:i,qps ˿gֈ 붋b~0 v?:gv]%Te&D )֎xYkEL37{Q"Ԛ`,J[/[28 $[b\-"1YFKj(v\$6MF}z_] 3,Uy[&30X6yZB]h'Bu7QၛB>T` "|]z\鱒I7hy0|NZWS§^q=KlblAgvŏfa}M{9=At7X{2>kw. pe(BGq?3X"h~i(sA"+ԦC95b&WeAc$/*b$-K&!:QviQE*ܧsL}0WGTZXq}b0$@ ]hrĨR ]Tֆ!1m:vP' MRu~?W0E9EG7b:ә&>zV6DAh;04{BZMC$ (|A z}vVa/Tiˆ伲"IҕS>;Gb7,Ю+N >@>oh=pbkL1~Cİy#c="=5&Zz꺆ƦFE8f_eI*cdݥ@!:^v80:XIC+$?vARF ޵:HC2+_4ݖp3ߵKVDҀ'cȂ@зc]j`5{r IॆK14æB*٫qQ7jA1%"'zAr D$t[@(DMϹhIa]W+B.&_S7#\ha\wׄ5ReG?,Yӏ>VƷ/n}b"Ѐb *cL+)Ihxa_:v~C,Իpx6 ] zad rk蘻#"3TgD^9j5~i`ITcˠۄ{\da׀%^^HL!EtvWcj|S#᭪7J 택k-NJR}r{UϪ}6y?dʝjnHa}L<]뜣V xu*i gC2rZ&Dr%w21 dkR ,SBC ,z{Wn9Pt~>̪f =wO+xߩQ?m\p z-"sr.bje8ժdZ =?x* |sjYy[ȱ09Dp͒_>sTMfWnIkrJKs6zX|3;A~j zoc-[7ɪ${mrKcgы].&0AS-Tp憔N=(רș\Ap>lP쳭ok#,(4fFYq^!ϳyo pf0DUHDL!aQvܚ bzjcᔠ_$YҸ"-Z$P$~NLKȍm]`=H@{uF΁6 1OIԓCd)C@P?Kugf?:mFvwj뢬<|ːyUt$b(92xJ#vۯ cք| : ].;fw599A l1x܇*/DJȫتk Q]&-ASmRڗVJd<SFi(=S +a{TQIYr6ed- b{IBUw69L\5o&dש#-j$dw$\Մ[MܓHt$8+̎=9H'c6P21`2lANXs0zFY}ÐGxjJa";:UQkjbϘ‰]5Y6@v29B]lWQH Ez¸r~X6ZB\~%{U;ʘI4]:RJ]wn2bq(Wr4_`3r f?%fԠ.چ!vC;bt$\_۔ hlQ|Ě%_zGHC,SP\frrq_IxcհK:*T_OPֈr:@G;R5 hd2O~8iHMe9Ke*Uv.r^FVx 70$͢T|ԚJ%'Ol(S.MM6\lnuP< <+ rpw@:N=8CFSD]tL4n/ 6SP `;C)zTJbSGd9bXo1~Il2e%]|u,J$<%5JOsQk.>9d7AFA zZ 9+z_`zci ={%0q W+}lIhugX4Usd' )r  ݉pX6 /ޘPWU aϙ!|5Wkc,w&Lxw,I-*SuC'2t]/bwxEP,ŭޯP.nֿrpf٭5)wPDB"%7;) \MJi֣G,|fk Wf#dReu"4,Vy{B\DKHb,3nmRcD%3[D@x/xI0hLzi $#gUy5Hk๮$K.d9jwȚJ1ܬ"TA[M&ۙd ϳٛnj ՞r ʃ$VAªr:$1\uJhq- 'sCd(ˉQ%]r;,+#^%ғ5&W,Qu06^D„G3pI(rКmJNq[ztʟT`2l&\ }Nl-f8v` ܝ=@Rm srC{-D #GB@wpkMjG\ ̀\HWò&UrjB4dcO- '=2fIdMTvKڪtg b-(@DY˰Z73اdW'Bb ({鱔]yu߲X£L ]:ǔ #J'+d)A`P ` <̡a7X"s6-PsyJB16ׅO' +\n60Y+Iح5&#ģ aӃ|N&@Q`p̥1~|Fy,X=ipG 5: Rϰ^_JGX[57҈Lĉ{/4(8ʸ-,Kq.v5vNf;Ҧmo}֢}֬S2#1O/~sS]]Mhŧk3;ـٿk$Ui q`Mhe,9/^pƵO%G,D[rI6PCmCTaUzz*9KʹWw-Q5))%vA7I$(%|>A^KDcYcfg<65lJ|;]5 wh81(;^HάGT(+*`\gKy4 ^v /JIkqw]HCΒ1Ȅa kaooi+r%JJFMDd݌h "ԻH 5]VܰΥ0c1E] !_g$\׳)6M!b>9+~N=ǭO&:5s9/ҧTFy=5y[L%ͦM4RX$Bɏ6"CoNq$l1FG19fQ s0xT=llI_z q̷Y 7I [j oS@U\ps vIB'O.MpXVŀ6jv9xFyĸ-1B {ۜm`7t$h)b>c"zG8 s](M29\m]~xܥYhHGddG%#ܿ@ߐzՀ<;O^sgmO *5YU>V%1"@\;B::JO~Q_G/2(s](w~E^n9*">Āvqq~>w8&&#&U ņZvX oW z/9=˗EEʥDZjO9W%e^w9itLxŢ_-'䑉M @3dPwp1 d~̚.ɮ4&]S+BDyǸ/|F81f$p$Tr]-8/Ž!kn3چC*`t\p溤AYMX@ZFՠT ^`x!Q(i`oRd۬A{-CɜY= "zJE>HƲ~n Bpv3Z<^ygµ}8.qoܲI ΟDT3܂@':R:Wh@&63Tz"}Iˍ#p9*;|ժKRkf{c!d90$x&YGQ=yFvJNޫ53#ob@ù֠ݖC7K@D$>$^ y6GG2+lۋrU}V)8aJxp$T++$BZiG ^-lsZŕ^wFدZ09^OlP%e=Xy_U&zgOgE^Qw=,\}HMLlȃ!CvYR6OOt;?<& tg[^iX6~!FLvy99 ^NEH$LbV]qrE.<2D.$P#ʧJ eNGD#=t]s VKژez-l1PVP# Z >MpV4je$1o8Pũ+PEɜ o&Vݶ&>"bImB/h-dv tOxF++V%Tg5 *OA1'."dKWvq96,-pPsEpXVCL6([utq):<cѼ(ݞuǦp/.ɓM|3Ƚ웭pUuto~ZA?ru>wv|%N"XPy΅_hP?Vc1KYa<R~̀b|Q\{9p8V5AY/:2 d eTR@j:\ {U` %@M36-@E-;G/)<V7x[adJ6vlClr;57-2`ZY0O= C8&AכkM mWZ`\b.BƐ Vt;6%e=r$SdRQڧ~K80ɊڅOen7.hAX7id8얟.KSUa~7k|lg݌ehzVxA|US^܃X!gI*TD=ΪЪc= aΟF 5a(H%OvK|dN2OLU7uWaҵXz&/ 3D0ܟ(B=6fϚWF9}0iP 5R$"7^I[^Hؐ`9䰨%0WhDqʘ4AYouo6hʿ~ Ŕv b2)݃K.(aa,M3BEcY(ʭ7t2zSYG.Т.-5+0L 'D\|w2u>r#a:?pZXwDp?wezA uuu䠬'3 KݹV&iiz{Z<%&,.4kZ/<-6|DDßzA1WZeMbһtWHw%B%,0W:$I h;ZG>tXBxB^a@&;7ĝpX2_Ϣ6CLV ʅgD{R/k,[ 7I(A3,x,GTCL%7CD4|'-3ɡ?5{ƘhVrO3b\  HMmYeR#J5k:ߜ7Pl3 ̏I5{ߍWSB*9(뉿yF[_Dy rЋH|ex0weo!;D1yce)Үd%K?Uck} DDÛ7~n]IM.&jrImSɻBNKtFW[4/K0L_]A'x"Ckd"J~m*e{ċXJK2U{gʝg1X 0vyRVmѫi"7v|d^}g {sF|!7H-a:3+5)5elQw)h|Ր j|cO6CQ/_x[OڱGT7-əEgy<QR41uWd]`5+Yf D"LDq5X%r@waTp[s(sC ʿɘNz QmCI907t J e3Th?^_{Ywzn)mxFU=G:<b[tY Nz|šdZUTn駧@( '{$I:甖>R3I"<+Xnd{J:D~=ު%NDRu_Kk0R^0hs3ne=b~ ECdz7V F͒("TFZ |ax<کPlv"eϵ^H9)(6)8uޕ,S/)@َ}ڠeZkr*H]9ZzBp@k Yrti ..9TzR=SA$SGNwl [Xl$nE䫮AsjKVX4{ 8l$rvQE]D8 ;7eAQG `qENPPCK5v0^N=r\@" ,m:(L ġ0j>zZ_10TN̳u7&o fb&<_1 #*si(1k FULq.<_nݺܴF11qH0 VszɆrKۉ!й&aD"Xemd;J&~q<{_T+{mo~(yW[ AnDCwX}[8xb`$2)L \ UL=^:NI{4{ưCA6E6izOwNejüO:4Dzǿ9#RG7'ٟ-ehQW[~JbOM;x:J⍖,Dɟ*ā;a| Oy-]P>yĮ x] YRgYؼ\s!#LEv*CQFL[Ӧjz8K#鞼5j@Fޞ()=]ÀEl^"-9(t=<5닶~Yz '@@wa5D͟'c}=*0*`sl]k⏕{҃hx3͎oA:m"3Rҭ *2Ϥx 04ٿ1rs ˀ$]UaVb>$ Nw= zMkN^x;o|zY&]xpjkuW2kǢ\'tH0 Y$zIT>-c|YGWrFp0,^AF ӯDs6u%KW0G߲eY=)ՒD$⥕X -KC]Wӭu ivsSQxHcd2>'+?ŝ2Pe%6fۯ{I/1 3p@Pmm%e(9{Nr&R-NDq zٚW4F_[S*ׂ,YS$l DOr3K$zO@1t58ۄam"!6🿼b3$UR*VLmB:E_x"BظjpAû\ڏv y FU-ǀ mCP.#R# h=k5AY\Z"CἘ%0Gn0&O܊ Q8qWPٸwG$q1s~"W[T̆b+(0HKPxahT:N%qXb9 wG@y2ޞNt$S#ZJ) v !2?6|3p:4 ;X S~zĂ8η;{Ƅ BF³tRmxn@M}Y;r&h¨-Ä!L!D.ƅNYcJb{Y 1wTdJD #/>/Ӎ{l[Ze KfVPj6,UM $+X!qrNd`RJA:mCToh! s{;ʰ7רbˢ; /\ j@fO|k T'>:ŴkRkwy\|g,vݝ cc[ $UC9n d5Bde}λ༳_oJWDd{Jΰc7[:GGUPϒ55u)d'Ч#݀eQr{Uɬ0Vۋ則Ĺi}`JM#DR pQ$7ul'Hk(*; -A*}|oRe `S xAiғ*xi$0RES.'CPVpdeڋ_ݿ[e--%˽,V ?lj-˶޻!70EMH ȌJXٔfcZtOeZ"#u<`AYiCh;{ n]gӕmD4ƵB dyUmP.kRX~d)J.~gwy09&I/P9zd_6-L8Y!%}BB}OdZKDybDL!LÔnU^a)nBEE${2 WkH0|؀ $^0ƇD.@!:EuvdWy<2:MDh#Vĭ!F'?ϼO+4S[j9 ?k )?ww2ï8e{^ӕ 0)¦)IU?? E^C(:qZdyz rWb ByHݽP.G^nG%`WYezꉵyi%hp~:4 y E-[3ZP*{/~/Oe, ,dj [ 꽝Z'pBcO(V<AB'. 49ycw]%={{^5|,J D[G/x *+Ӎ+Ӓbrʻ: qп4[&a)|$=^ 4:p^tY[> _͖ey,Y,9OqY#gPI#3 mo?Ғ]L>{sIl9M^H>ûP R\r6&]xT1'2f;i2]׽SOJQ q[5A1fĜ@uj>y5 F*?f$.D}_ T@Qƈz52@_49,dJVdyqOtuD0އ[?:ݗׇ`{Zվ荠7*/dpVgk3+"K-թR}C\(=x 'L/I7tB Z+LkWDP>w^okY}KŲfaOZRN“z(7=~}ld~y%F]ڦMS)E)q >pQ ET$ɧ-g(Xe-dJn!g]:J,H ߓk#g!^0Ut]Zg!dP'L=j+识PYm`Ⴓ%g'HKuiW"6qrI'Fp4{͉%,-]^A !2F 4JjL L`5[T!_x!F?:.|X3SVA}{ 2Y gĘΞV׼9RAk󫏉[rB2Fd($5"I5 [y<"XpV9Ar?ExvzK! D7kgnrG+_o$F.ɪK<^p,8LAi* l:pfBPRw%;XO3x vFQTZ˱ efݕ mj,UKsjᵝr\y~az.h @8Ugwȗ}D*Vpɵ:luV7+bKbV?1CgM>( C/T%en|@Wv&~~e[E(IR)Zt0=^s!Y`ɮJ]Rh'u׋t\(sE:b=RUT]r6?78LNFvzQٺֽ2>ӎZ$ЏDp2i_/#a8#&Iη"p~7 {w'`@]왥hӤBPW0EL˕uJ, W\TSKR͒)9/.iM 随RvJq84L"9m_>-z_`$DJ"RFFT,?-:Xڑ^٥ͳd[Ț?!zmBߵ3MoڸVLs~)9YQQr۔S cҜ#g@(H\-m$|Bf6W鮷UQ`m͛bPѓV|)Oܧ-r쒺aQ_ru Z>G?Ugz폟U`kT!a!,nxlek7>hjEoIA[WPCrJTOJ; -FpV1/x).d!t >scX a:e~0JvxSu;WxLp"i(tt=Sr= 傎D&r+2ǒC@Th zFE㷻Nc{)I%\sq)d;oBkԺ*MzY!9鶽4m`XQ4DXW%R.^dF-B Ws>~{#Z*Sspa#[ 6O:+HxL([5AYOudNZsƌ9!*e~yϺ,}^ytcvuI-b͑ :tI '䕈|@GWPRn(i׃"Bg֥$;tfu=9‘!nzFqEkO⯵AAIBDI=mtKR *\2QԚW p _:0 f;rI#wkݙptԈdGS%eDZDsbBokEP} "VX?ex f3vdG1̟>ÒWt!8`}O%M ˮSe$ݹwX&gI/ST>7m1@GC16k$G?܆ R' CeS/[; O\[C#':Ih" `X{f$;017+-9(K C[Ĺ (_= O&PY>`olUSdVh Z\E>Ց\(zMG(JTPVe?U^J=O?BYݺPUuE-د aÒy=elpz0ܢ 'ż@\KC,r'3 #g+Led/:Q$;*c 37s`?6zBrU^7C$u>31>VAUwxv"X؃,yO ">*ѡX&g%1 DuSP>>)_ek9&Zqr#GX>>w8ֻb g~2:.aX(6 5wU*t݇;)^ A)gs9!.+mCkuY/p""A6Ő4AY]Z 4]&UIZ'@>eSdyh*xw"b,A~yEfXRD)o8 e6}aDO2a vҐPܹ(.eӋ tݫ 4.Ju?Dҧ)z''KQ {GBo/c0mHsL}bA[[ #}=r,aByw( ceW.IIRmxoassdh5:tXg} dJ$\~jw(zOZMP:V1JnFy"$a^Xl)/: /Z]:&o(9q6y8kl @$; 1F90kMw y[{:9Z} E 1#aC$y7o;E% FU~dM(=w =/ٯ U9:ꄸgޣ_ NLtI[le]Bgzs g 'L\)RoUR~o:%+}qLmmiޗDD0!,oE㑱| 1(~BwF# 0R3F6rؠ4d b̯-䠬Q8wdY\/@M$MH$m}V=ʕgJfI=%j~Vhѓꔿ#)(!""E{`WTX_Q*2 \`CP=~_"$AѼb~2ڢ}~j+* QaQdԓ^bNmzmS+p ѐoS ƨreAݱa)l,t c]oߨJ2]h/dI5#M( "i, BD4  #k 4b~(rJ&P̈QtV_ @D#rXP= LXсmI_`V~9(ߥۃ5oX(AJ'$i=AgI@M-F(FKBE4JOMs< VvYNq.Jag9+v5?S8AȂv N;sPZ˔ o+㮝#P8O!HBXP=7vٷ~;v%|UJ&Y^ɺ6qhK"D4:wQχtUX!| eºw9a={QJ`Nm{Ej͖tY3M+ `y%֮XH]RuNapQ~:) V$ȵ.bN]-a*s9hg\eJڣ Y.[1lvުq`ӎyau$mAYd@Dr$w_[g%z5-LWH8Pkl@GSXzfpP&lJ¹y">@ zG On9\HTTpL:fB{I^| ]Tpy_ DiRD(zHQ2)>ݨJ2NmL"3PBSwqW{kDDO' fN76'B, _ĺ2Ii1kJH̻/H?%CXW }K\?hk/}l84Hx<{e=}0s)ՇAzgSYzI$VײP)>da>mhku7Ȏ3 Cp9iAYjM_n%-=SApg"A09{c="do-/V$ cMLɦvJ -@Do ˄CԱPzg:L-| .|wG(1jEE`N6$]?[BWg0Il̢wSy@bk=C6S{ϯz8)ifd@'hd:f=>(ǻP@X5PJ9( dR!a1pLETd=E mmvq˧kԮoc̈Qtf_)D3KZș=EȪ~u;)~2U}*PrsHz$MHKѿyn0pq AOlJt?Q*t`@h-8E<Uf<pq]Q_Y!I&9(+KC8\p* zS守F>A:}߂[? Cġ26D:m͗s, 7#N8T"kϼYD_Av3 {d3fy҉ z$p|I›[vL wOHG9:IKtm<HmCÄ ϕAloVo'-hpFd\2ZW/OKkRm|q6xcj7#iJR!lk< "b|Ka}a!pblqytޖt)kсRɫ~P˖E}~|^ݔz7=S-9mBKH?NZp{ULBu~>8A_`*HWt&-3VkYh;P4ܥe{ TPrKsȶK ~g#'tْ=.-z^\rp aM8 k "}{OA{yi6`T$,{Z3_qR`;UN[л7;Z^rPlu[>9E5Eww)kB5.ET-AdZ(IXhT"ϼGɆ7AQqJSK4"LlX1 'qB !(X zGX(,$U~O9CC\Bg7;2{#F\"*tNCzhDQ58*Ur%{[j)19X6AZQWfI;X~jDDo {;_%R ao[@Y ̾ W˱GT*MwezRX~M(pKXc_PoY ~p705,E"A33DF\BeN9dc*$9asz^!8Zp9[muz3(TZs jQ o<</sI%[[A_, 2dӮ&wW->=-k,ӫ%[OH%4;E|mg\PC78o깭?{ޓTpm <#5:l `3!]jEWpFpߋ/(]j5X}F2eL8pF3K?8@;7rPj7oFa{a'P9Ӂm2{[OZ'$(G%uKBNyO\}S9_Ԓ1k-..1dJ Ak?=q$f!?8mD:V-xNMF\"z*mi [@C._%Kk9( ҈ZuWIFUruTWEy[k{T̙_ӷd^sou޶<%!v\x\]>V%w MOm29fƜj'1VZTUذJ;GG*z“+O/9gՋ `DE)=p8r Gdސof]+ƠAC-x)P#/OIReoK1@5R3%_@ fM, &h $5'd񘓫5yގfjz<\#Ad

3F ƧU PtR Y4{[6 q%9s-G]! P*R0.lRA93A| T"mkG̵$-4|j[k[T"y6<`YPa}] DD@s` =˚;@nN3cJf'tᤞe"NO"_l5K~p l]'M"uª-Zqݺܝimze6?UU}ČuUMa'zW ^/HrE@˜@.,35(^H^(MpO!U+,Ҡn"~qOn;nׂ ܷu6[tH 'Ae􅛁>CyjzU+: <-S2Drр+2e% wm  0NMHW`ѣJꜻi?B&yΚs@A @*w1.07;g;IFgM5x%){;hPzAk"ޟD,ثr3ݢ?Ay;t7弨3rзzx/ jmҤ8<Η?v{JJe*53Ϯ6G[y$DC/?yB؁0o}b/$E# L GuoA2dV&IYla)m4XӐB D4|YY ܙXk{2wwq-cg>ߠ!D;%{ֳLi<"n3%W DCUp1ZpڌܭRor2~ m 2džb\`n1y /RA 4){;[@D|j/c, { %=~1"DZs=!nGaκR3b%!8jWabaet)-팈~fm`LB#@ ~w3\wÅkdd׼O"w“4YWyAI09s*jx)-:VɞYg ]/iO #y%CL1zw</HrŭA?p2%{;4q6(QH;PћasдW'3ώuy T.v. s:m$S^^`4DSbPkM;O/Go:j9jwX#S\s=;r4"W4"=ObMܝNe$ock- dv@L6)[NN}@M8hQ$n#p=R.KʐX3H=)EԲi%6D~_}=ww>ȌNXj'X&2X6Wµh4T]G*>LIk~qvm4=VB<]d$\wxKK7c܌ 7X+0.#ٓ'q-}$CB Itʃ?Y P%{8En;?2Duoƴ{Wl~b°Ȗ>*>AGDoFjd.o(=L-%/l򐔮{&U]fs+: \"Jv}0Q+C;nRJ_)Z95Xb;U%&L`XWkddf"B|}"&cmot?Bho9U1X}XYsgXJ)]b~)Uy~s (p 06;+BբI98;*, -O,59 `>+5SCwWC~%7Q؞n-0aHvݗr+[+ĪǷ.8oO-4e 6VbZd${62*U)?O,qT2?+.9Gr4/ת(Nz !ĎyA[^3鈚d/3îև6UhK %\~L{A`mwS9IV_i jp "G'_v>M "uųĭ dMfQT(z/[PkA!,\ D4rlCrA>LϬb͸鄦iZ15`ގ!1 qxE2 *"Tl83&,deXA{Hz˯nrrdMe؋@K[8AQ]EvhU]OD[mB}뗻#ԙfiXY|bg0 U&Z3PO!vN1-Ғ<6џ׊nLu>-!%-^a.HJ[\^&\6GuEAtȸ8cRιQ)TW]!C~ +O&",6 φIr݁w~?dٍ(le8rϞ:Pd6|r;biHӴXV՞!᱌*|M0n:56>7'YCg܇auUKg2+F,b^afrp=`u`&¥8*~ԤI΁ri[>fJӭ%j`D+¿'*egm'۽= 5s\)@|UBL`G/Mw qlŗ!jl ޲XUc2 [ܶEOnf7.7G4Mkܯ2х nք4)@;fsm=`Ho9 )=xﮟvBĈH>$9FӺGu]ץ0Mf->\| AHӒsa;LI=ԺEdNFܾ6=hX],d†qW&I;5~ Y?"EL`EW{mG>lOIm^@Ͽ|,ljdY VUMSz%qK0qV-1 "6\o Ƥfly/V̈!7EKMY6^5`su@[xz0341s.g{ķ;'˚yiXb;?B`7ᒼ aFk~U0-ayޑ-&w$k -o5GaE,N[5qIf}%W@T1 `GQ l3 HIIBn`g/P{c f eĘ9wO+#k^wf;5 (R!I fݧemL̿Xl^^kIʄm1Mr.lɤd3eyևF-q}0?">e,l-B =/A_GgJ 1pd}>-` X4q0,P%.)! EXDCr~_]b~OP5kJ>##bL;E*A&xM0$9lzHG6*[5@GX3'BĬO>46Ϩ]S5k-2.oJK΅ΝN)0Sk a}3j8(4#/kq XhYIb;K94 e'}ފ`[krEhi6 l])rײ'Bۼ*MT5n)6\ -\nl8(~3" A֓tTK:ysI1Ad } O/hf1kH~3{1N9'>iv2)Úo.lb: +:c3e@ا}.朹$^e9엿~jTYz& tKP+( c pw i7ېZH>okZ);c¯+*U z/{ȇ ke =,3ܷ79<'GKd]?B5oW)^ˑ;9ZN׽3([^1MxvR G} Kb>.iɆ!s^VQIǝ2cPMٖU\ y.:qلł[sGfkʭAR>KrG]i{_٫,԰|mxK\!%Hţ ܭd7B=rW Bi#v G Sx=m vAiHc)IY͂lϨh|/FSoHzY7K+,ӕnl&镜1{٢sN+.{;3AiGO}$ P/8(2e?wDBXf4ڳ Ib_?O" &d"6*q%QE.:P6=dg%ށUil4::m;% 9WH>}6;9.瞽,jD;`l NN'NJ˔}϶>v+I,᲋9ȩшfD}-2?t/-"/Ps.YV<{H\Cd K}P4j /[Δb {yE_}ٗtA%5 Hf>.Ć!x&?)LJP0d^dWDyF=p^F<ـ”YJ+ta|hV (%{UAv0RTu H>HM-לlJ Zw# 7òX`Ybott,KՏ 2DN.GIW*Mn;`WK7(Jx͢9R)b#C% B 3A "AmT!~H&vW=hՍP8jWdjOcYUHLO@r"9R#aYf4^Kٷ 3!H,oK(]8em$f(#lE mj$ilաvj'}+DToUKrWжtg,^٫ӽ(1oij(^?DedaY;鰘k2 =UX c9kVIn-X|W61mF- *Z|@Q$elQt$]V.V7&vHs4 (6 hv'6Wy~^)ÊjH'!meI"lZmG)SƇbߍ"Hz*=z–PQݑӊ>Wㆹ7=⟆¬V~266cV93 JӰ,ªE&97c)vA{ҿ樝 " Gfgj$}XSƿ]Mu]uɋdCEӵ (|EK.eH,]vvf]&.s:VVTUTu^DY!*;AoxEVK7Sڻ&Ylyk) ^ Ry!D6 ʯ] n=ADy˞*߰1-97˯}g~e!]P5GwE$U$fY%}\XX\8e]%ζ! "Zӓ s! "A*|v?]?訟VUVps$QV,!׬2Q;0)= _RQ~amt))(5@ |y@W,6WO]cܞQ{Y:Ȧ^얩AiW|$?ɉb[חdCXp˚(Wk(6) q h1,&Azޫdǭ1yn\֤*7?%1 9gmN=<D_ Q?2s qMR,_v|Q3ļA6?W²}®asN#(M[YUeMܨM;n/6ܣ&}Xz^ 8f3E o TBZ H?0V#b[V,qFbMW&KQ {mט!7x>jJd=l=N2sj|}h:1W gcHXgy|ﲸc^x, "@Id`7|2Bq <?uV۬ Uru+%g~&!3.٨8 8S5OHre]3y75d]_ee.3l(]hԷI۴a";[/jЗ7t %,gwS#QEܪF3zqt?&>2[ϔ國BD6 ܴ50W {#1K1DŖ#$YP !<7uW$-SQ郾0zvȍcg*O-} B35~'Q 3K !$jcja+rL\r| "@sKua-` Ztɚd Wqv)P2I;+H|nn9?(BX|g]$Ⱦ7*x!Ǒdu )56{O彁׭ם]b;!OQin#Ԫx WsC 5,S"f’@Va5qs>>V9ezU1"u}{OGN82XJ ;/&p?_lKOU6l aiG)PPtM`6#7zP45R3,,egf#ZO/ R,f> ]86̻tW vӞwD]OBުJfeڹn'$FQ,ª2:MrnJj6~0\B<&2nHypH8OL1r3`r*=+$N!!#5~E7Uh~̑Nm5\Y59+c˷,&K+ ;]o\ez[[,Yȑsdz:gy~)+ޯՐXù:(94̔~a}s3Bjx: Oui\> }𗦲-wiGn;;u81*0HfT IH;;UgVEs; A ZglV_0@aUt^eJ\j- crڈ$pN*Z*2%[$Ⱦ}t2(]l9ma?҂fpzHRc 5Ǖlzt"7945tj=5"ז{ĕ͚GQ>c[]N(gDOج1J3zOq=x0!63T(d97J]rT]aLxo)#AnNJ|:?$Wڤ,r>C9 gDZ܄UB%WKu3IF QGMAT8FFhFf#Z*~ݜla5YΙFvw]AǮwcℯeUUlZrߟ{^zݿs,_Q1MŇ4˘L4`E;OeDq_lPe`nwQ[:@ڴ9 cr1$p4#nI?%έ$ cW]n FJf ;ςOM?j[*Mp72cݷ^| uK>ڢ[‚pH! BRBfZƝ!~215_O黵GrJފ4 B^@9zfRr`Sn(1y |3VrHÃ`{$Yh  U),7TQbV$"DfmgI6="X(fqێVc`Jv>%jƔ 㦩Lu_<\;$d`NmxcːK@2ueuAY5o0$Og^#mqH-Gb#Ⱦ54D 6.c8X%%<<}%&',P6b~e e瀱h_dԄEDL 'Y\r ڰs.깕o^ Ve`n]v[WLa]xVN!'/TH1kZ"KE\^HmTnw\o+j"MxT\9rC9l, fi7 {D!N/c4^.KJUBӀɁ$\d'*=luj[ȣ6 ]T=ZxvΞU.gL,傉x41 Ou- ZQPT ,e&];mU9(kG /{Y#s̓ \`7| LW67gdf(JDz0^4Dx^@p %%BB=A%j!ןca;7rj2+1sg$Oj{Px ²B.yp VefO>Dj%&a_݇8O傘vh2=,T>o:ãKu;\fL#cpwsMU֍݄%J2 x߾Ę>T;˅;@>Fezٕ0&/K-fd_ 1WWV)#\_|Q„(R Ei)QB\_vNsƈY)r42ž VV=3T`}1=Ad2NfyQ<9etAY0&/].Y~:Ĵ,plc9?=!@7 (ڣb_PY:e,aNRNJq|+#R|Ц&ۦ:L$FLK$.Pf.TT0) ,W*כ)!3&x֩ wk],:^ ^2UjGF]B.V_xt p; 'H@"$e$PƉ ?!r`bW )U}TV鷀kr!K_]Ge8X2M bhVw4Gyf&?7"[X9U6;@>GO^ezu得޹Äh̚V#Jni@/.^ qbqLS8-b,SUr]̔%",䲗9x߽wr@/BTԩ_j>9/Mmf:p*na|Ů@a|u:0WGk\Q.9(Ka3un4l#7l8O/s@5>$ߟ~CYorH2r;ny1Oo5(b;5OĿ@d m'Xwe];_$^jzQomOein75~)z`nI49:@|QIKνڒfKB=bÃvmpF'끈u$<`LQcLo/E N9IRiG\{v*2 cfN̺oǘ3ʰS>W6O5D+:ЗsT.bNpD D :߷ zC̚tYa;@}ÇLHνϹ=,M./9K3hfDf~@Ď'.$g m"8F79 u"Z4E2N0Se#0˖VSS+~ tܡOT+e&?g/İר?u^b:Ɯ^ G:]rMpHdc|8V1yrFjNqg1<YZ]128z>qGFzA"I(BD)J~ s N2Sc컘e|OQF^ӹ=z+i# ME~[MzGXkbVuǘ[][!\譺k*9׎${Wl'V)y8KQbM܌/r:DqsqoaTAngZ<˒CWk[=R{]ȑk*cX"SjK򩟿|ܰ@azϠ*U+FZok l](fS"^ ^0E%teGrsoIRZ]v}+U6uww0&/hfB:5'IqKNF9\%[qGZ#u 6?eq&C);}3WU aiU}p3-5l"[2J#zetY[R_r_Ҭ6[]djC0%+[&A1?3 WJRo;6 pg`>Tax7*F:O~*ɵ+cOa;ni#tbgq>/"Nܪ{Yw9RDꋗtURccaՒ5L\gLΔ `lD+)4 0&/s :WZ*S5D$DU13SG-y^4u!g"?cB^ {/j֒^rY6L;2~H@wlt-/w4X0&/4Mle#0ͅvF+@l}I5z_JA7BO9z~DϡR Q?*x]W0S[q0mO/,MHE*1 D5)`NI`U~3Vq!I'I5᥶5g,RgCǿ=S04L(dɆFя|0 kuѣpLݱOu}+0(_.nމYO{xZ,^//v^^ f,S>,˞%\wH"DiiFKǪr Si\siLJ;6JR p(bBذH#d[,(lv .D$N2y&W]PL]oc `:rW>;fF fUyu;@~II2~#\|:{[Tk_+`L^.?_1b$&d|^Q|N ą7Gȉk_ZlGa`Yg_;FjpiBMg/C.Z`L]3cNp<@d⪥wC6zұ}:EdC5&S0v&#CЌ9MmХ^<1F;/nQ(ѵJ͙H-!ࡿ[1˒jv{%n;0 tC ڻFx yf}?X ʏ*6$"I%Y-kzѪ4{]L<9 S2ZV(.' KbA_]Xfzq>5Gv v࡛6  'R SL@c`k1M{܌xYξ D^h\x jH˜.J9ұ 5:{]L=ӹׄ_CLm7]xvqj#xQ\.k3 #= o[S1B'UXlZU*06 B4lYZKΥH^pSɻybۉ^Ql4O8i$`ͫIh p6Q2>ځ._qT (8\s5 <.e0f~s۰3I=@d}@X&VS<0il[Rmb Spd * &Yʁ;^t@g AD&_ eHq/dx-6w; ז=ZvG:Z w Gdճz`֛W B0ZȬgayۑkE.c}<;wja_.X|h^yA3Ds/DgHk/>K1wYLfG7R*SrYtgmL(k"[TaVk?' B4Uo]ejޅE0%׬ܞل]6ַ$!5>v ؜Qh+sn]-xލ*hVu]F [$v Snðs".14m @*@)B}R ̉O  /$Li8bfʑ,ՆWœ<\dۗMZfϣw5C\/ ?m'뙴fnSWp.gŠ>RhD=\{R${&A ^~,?K{D~osk 7!MMojw+M0%W4]vM`rZ L?ѐc9.Y݂iYcHsk+8J@AIieb"*w yrՕi id\U3YŪvEPĪG 7<?>nq`$Ԛ'n'`JƸϒ. 'eqcUqmrgCR ?WùGq`uATHW[grU֯1]8(OYr96ﺇcUvuCr&>>=iZanQp!IT`:qL a۩7ɹ]ߐch1muPET~d>3(ՐB'IP0!v&.uW-71rOOڗxآ@XN9( >H5W,h):TP.`Jzlۤ Dy՘}X3~͙;ETE@]ˎe`cǼ&! 1brՕriijlfK\qp l3a(#(c>Yo|K $ i{r#YjM0%IqF09o=?L1$f>j'MWfw? ~S ߽1Ǣ<.R3 2gp>@.-Oo$?#= r:=0W=K86$ ᏮvDf=LB#go#hre#jZE=NyjL2$U"bH|C`x]0cgK]L8M^I>= >v9\ .s뼋Jؙ!ITuyAFKd酗 w:kh4cg _ΣA?<?T]o{GtH%KtR3ELIE\H*כ$,{D@~y*IG؁! C'r$2KႝNs; D!:[Lu.FrX;?[u/dm&fx61<`+r*Sܛ\ 1sMXGdPa@ Pa!If[KTZ*0%c-mB>O;'_Y3jzA7my-JhQv ˰&ǻ'2"c_OhT˗i7G#Q3 70vPH4U=XaN=[#._zȔ ی_yrD/QIEsNc-0IqNj.{]V`worMqMr>D~=" *nX z뿗!+^v$2KuLår{ .LШ~#܅*Wyޫ ԑtٗHuQ;H=.1L@b"S=4'һ="{2uja= ƎIŸ֎_:ay'p-α:OG ,ېyK^|?*:F:kggx]V%Lcc]G2 ۸@x_ea^(ƎIj}l-YO;]Rr;y [9ڐo54Z $m8챆swoCx]V0lgVc[55sKӠ=VX~9: XbN~k $Lizc%2ʫK`Nr2µ^Q' MDp$ v1)+Ѷz>s相ބG;:mB% l8-˽x'`>W<~ȴHv/ʗe0ֵ&MurE|:4+5"RA?$ Aj29]_+~5\K=۝/>e mic3;": r&Px]V`(d.c]jZc),yaa[~ #p LJ jaNW~[= Z9yx+-ܞh'oWˇ>3{ašnwy ĀîcuY|e# 3DᔝvARn$I 5'O"n(j]@`ٓ,/˚9;njeųbWxY3{yɀK׈,5ԭw:N\}%2ԒKaN8sΛWY sQ^#K%  I_ߏb$i3`m@C~J|-o;T^ekOO<Ϫz,Mp!9AZqtft@tPeY=Fȼ>fRT,`N-o\ f밗h}FD9QDvAʃ$YӠ{8g]P+ϑzg]uCyk.ױԸ('NG+k~I_Xj[Dޏ;{jw^\,?v Ü<:\g-υ{ ]&- ~ I?N߆q YzHHzgnVkx5 7q(dc'Ysru[xCG-=E @`Y=F"\3X_$f=ϙH-x͆+lr3"ߦD.h$%ɃW)gKfчi0ꖀ3C#kXZ˖,"ű']Fqj<:A󲤨(ulK]zHs^`]_,-90tݣa[۷)ތ~?1peHƮ(kH2aT] \DY ebjyRQٖjUSƒ0G sPiY\o2ttӿmn@DQsJ,u-"ȯ# Q+:fLA9yx YqG0*qI>vr{L[܁SF.+SMٙj,X&TU4j zEߚ)oQ'=)dyIځp*90Jɻfɥ`Tz:ץ7@Ynw$`sVm汞5ȉ}Y(v65&ֶ6DHԮ*K'Cq:fEn;kx tY3u)6FnnQrf,^2ujzLrZx06{꼵N]QTv-Gc?D @ 'Bdr|.t# ߴhڳ`[xɛ9븟(\;*iZsO1#=;gAy),?Z؝ 7C)%oܙfлF\\‘@@񙨰2x;Q]ӧ5(ﭞ^>TF@nw$f9yxӄxP­%c֬P 9=@lT. BGU0e=c ȸˎHR^>NOyjN5] z޾5m$´IԚ#`jD@ xbUR; >On7|YO8 NWA.k$,YW}}Q-/MYGC4 JAs:l` )ywRJ`m1LScҒTD':9{ 45"'eǏkADߧ&!tÜbw5ɁYOV_[ ݂cx3opptjψN,Yrʂd绪H֎a/"Gط&Ta It[)^ؔ]<}b.G- bD~oGD0oQ!GQ#GB$U_0a*90Ku+},gcchͪ&t8y%wx$-̀%6yK$Hz϶ȾX1%g#~Gn3WF@d}'RO嬣+ġj'I!"vX- :Dҡ-"G6ky> HA]yԴ#XR|,w߸I[eԩ۽"⩑.b4솒 4W;$Q!Zۆ歎'EBEެSRO]Y7l 6r0o?91|A}h~b9)=x|J؎:e3cH @Xu\ǃ<1v,YBNsڡ}mvrAΙ?7PC4H6o?!BU^<$jidPb%Ӏ˪*pS)SxZw_!"z(&َy"9Q Ψ#U"+L JO5(M&uff g !śg_L8Af| ^Ry+Jl$6w֏M٩7yCnʹ|J,$~Qh=7S $&ʉ]bLQ*Sv-Z-o\WA(wQiCch9F DfS|xtn[\{FKAesYjߌT`< VEfzHGdZ9*lsd^{fo}3o}c}6}wM< hHq"IpH)(j'>`geu?-|Iֆ v3*;TSkx"(UwhiHk?=v!v:7$e#R5rS+ADdK 6ģtt#0jl-Pe2YW^Y WɄ;>H(gm.$,fno *CrŨa4o߻{th9A;WpܓOǛ76jUgJ oQRɦſu PDcϾF==-"Ge72YuܘŇGFpTR:g3T?*`<toUt|Y b]6HT})8UsNx["ϷM;1+ +6g =e?if9YH?_>3!R(+yA8;IhRO'6-ر(&be}/yo^R7b׌#@iu2Ū~`C^<_5'm̼#`ahjB@$~x2۴,)zz=Fc_ jA1+C?g]ig2E׿\ sTӦY?|}8}rYB4"mùEQ#g J/[D"Gf!UIRm>őr?erمJ 5Mͽpqt6\|#96ȷ<{;IF(y(L,_7~5=4^3V1#j8P~g u ̇H4'*꾗9ADQZL}m*G9߭W24{Dg֦Qhí 6g Nc{Nx AOy~47'mLIk6칂A ̮ !fj{X\d=U1OƓ\>G%ve}BђmtwY҇C? !ajm-PVOa^}ͰQ03k,g#{wdGm<=(Vs>IV?P3k!)k8n!Dtw sCBpޟӟœBoBX_Kpյ V O E DW EvÆGFrŒBbO3Ԛ^&`<|_Kߛpی=`d򴈐5}쟂[sGy?+Ad+C§ډ !M{sUz,-\V,x0Tq>=IGv^.MŜ|@ElzUwj_mp!]a,;#0=ua]w0̬UlIu̬~gDq|g Y<0֡( %OIrߥ݀3 3dj* %FoTB#룞tMy?}41/C!N31=hfk ;wQTuB =&im]ױ_k?o׭]zj%%JUfg[G+;[Tm&RkmVɳ\.߿|UӦLڽ(Me>ٕL:lCyxM0I>\ 5t\[M5v&(ݷ *=))Jr;ECH:Om#;C_p!h/.F$s;F>O2 .ģ=D$;OnujQ~xtXjsaϰc7`"^@D@}1t.( <&HPם>ɪW 6i8OyZpsE- J?P/v#oLJeˋר"r(<-?z"Dj B:>q.N%Y&`<v3=]?<| RN1>P: ',|D+ J1Z>+`<ՒۚΗfAL\- Zc =z׈DۋN:eI> !&ZT /r%TҞ؁cnm4jibp&0MpoK MV"vw:{;uBSHp1OK.@"1C7$VG fYO85 uncɔ!vb8?C1➏x1]?A$7 S_jGf'm(ϾXrN4wjq@)Lrc%(t]^1_gZ##^!yr"rc7$?ץCOHRmY6|V_3끳tfwvH miۘ7tvA_/:g덽$2tA{/6bή ݖmj;mqݯj:Q%rRϐ.@"^.4TF$Y~wiG b˔$x;W33I`<[|,۔E׷wB\W)._uCWf]%״~첒|L*#'P b "3V4 앪&#~LorEXX5hAs_IpL5c2$ԙZ2^AYza.C@ _Ū~<ǂ$+OR[Uwn!X%v>fmʺkK(˥낢Յtm_$OuU7CMT52;5e\4 rNb|~kc7=v4Ddn)3;=քz$ݘP1AQ8LXgݺ8Ҳ7_+eڸr;^P\gv^k4Ș7> .9I0'&[Ka%df־G*y54n;.Vs93mh~z>PMGk [H{Ǜ`A65nq]}|C꽙hr %Cx06(ʻKxo+:K,5"pbDm鮽xnFKdZ!X%8mmƖv~䓭D .'8gˈW|4?ưA#VkO+}1Aoy@A|Ci[k,5l,n'>sV&xAh'9QqD!}1w4n;ymDgqRVɳb*+!_hdCp3$6%ZD1L. &^&5-aC|CGM'VC[0Og5g^Ap@z5>հ&=nk Ͷ,uueV Y. u=OxZ\$ϖ{Qr`ӫ8I<_0Ujd43yv)X&ݣPz+|*F݁[Ƽp ۄjDj7ߘo4F=o]VHVɫ+%)Fw{3 |8p[ogݒ)cKk| ޔi,uݠYz0 n,H%r(8uN "$*t#(ˏ>Ht֓UlUn3KJ~]$gztxEzcR9(ܘb Qh%ゞHoᆬ,φ X%*}o h(4)$Q{È]ܬ[6%pϪ GsԊ]ҷ;"E]}c\:V .c\SX#-=8zZ8"vV9~ax#s v~@O_ {X&*bjvQȳ'NeP%+zPQQ/Cp-SU$Y~K1g%ynibUiz”hz|!X&mF⭮,^O5s'7B ߻%SzqudS sjw.j$ r):Ĉ2&Q)Od`ɘ ~JF鱮DO{Bz!}^We-@X9?$!rk/En4-Dg=X&b{qn&#N\xC3{^_!<碦.堣^/R}!e m}ϔ\PEaMHYPh@u"Gxa;-̇Մ*i?@ziܥ,0&m& ܵ5TvmK9VE.S@3zbNClT CZlDf<_*yU}Rr12ZvKg_)Nɔ^<}B=#N[*H.:받X#.9\"$#0 @paUtD McVd`PΈ\琓7PM̵oCDED⊽FOҸ--YjSV`Zq46\ }b,FZukܳfXya!8= s2ABQP i }%t!0_pbEXx>rv$[/qA>x,IyS6)4-U!r4)qJF\,f!am>Np r 3ߒ&OSO7QBυIr@d2L fmo2@HatY:w+dV&SC3\`Tvp|9&t]oc:CNIg.P()ey 'ilm[TW=]2y}/\ *ܸi#*g/,Xu}-ɐK\tvlמ9ua]{ML;q}Ow鉤̚&Ӊ9q4g F%a$p;ݛZ`nk;DN}ߺbzE^GYG|bx4VjRk|{.95 tz2rcG:%Sl1k 5Ir YLwȱī[@E` mra}Ю*x Mx: x$rX ˜yہ;r_{ @?6 9htpgLA(Y9pʙm\rE yɳZ .@zbiL^U2]vlQEOZnܧMfۨ &JYC59u.p/P*F|=d:D]}Q(|M撶K9>Gs % L̩)Ȥl? 3˄;wÖEt$Im(+Nxd~]`_Ƕ{ɑBHUtJ^g8?4zA G,l?J7#7Z|pB堓^燌yOuDhc=!숏#L g8ؿnL HniD2o "q`v\qTO\'sq}QxibR֗AStv)kj]ᢘZsZ_'ʳ]pz!Li:@Z _!`Ƈi-R .hXs )\<@gl)z|QaX&Cَ : 03_ =nv %'}fW}HQyVcN4Ny"F%$ E%#%@yU1:Jow 7QZ)J,ɱ;N LsL[00}!6a2"=?g@O,A;U}uㄎ.1vȔ{ks@RRxeNNЗ9($/F~X_FS_ȖdͿﺰj\Fc-cNϱHgՙN8/ ֶ>-4cw`;{$5mlyDiO$oG02>"}w`J6 `.S Gi(I,#B`sFRus^U8@2yݯY-9W^!/*c#__4/JJ)UU.zp^5޿i0aC+ Lkg=30s -=?nh8YqxPv3mn ?NU[\SiсjNlH]4h6Ö v޽: ; Rxa,WKwM@"B]v ވjoRj'&;T5~t2cso#A8!DUYIQ(C3ڝJ`N.pO[⩭;-oka0$?l0W=99F'v5=6NGh#!RZ M ɳ#^oGyR5 vVe_euz#wPjM^x~bٲsvWzirCl7J|R19zMpkPh>ʎf-BwV[JSW`t#NbZB:=aV߆a}ɽ S>_4ՑRmGvmFwcDDbYw@}Ev^it`iJꦗX&}Bfa~;CK{ ws~B~K:!Z-9C=a!83`n7=s(?vI<׻”5tچaNM"N\(#EͮK{ͤ3k)ilP vUVg-X 2yI*7Etp1P|z[JN`ܦF"+nz: %sƆڂ}3].yR!C5Ύ9-ozmV!T{'K.Cs.sW\CtIk0@-/ g}b7<;  oU"%m~c$-q蹮.0B36#黯-Oi0 ɏ{mò򺾊;=!e" JO)WR0dC]A`dlw[2]TOX'ǃ+*y4A\~}zL|NAf('̨Ò;X9HmqwҔRAۿ;O]2xiAy>O%8~h$!*]sxg$w}Y]Cw}ÿ<SEn;dS)J#`| uDVZ"j?2yj ¼%9Zv>.;LL`sU z9VC 8s C(L?1fϫ4 ۺ)r"-=>lh-8WvNRc֢xEhq`ڿ!GZ> <urΊ?5aۻ.ĴsUYh X'l}R9]0Jy6U8Y{ʴ'8"O}K;dQpXJTF4_]N9aSd:10ˊH.&˲`"^j8;1`kiA3"$gu1N +1oXFmY%>ulhg[")8mzo!*ʳ#* u,Swvuj'9atNe;)e&Eñ^VElQ[8jwI|r-v:7 u@&tPS 2.QL7"OV`5HυGG!~9[}ml;SXp0h~0Feٔ YV&B鿇uw ?ʘ#̟;⥽ņc}Z#+.TZ"[' բNq_By9)ۧՊM&@ovq](ipq=`x&vvvH`^ww#OvcdE` p!BXB/jxi?4Kvrn&}Uu(D$7ͿjZ؏<)9:ԲKa:FQ< ߻GbJn=fMD @@aC`@'rvȷ,=O5;-{?^E:ONcqǸPr2(_xzYeE<%WٝIo|Wn3(5(O r9=)&߬KRmxwejWK-6,m}8^া˷4L{$X 5׺Cn-[:< c#cUgtl5mev0 X,yS4G*n͟vqQJh|I k5wS<=x9KjqtvY[t(9駬89"w 15fuK[61OF0ѻnޣ*ry*#q-ڱX냤tr^3%{S606,U1m{^ݻ4^c}Q43*DgODH^,S6wC KgSOq lATiZ&m2y* kvG8|\ LN|nchw!*/Ө9CQ]ӦfPp~ht)d.Uk%0g?\B+3;U #$0^QB7Y|m3{WwD1PIyeɦۂƜ{nѫ,"i: KO ӂ;O ج:-98i`<~ۆQ5;15FjAb5i;0qK\̖Jƾ#Y `oL;:ǀi37`ڲUY!AcTGd0;z}"+tolmMYhՒK,_{ dY;ɞVg9  !E ncn}w+<i}R V3*' 2j) XoBrpU7UX&vۈחeZ~J hs92f9Fn8D{$cCjpBK&rf*lY/V'ȡW:tU.L3`wOnUdNE&j|0b 232^ z04Mc jo,/NKnm dfI0Ph}d-'7tɁYjMo)`Zqۆڼbu-NAΉyl:(]7!t,cJ8Rg;]. 3;5mxy (s` }`@,/-^1Fa2o=LX*;֒^@Ĥv=s|}?2 e+FXݞR%g=>(,WccOpPNr@|)^}r[@jDn7溌"~k,-Pݔ1foRk"ClpY"}/uzJ4T i v`fx GH[pige(}nh, дj-2 dXg4 xwm66 -lX*)98+J,W{_mDmA>>-PGjCMFFz24ets5mf:QϝQ#!]5*2c(V?eV60h#.'_{RѐKZ_y3NBkR٧SG&_rs Q[S!a/hmCgk5:|NM;|޿+c4L.rx+u$gw#oDe1[FUkÂY1/AѷP'gĻf_)0^OmT%f/❾Hr[1ueF ӵ[ܝێ*}6c+ٲQw(֞A;_Ѳ S=0 4l^8ϲ(xQg$nh:8+y"s5"h8Gy@Qzogo_7fm"t}eN }8P`{e] 0-bcJrp֓ /(ujG͘Q8BPM:hP ʚ)zI7>˃hW`3j*e3A$BNɫeWf_SLQiK̥g"z)ĕQ679шtm]f` G<2UIv]ab¶Nϱ0pX+vjbSK>?.v>FS}hh ui+fg'Kf;Y6|}S_1_X4PDy5ڞ} YClRFݻw*J>d!hZ~Y2yt_vl*lrpOD/+bl cIJ57Iڧ ">j6Yf^= wpe}@n?HՃE.a[E7O5/Բ斈uSllBP] xbL60~{yB ,IrQ?dComd_UI漣F*`6ʶy_gٝ)H|uղOt+^eޜOTܶ6-985g:yÞl6d%6647aW/Pj7cA#[?~%9~Ш^E&yW:ZI|``At~h uϵԪx$r{] )=[ƺ(lyW!%} Kފ +Vtww2M"sG*`)xϤZab h?CT?L[@ TWE2yuܖ:9D~&d, s9cں&ܑNCk8e5e^IsCk%*Nf4]k{I--;=߆`QLmS >Jlvr_1m%M=/ ?̃N="bXOoAV5]%]; ? ýJ{B|T1junhw>+_o$딭k$f5=F2yȩ*%HYocIጲZ"h9Yŭ^5j# as7]ڃq+87ULטT$RC}=vPzS VSOv&;M/mV`>WB gfT賧 K6 74 #] Q~f_5 mNAD._>5ȃ0hF-m : 'ejdK-ܦ1|, +_EГ^jBȿm(q'2D{5U:A1I{ܩ>LI6>V٤77*/at^_s9866 Fv*DLS3g şVRR(1]0@?vn8==(9;%"Ǩ+pzR|aejs4nSjtCCSm  0a-~ ZKh-?ێiG8T/($E9iNN3^0X}ҧ [8,bMʎSqAy¶-lB{<][zDH?=<8flkiq@hIg#G^oB|K@.Ik]PHCL^-^ۖWz|RuzBބ@"3tD֟Ƹ[%!|Xo S]đ_D7߫du#V<Ŧ1z$d&Ky7OigGKKmecKjMJ2n=YQ #?\;" Of`hiɫ-ҹY{agr9.x4 CIE? "6{뮞O^K˰ikh-BH[ xZ9B7`ӤCF8lni|STIo3ލ6 u*xXMDo|5r(Xp㟇jؔvI0z&%NFN? <'me ̑OsBJ7Kߺy ²Ր/ilGɫ;C#K+]#s<_F!P܆EU'y Y4̾Mf&8Q0N[ fS+4 @W[Vݭ^3g ނTEc_]^c8(z,mb]4҆!Nsk2&?.IQ1w/5#D$D@\W*Bpf76I:}z,gSmM66FmFҳ6cBJ*f#pYADϴ/J1@esy(Ua= t:qhxVģة~PCK6ɗD[䟶ZPbVt/%MD 4`D3s^4nn:7V#b ,7‘%t-: ÆXu_hzZ63To b c,!Ṽs;ը{5I_;I:asa%[,.W Gy+chp6|ň :3| Zs&|QVVm*qN8#+s>Z0ǿJcm׿EcHD[zw/gqMSn*wNLHW #Xn ,?CQ$ /'P21fn\m]7W/fɋ9'{]^.3㔅[tUgq?;9\[muO QH?]psBg$VGoMbUl d#(ZެpJĔCwqFDb˭?l'dՙ/`SdM/IzW$z6VH 1j;#3gM(p8]6w@kyDMF5hk gAo%53dϱH^<)ǀwn6e;(2uwW ,^tWWTX3_"U[ȒUvJ-bӫnJcٌv3+J3ETMǐ#S;9zO;gE|P0+(o(wd@w(+ix|5!3yDH@F*m-[㛬 H9)]lp,S8KsqA'㤟u$/j~cg7_hS6Xp5+.Lm/84!~̄!m ; ~9e3 l[Luv#,CX!5}Hԫ8V(]_N![Pk5zS@mHinCIrxdt3r _C'$2hMAY#䍂'd`Gsj[xsNP~I묆DqX PUdPf~],N=4Xu~OEǏ\S}*V?}2 Fn "ș1ȔpvxA cAx΄lM,o&wy)'|=d*\_{#XH/PDH_'iS0Sߩ m~wIPBPTm`P 0 /{Qw1JևXxA'/ڵZ},}E2 JLDsj !x,x2vJ\QZLuz54-y?#1=3d|qzD{壕6~˷wEA)B61Э,%H{Uݒ0ki~IbOӣ+@&v~)dꐤdXH:էҢhbPօء%*LAW`#kj<3MJ5}،F{Wf!iTznW mRRN.ʃ@ɋC!`EDK̈6'u_M*9 }^!M\+Wh~pϦmG)ojh_PUE O$]qG/fvI S{i D.#>DRw 7I? $:}G9+hmsG1#QU_HW תCP5gW$MinCI%śNB3>&ܕ/oOce@wrqDkę3JeU^4ꚋ`Y9ʊ^!:pN7_)lXgKoJ46 xflԝ,c"*C4RƼH:f$RŇ*A#TB)@$n &ģ "RGH~-,:S`_#o3VuD>sqq_DS-ry$u_5ĂEڷ `}o9!ArٕS(Hn$ Ix? $2ms֧iչvӄ-c4EަȘHKk3}eqCi\-R+ې-^O:3SlyM}쉄,tX}!;Q%a4:(ex9Ie¤f)c`ÆS ݭ$%Ά9MdUL= RAFB_ޔZH$Ѧ1@2K0.'$N#h|N1ؒDZqʰt2ʛ绻"95Qi;#$dS?I:O( rz$^͆C~KI_t":oB`}u;se=^)?[; 'I qOw`̑WC~p 86Qݛ:ÓX389g[\;wֹY}=d3]o@DV84ꯜ "X7'W͒S4Eڻ?[IB\Ka! wv$](nC/[cwcp(d~O޶4H>dr/d[D1b~ʿ3m`!_9cOt>ŢğDܣSH J4?+=U`EB= Nu BǏե]~oxn8#+\]v*"+D$@_$t?+Iz97'iɥNFys.FB:ƙZ?QNm*0㑰ֱ;؈ʡ8cȿU9,o;"ƺZU @X/'e|1˾GZDڎD<Xn',TCGzPM""M|rh1"D&Ɗ qe4U|G0:n W$)j?9_[ ]iy.7Hm:}s'^{ϖlSMSSIlCK=fZ?P֫./wKtm$un6-?!P>fad}-83ӱF?˺`]i13d)l ~l! &Mݛk' Rn斻FwoӲEɃHm?:7UTp],$:1sog^ IKnm8M^oՐB Ɵ̜Sr@1{\^oӴxǿoV$x6;}Vt}[?#nZ Woz ҼbSiwk{?h1IH+,] B? ˔T̅&AaoǙY]vv={#[P? =ևUaX8 s3">ٶYhICȁwjGP@=4]^"Pr;ߢ2g1lμ0)7$Na l);*[7])zfL6̖ۤ^ȂGi̾ZuxҶ :!Zyɘ9@JQZRQ:kk?c;E hXȷIi<k'/<- "+OVtӖg 4w9 S* v$Dy"IeAD˾ `GgGyk;kF<" f&Fb&]'mt"&n  6W, %ooE.0vv}ÖDDBX׏I|\y[= YHgp“U#6wxOwqI`-`WUm=m $u2޷-K~W;&MGdYBm'Np6ψ n)AT`K}#[u esǚ R2>Uue7 }{Fat/=W[-]M$P~qs=ik$}@;Lْoy ;Z4E޺}%U]ƋI\&dgxf$u.&T%54iw:t !H:|)6eMr1>d_}Dt[U{y[Ǧ6#[=YUս=<@緂Ŀ9tkZê^9}EB$+3MQǟ|'8҇8i *nՒ 7 9I%S( s#.2E旧w 'e9iԫ)+DD~ I/uHnXD:wNjI4E#=љ`Ht.:+7ĶNɆ(ps6e<WQ]) |CcN˲%~娊 {Ʀ S'H!!Pfu@L7tNc֥ zuڹs^~AQGt}}hW7~Pefmާn̈́eH]IDaM-8BVd4UИ-^a rC{ @8AD~ڈn/dE;*HzL֖{s7EC|FbR:&4yk`w4ETUU8.h w^wk|٧n9ڗ_{?.=ސ(_+<Pt "󫁝yГa_a$Ṣ"¬"hq#Q'I=>/6IsK%rGs/)* YK 1q^6U4E޺F V?J&3]RLyV~'kq;,h9p[i1A{v;tkI6YyO|j;?;&u n8aF<Я.8Rg*%;$ܰ+uMQ-Khk= J ~FD>@GGTy[붠G^:wd?8yf1rYhTdȊi3"558F @Ȗ$i;=XHuL}E)t(V|$9WE[lkiqx^-y vHw:{51w'<䳶<Яu Vt-;3ۆ`-/UQ@G"4AH{Of̔_;^gꮱvc{9BE郾0%ʮ)OYӿ=x=%f}5$?y͙7Ϋ |⏠ g֜k)I~us c1p]f"-g>FADB>@tf 616%LJ-CԢho>mȿ %,:KѾ1eEo'3gڋ<ȊþLsUa!1tpJjD~Vz 5wfOo $D4ll1*YEcV ݁^sU4Q̠.dEvP)o~DP+,r\GcRCR"-$]dX$'4 glD'=c$}Kh{#)""A"6L YzB Pķ),)y'~rF bw$j>g>VGM$xɕ6 }5MkryT@|he]^MxY/{d&.o&9_>NEg!al@}vSD+@]DXar\Z85Q8"" APnDhf@l@DHg8) YٯgʮǤ´:@yߍ6-l\H?̕R~swRӓ}I"+1#ql) [>:C2xTg?>h}<I7ԥoVFsBEONnj }Hyi?UvD,R0v)sKA@]5"CC !c$2Wo7b-r.DSorQW%20|묨 -c7*e~ XОscРy`?go5nމitȪF;+BlәǾK[,ͽޮ坞 {Ʀ L[d&.jdV/t "34sw0/E^~F$.-Z;T\dI1C2ut$#_V"~ ;sWEk3F2LD$A^@]P?EC<_9M%L*T#$;~UhCpSǕ{>W\C2 6XR>n:juifdgHhu{1= sqnNe3 "{>@Dq;jĂcbwtE>(fyO.J3;1 qŘn; p:LĹ2,<౽w;,m( wōr. !{d/6 oݖ2 اLh_}vIcm Vnj<\ n:4Ꮤ D~>ڞEi?9GD]Q4DsDRLK6Q!sHaQZƬ)yppb FB$i:ƕBzkfBRwU OBj.l+pwۻӮA_xEhe.8Nw/r"ӽW\\KQS='9mXg#1xR>X!=4tsgꝊ ""G?f@F&/,X$~ڡLPt=,"^jZ}APajC7 hap?*2zemRK6a2N#紅e*VՙYuҤlhNE΋8KD-zia$i`f"2G--KϬR$@cF"-yGE  C;Vp ޣ"L]GUZLŐ5wł$k.Ufdc\dD5,]1M |!2Ȇ`Ƽ3&y. 5(\Pmq8j7"[3U,iZud*!AbøB>@_ΘC.S6ta,勆yxW(.<4w3EDtGD?B $4UwxxY."rw-Idsm$S[)!LrVSS^E]QZ[(gK+~Z*Fmz-B֏CSr4yǦȚdCIO|T]/WSeuwH`/UsHuo;=gĈ.Gfԩ_@8lČ eC1rE% ~Z1b")XOMkYv!ƹjWO=Xf>":C+@& a~vC#&zUUiEƷ};%Ewp(?TSMw_Uefce,y~?ptZ:[ o6Ivm!nT_߲|;D8mkTǦ`8wvc=t.y v#0G4J"P3w}eA[a5D7z)&Mv]kХK)G2ZDtOD+?ݧBa$mh~" 1qW@Col ˴AU.TǂcV&!>V/lɐ;l_%)sVW 칓 ^ҘҁV5'|GmpN:9~3(-W8r5n']EI#=2W-+k: *ATy{!Kut2'jQU25٥^|\}L5fM?ZqHv{V*@@~~ERpcG#Ex6Lh(_֙X6,߽zlU0,c).yaR~{f$oB9QB?tS~CE;kL熪~*F1\\KX[Uƕ:NhDҧ G` V{"PSTsjꂖP2r 9e94qOOߪ&M5*I^R6C)F͙t{KZDљURP@@GDzH޺1/XqFCNfTu, j| ]Q9mP|P F)6#$6`7FVgia#-H1QOtd9hU.ҔȌ /*/-ΥeաH-:~ %# Лgml_;^M:dK=MR7O XZѢMDtzՋ;& @61 |>I\@|U5^{WKqyiWGX@kzHᙏWM{Qro`cLEm{α+IVQ44UNN\dy!He!;Ĵ|R` O?sn/vd!H ĨJ2XhrR!AryjRNj ?CK}ST] anА Ӌ*@6 '9ADzZ$zP% G;ah/PqC\ƃ#R7p芊|X%;ԨT6~#SA2#ٗE$}qEHRKnd2un UCn/V9ĆhR EMl z~-,ATX$wrʜ<8jlPRwJut $j|{\GHVy{Q1mp@7@Y su$g1>+źcR^ڂLTMjeEHHr9b6h؋n.'I6 {)yGKVuMU!Ph Vr33Ms&FaG` Fm/8C2Tj&ELQ#4?ZD/,T@7QY9ORj⬫DֿyRLjwK\ߣ7^qHF76љw  kD2V0H^G>({hʳXu:*7wImD| D#С OcϨ&CxZ)@|"]q?z$U~oun  Ij $i^$,s8mֺ;?_G~yi* =@9pV%#65*bJ>,Lm;,FgSHE.Ru:';7{3\oxC7æ^fb?sAtNl%fn,f GEv<\E \hoO'^T= X>8JѩR{8MO؀Ț~HRzZc&P\$@& Uz6|? GO[+.ӪRolE9 1r6/P9)J 6.~q:+6 wVXuB͞XRn[ lRu:wc&!]qmn>Ȓ^U/N=A;ň+GAw8qvDD$Yp֎L{s>Y'A~D#if$4S|ɓ*eo(i˥|V}45zRDOw5@ Z37*H$M@l;W)G.KXS*f0,jܤ3:}zTnBKN+Nl #7wVfdzio#p:7uWwk]]ÕqG[bE#y__}di9i r0p $&ATqc@O:ֶ20 /SV%񼏧Կ\fh>t,,"x蒌6vRd(H$|ʬy yU}ě+m)mI,4.^EҴ0_U)|",}N%hs}md~7ivr Acd( 8a@N]$ WRڬ#@ŮvDDCĹL3Wsc?zhR!AmJ  YQgY'W[RAn񪶤F.W2@&o.Ϛ32 .IΕ vV->X оS结Z՗qsӯ掕Z[OU"R^&GWSZa_ro5Im ~n$74y U+MdV( S/}v̪2\e.dMBĠ* ${YByFZ ý<E'!`x =-t!ډ^"MeӢ<%/.Y7HtsoS[7rn{}hHoC\$& c'z]_4$什]yX@ niOɁ=,ΨI& Lef;o 7kAic1ꆹ=H?mYO"p<^hN+eF)K^ %MW2(rNr " UsTEWs@ѝUDw*0tYu5{jjF[*\DMguG|D7@wչiW;ZtH$ ;}O27V)^ĕ[*8G56UJkO.Ѫ%-+ ǐ RN+A;;$#oY?44}^E\r΂&'饟W|Tß[cd k$a-H)qAF{DixG\Ԃ^6EUZy+aQt.vu$)K=nxݯv`re&Xm*ɧvÅ]+Oh 7j+ ,ΣW#{= J3M%DyF@I?IQ=^S @5Woj{`P Y*3,dcYvdžӌ$b4 [:(nܙi^m*֐RN e}m{yIf~b{03IEͳE|:Z5 XްE[ňuGk6 UYPD$V0]!$6M,R7uADŗilQUZ3b%S?F}DԜ*/Gjs DZ> <?6K Lmo{ j|Ll vQ YRBNea]a`τHҚ?*41WQ IE5 ~V4I˿}pnѺU1Үr 12aV=:P|ٞ=2d6P @^ ;l"iήJ/L+9Hs)L!z6{ލݽ0$IŸBQHR(t沴^m2Sws|n|V7B8ːr9핑vJwl;]dwth1=x$}z\=j/n[~[S f(ى8Ub+#Vh+#"5Lbfs_-AW݅H<כM$3Q`G&#>dYBrtoU)<>nr"b!/᫶\" =yJo/O/S%^dI;s<>T ؅$,3?nĭ/q>V=Nbj:Ye<2@ [ ;Ԅz. @s=[îwQ^ דS9͊HUS"p筠ziػL9{VH h &b7LFDRKEx1IxcVIх:yCoB/:ܦRN4Kr7C`=_ T<Ə$"U `tXxrE d8+a6z@bG@zp+ ǧ; ʴ!%#[D{N/TX. piU`$s+Vj=E q0̲*/椵H'Uz!#=ISr˥Rz$Zo؝HznjZ:;ROih'udžӥIvm@=LEI$U,ưcv"ps?b1hbpf`7jHSϑP^I]]= %l!B%˞@`7>&S"U2td{B2eI058@~DXCZ1{Z^d0BMs2)=lڛid_(FU ƞ] $.ܦ q! aDdC=@M+BwB# dJHw.7ňG iZ0z!`(L(ـT59MAIeө\3~#^:~bACfjpHsO^ D_Pc\5IͿk"7K 2mH#ڃ(#4]=ZD{m -~qL2Y z$3o0HI [Y8Z/$l,iw^~ '.#~:ΒEOe|<^z@CDKzl`AduE\ Vm>!N^m&H?0KҎHˏt!s@;j HQWZ/x=)lq"I/<"37By>;<2ii%VK)+ h)1)=PO%[BErlBMK)Az0c2Kݰaϝ\dԌwk<. iՅG4x3=o˜c8-N=^1JYhƈVQ̿aX窨mct.EcW ,ѠXJ[[Vݔ߻F\`'m"IN{oWka'Ίvfel2_J|hzqq|vd{ս*#$yhћbJ;%9!A7z%IiX[Sh3xyßd  Ӟ-eDX5ކ r`O) z8VŨ@24ׂk6޳j шan ID9f!!JAa`5/5jTI gqM i"ITtirn܋@k33Gy#$| jR$kU(J[$ v=6}=ҧDܙ<"gu@RR.0]d7^_Oc{ܛק2Hf}VAk | s9YhI"{d9Nw1O2$btL\TsŏJEHmJ"]Ljw[]e@Cc \L5T^ؖ,4.|J/2,1G)ZfVa^۔fSJ,\+wJ&c5wÆ?$+(ioF)x+vQ4_sÇ#CW@S ۡ^p>ٵxnCD4hcRØQNIzE4V*HAOj5+SZ)@ef"cHk$y[0qȄ&O݋{*0Fj)Q>)0|떮?l$=b m>iSZ ~wuv a$c L5dsw{g,xÜEI>0krӭ=NN,{n"bG&>V/:]2$rxO$GE#D;:.jR $nTSr94!)$W*8_|h_Z?iM!nTʫ7R!@[󆡰dE Y Mr2H.TBD?wѓûb_tK9IOl ;vU)7ҪxpsyP_Fv w q=A#t2i:]EmG._κ 2$(tuVj4>0>dIx_jO"փȌѿE](8ar!; GWz= tcDJj3 gWQݣź7uZiз*C$U}:- Iy"] ڂ؍SO@D'H"꒴,?%kJn;,*2|KWmWt$սNv7} Y#5\5;.e1I(1z~t$/1y6${O}>L#xm)we`=h hy`8!}=;*#- m6*Rhppm0^ļO?@yѸuBĿc 5~ںT|ϥ;q-sכo\,4AeSx+{@E? L$39] EtzI{M^?R|vF^>e5haf¥wE "ʂ[$y7{&"yҟhbG7]n E챶?r*ҢN t|rOF/D[^*^%mv$#U?d< IM&<*}W,HkyӜF6wدi~xS5&ebeR0XjzKS>RKb6I;X(C%=&$*1ڽlQ2ݓYm$DD ay2 U :K?WKJd2Tvu#CI4$=tT|HE]&%ML\'R$CTYv$E*E+3/IqBsˊ=UA;4V<D#N柹tPc֖S0,()FAsTi؍UmczVwUIEx^4V5IJ ]2Q}whL)'IQάzvENh/=?/%,%uY=lU[Β*fPTGbdedž1=l52 $sG.0>Pվ=^t_:+F ; exv6"1`bf  UAMrQ< z'yܥ*rcV'kP*ܜ`HrXJջј>ߦ+gKL\*;?E7O'HR,3&¾";g3gHGĞչq mό\d|*FMsT~vRqڪa^5i,9{_4Kh"3̦U*ҥ7y$<Ʌ6~>?8MD$I 0zs>3w CxucO2Q%|֋`tЭyHvE4e{|0C@Q2I@?I(\2"Q)FlQ2eѠm$DDC`|q@ 2$V)d]LMqOUc23..?PO>z957"N\N5ݕ$ Шa)Kd/M)ޘ۷X,2>ԫ־.wVcvo*?fEg{oΓAҗVuoW ~; [ f8:FIc <̈́'vrJ^.[FAgˀ7[8+K_ j2IűJxĻԭfq e4{UsCqZg^\A|I0F?4- $Ɔ,$Z9Z K I~PD0Xz%~g'kl$yR7=vOckf.r;P`щg rBą;H=^ء d(7gX4ho =ڶ\wc{> >ߓ7f*YwJAo1nK>ޞv&GAnyG˃:R2l:.adn8#7WIĮ2XZz3Rry^1 Ϫ!7?! ǿ*pQgԐ-NUx`2{S^bfRs/L #Ju;DwU=Jrg%#~6ݘt|EQES7IQ-sϋvD6 OtF` wɔp!̮2^D%?n ɺ'J3%QSƓZ/s:rOtH[lԉAbء d(c UFO:ptɘBhB+&Z]X>I]R,mMs{:nrL["N,2 x3Eƛ(ɷ2Y~<9k$A&瘝3dKɚxz&I)ǔQWu 3$`CBcJ<5(OWW"3*~z(zPL_"ɩyP|^~Vh4'J+yxɋ\-Q5Mc{Z\yK+G )yR~z_FdS<<%JY&}V̮2ސ5iztt?n;50b}NƓz[jO+;1d3o75Ψ0BK8 "b7du>V6P2쏩E.f˟IFD4 ccjVuڛӠz?"L]{q@Lil` _EZ|Rroa6j "J5)Ta ҫC%$k*KW[Edg(O͍*,b71ǡF!DWEfTx{ֱ(лP4̷0OZ a|77+*AT}Oaɶejt;9Mu9 `D`H]w ӲZ%Cqki5EWf$wwu75z:y9 ω9Wn ^L`aD7[1*pbDMW5TA̬y?<ǃ%GnI9^& T&#RWVlm䏞^+_ &Yq"]j+}H̆E3_4"YrW.xCj$z!|zk~ i]ԏAxo;<+7te,^Bk.Nh"8v(DDѻ2z(r iW`E׽ {UQ[5Ļn dۛӖgN~5[XAr)}b]_}8|_A=?P^xrc$Y}O z~%VkpmcwBp/s$3磠fG>iH хic,J5[L02ۈ5Fʢ{~uEחK (NM<U#qGxVwp.9۴@"::7U,CM+l'S<BE+سY>xZ"Tx%I) ;rEK1$_:f<_롨cŘ"D!걶(DD{7}^ō^bx "}x{ k)*KʥkCpI` } HSJqF[I|;Idud\}E)_7(87!Iϐi.^݈RIe`{>ֶ?.˔PUbIٿV"Ģm&DR9 [uv-WuO{\3Hc}pNCfMzպNy0ߒt*麰61>"zٝh(@r8RpNM 7x]DkEiVy4L x)iť^\ϱ68M/~EriYzRN"Uee!{:%J&IlJkNb*L7Gss j0u5ZP\8 3ގG"3 nb֭ |PY D!:A2-i㉗ȥmֿv ͒R)V ^QIND 3N!6֮"]B.+itVqTw=j$f(Xos\.?ÅqyΡ,."h0ߒ4z6/I&5Rd ]"_f~K;UhfX+:C(N7I|[um IFޒ*$8b w0-.fBXP]; fS`pTǁt ! 2 mi `Y/'^CD)ǭ0xE7wkYʚ4hRr w RطX E9Qj+}$m@e`~*c͙2E isjb"em/^`{s$DxoNxp8F}gv *rEl>lzoKHOGJc'.u3zz1, OHg-@,H!}!nuh@Cl'}RϣQ`:q_sjҸoQӊE#`]|~Gs?p1V|qo((D";d!r_•IƛQp[ iѧGiLF]J vWË*R"IJQ72D]l |CնC_, wJ,1"ycMeյe.]8zgmLϺJ oj{;Phq"m61{ S\!HHo?R%qw[-d40[Hxd_mS FpejII#v UIdIcS@0u1Lomoa 9ۜb_?~eFqS] ?ˈ ?r"'iEI Xdٖbb?@Di[J}7Qn"Iee_w+{R*D:2 9_Q#v{uůfEHvEŠ~ľ* r#A3wWpן:u o[1זEcQ" Tj q9`m("M#eȯ6'iWZ[gviN4|>^\o|:u$#\{U[d_ 1sBa( ɶDRjƺWXdN |.FpY'?0wf$43^w_0]X !Kѡ`u¬rW-Ӫa##X!9dBc3nFx9)2o 4fHɧ9a&9GHD35{#zo#S@˥2DdTuC#}<揍+=XQ1j4xh cr7"P3;8hH(A(WK'^"G r77 Kz6OJ>jH~P򟕚1H"}(cl̎AY bz||t Ă mB:F |\Eɳ]bYF cu`NIP$@I{*UӌLjQ;Шau#6]J$\}-I(#H"#cD*QEo^B{].Grf6J|<""$^ݚ%wcGQ&0ےt2LDf"Mo/vpF(|$9U7AcN%ӿU:vKJp $͢4"&1D҈l^wF 6+. k/IdoEK+~ !?;;"ЁV=LbLCzH('uwu"AѝPdV4&#$չc:u*\]E2 PgPȠ#bT;;X84>uYV`97^g%:Z'CdDDH!ROo HfjSW~l"ML"wvlr+J eBBgyxSK`%"-1id&xf$L zu;>FR S`c'Yp&$m9nHڱaH /lL0FBD\}-lkiD=ecjv-F aX OZԈ.5x)k4+""5;cD9XU?%SQگKLTGc_ǓIkZUd@ L*F~^/8۾UΒ %`1X2 ~Ԃ$g8KDh,]M[ G.iK sCRzףm[ibEʎM>nՈ_ >FRwRxȪ;9O=g-dI&EsRM9&,p,f^` gXt? L`S=ڵ0e(@9k;{;͎ t`;mN+2k!`J6o4)h^1h*=:f(A.k;z#81Wko>ESUM}\bLKO'kzɞ41 v1.- ~Uh)}5~9i/_g9;5N_A]/>o0K.9%ĺq Oٮmn6gh潵BUCʹp.1<ڐA5Mj YVӹm9&+7A Db k|u׬䚐4}SH Hb<@Mh/ JAA={Qj,EnuB ZZOmC-x=WKfQ`J99in4@/#Y&qq{u$jJ<S]lw;mNPR*64|{Ӳ %1aNla5>/uU!-*dh Ӈ#ZԮ(K!"UWP c~OGzEN(c@P ޟ _?Ôr4U/q21BxEu/jyHDK7TT$SvBрY%ML)|9#}U$8뮬,>Cog!4SAH H~ ʩZ!"0RzDϖbl4~{4ү:9<4AECIs%?xT Ɣ j'hD9dmBO!&jlK9kJvPa)DMowUJ_EZ C_*zFq4%'=PI0!<TΫ"9мΟ4'& P}a”kl ~5;(UyL&{ِ\1ƕaLczVuH={TK6 AA,r*sETf %vɥF6a^m:hZ`,zqF&y{cG%q_.^;y$TJ 7^j-^NCqӈ/ҽr({S`[PWRW6!BZZf1G8{M7[h׏P2&I]!9KQ胔d?M"v+yl⩇`xEJ?:ވ O*7NTbW[>cƌ(lc yc4֕(Pm ѓ׊ goˤ7pTZ]5<)`+\/DZ>U# MzINy}嫖EDmڳ19P'JB|ZA'J[ރxmYy{)zUrM56Sa {+DzE.Wy+c$1 H& zT2/O t1^eغǔc\ 97&B֊ wTpb֚4P}hD '5+z* %^Dopmx6 lqm㠭DZmK%qUr˛[pLj}WEi[\T9B*{x3V=by>~;sui14@ m]-i`+'/],D$61j#Ndc)u ?QVIۍ%%H}EIRc :UqI@eK/arXbS\ɇyZTGA@n ^Dfyee¡Ncb :ҳM.'#rV"2 Ib++'FEH 6RKhl{ =_8vK rN=inejDғW!Y~zzj纉MxB|Am:2w$uIBQ[ŢM\C<2HͱDכ : (1H@^DUhNvLfSQަP~ )̏AL _erq0^Mok&Js7J˝(I<_feCRWjޭQt+#}iP4fd~WE"".Iȕ'%5`x#^:8h_VR@]zFpdvZ0sTC_BS3gu@P41c @ 'Puwd?۹K{ BYokzVӭz+`+9_"lb:EܤFT}c L@Mp%Ib\!!)!hA{\JE$`W&&>/#W$ʌIO$#?d^R%SQh3 SDD^ޡ<דpr',e@NU: Jc]YCBFJ@"mELEY> p%}@ '/B xuMZ}큱^NLjnPyB͘'2t 5*Az yha{]("sj5H*WI: iSat1!B2MYcI@[ŏ̏1xDQt3qp/Q ;D6ۍ^דH : :*.wEt6a pyB&; OJnMQHDd`_;ID_9j?=7͹: %}A~4Tcǎva1eۻ;I2K\ #/^r$q$6qZ5B E.\XH[c|@#Sc º+qI~GM`OYϲH>/2u$sկm7;I"% ^1Ucǎ{c"29<5?SCKU]<%% sĖoJk} r ZD&^LOz۞9[,車]*Z_ #ǓүeL\(BZ֙xJ/@ܬ !zػeR5Y lE S y T^=̕[ J`{"^{[׋%TL1D^CZ,~\9gDDt~}T Y ua<ܥ֡e㍷\dKY9^eO@W,?ardE}L;=LvH_m=F.*,i|(TD`o`#c,"W;MoW&81Aބ7ޜT1+LrnK:*/$ B=̽W8 qGcL/<[CA~890JɗƸI:|€3DW-b彠~EeDF7İ+V0o5$.K!igg4lͨ%agm"d-y5Hbxz8`ءDDzLi[UUJ-PVp i?;,mJbE҄"- lo^~̴#٦}rTzZ#;&(*_/%\BoU"ψMT} GBW<>v E jċ m̽g@F/*./b~eEų^joKڜ©.06=vN'_D@Z͎b9Ɍ1ƬGPj~JB fZm-9n @DW7r"5) zHa׵ƘnIHy_fbp6t'R&#}+)>JRR=./ bTt:V$)Ao<]>1^l"hC*Zݓϫ5;T{{kqd2ak0 Om7(H۰c ֒-+;i'TUŏ'@c툊Iiq<9`+Sn!@@ԬkXu\jf걋U-o/"^>XΊ#Փ 4N˻I^ 5nl{X,x*ƪwWnU6rbIQǜ"50Om&/`pID%$@Jy=wZmՋ!$s&yN3^^}<\Ar}}a#oۘ~P1ة MbձMR+|Bgfw #Qࣽ[cW {Deqt#qw k&Ob?3*DXf8r'} +=M `G MrVv)&1$( Ea'`cxxFVKfV~X9p9tk 1yy0wÒ//!'d# 8ڹV} (tE̜nc;.*6@>m=%WTH{Tu{F_E90uD81%CjB~`NZ@Gb,mb$JHJ?_QlЁE|kx6GK^~  scƺ)U^AbVCvWN|dS r0^͂ĉ1ƶT-2eo!1Z{+ݘ=蛽~t_^n=,D C~$'$/k"L%X}\P0R%M嘼,=)I% ;9bc|e%3k<02s;lVZU e`7AE;5znc{U/W#ʡGDHBJ66/S&Kq%\T]h_W`oӳN]Q{-ƭewK{C)EGʐU'u;BAK|oQ|kY* 'ugxTT=MU~y\` {r *ȫp1s!}rQy(IN>ԣ|`+N Klu%@O 4F%*`T ΖPx}|=:nJ4=3EH:Eޑɻ̬ڨJz 3s@qk%[z,3AXW NI^D8h8:*\WdACo&BXזo#ba2 I6SI$_JhOnxZ/2wuA֛p 'UIbH({S-NSRw`;@g5P;OYLHvQOkƦ_W%Abl=,"S* C P)ݥCPbJU8\~So0N^/q>(SđS XDeM$Y/|YVKaՍɋ̙Prc?u@6*mgl{ n*aR/@$=O;څ7f| ZZ$/HpX$H!Zo1CT^]~# :{97(*J%%9h@$.ٲ A#׵p0 ]q1T;!?EFT+I5ンbescM\w 3[§Za[cQR6DD1BPb%qb\<֋_]@{ #ЏrOzFpP@)%&b-@mN8cUoEP, !Wjk)Ӌx9 PID]064 ;/lR7z F_=~00w\ 62[Z'Y꺔H̬6MV>G(|W NX/At"6#"i]+V(Ak<y*AqNRX‰7!xM,ӿXݻK~&$Aͳ8._B*j) _Uy J *'N;Y-f>:b#S,[q0ν΂D_"#*{ i[LKv@Jv\f|;s=n ph;6D-}hx'XHÇyok"MDQrP3/zb lLaUqUa<&3~$eP= ɓҁl꒴כ-a˻)/24JS?WAd"*USBg3^0H_D)16,@COkiAM!`&]^ʶl\fp ̴GyDT_貪 w k ڤLjr?)q9FS_U xA!]4=BCoUS H!Iti2$yT8uUvxlʋ PJ*6uI8׳f~~+% :ct/3KS-2PbhY/!/$"#}xk07J8HWDDBqMXxN1u`1>o"fҦZ׻2 tL;1űQUZfɫd*e$)jpЪLs6ؑ6Nb(RJwOE4r"z6MJRz/c"p0NtC\bu"T5d C١7.pS\"F 1 Ƚ@m$Fv""K-H2ŸoH?,')N=azrP|*Pw-p/rEB,՘9cDS{Y4aV:Jkl R5gp]Mv 0-Uk,erTq7%ӣRM,iƪg-JE{/_Nx=Mnniυy"G*L!s?I.BGS-Rj]'c C \pg鱥Q`"cvϭē ozp6 ßМҀNi W, / s_6xf +(mטd`1%-kX@XeHؖ2Fx=Lqv|D](/HR $rݠJծ+6 X͍\Gy!B(%" ז^BQrP':0z>7"#}̕cvTyY&ϴ5[q[y﨑 P}&TdUŸv0}qUbm 1fڐǐ/k}- ~\ES0#bdq8=:Pʺ:cD` E@<=6zuRbd[~$hzgR|cH=T_j~P4K$k:bo눈rkl06r! VR~DϢd2sȯe)\)곘H&?P%jE_3`7~wKA) roUM)2 gDe0a_gc"#* o/2c7J)QtgX47dp}(B@]$~\+Wn(AeG%+%nqڕճ t3-5%jq!'AjP83N51E${P ,eix2aO7PJC|+|"{ԡLƻ!1FOECqaC@Exs]֯qN5sZ&*R}xO7+0QR,{!K"?55SuW˕r;dq<3-4JI C%$uC\sD?`Q5G(/>ڹͦ;o<( RM>s?*֟dKpR*|פi*sF/į(q{ rVX4xY:$*0WSJ E.d<3`yw؆څm突 1wp ;fpZj:= ۉD1;Kn,8l.ًG'(4 ^W~*?$<͎yUd4@+9b2hjɡTBޔZ]7Nas%J)zHY}^.CqAc Ota?m-lF~*g 97nCI{"rgq#m?ϖe(xyDD;챱6y!qSSXTQRo =yR4fn%qD{~:䫎Iկ5G5&ϣTk䫂$9. Uv^cʀhůg[J]>y,,TcM ZsJT}2b$ma0DbRzC@Dj4x>ېK=H!"iHޣ"Q, F (SI_$Ge2x].͔N&nBXN68VpNIQH)EOAڵq$qpDvazN Z)&=\9R1M !\bLK{gOZ8pbIG@}u$~0D=+x'qߐ(#WR8F ^ޭyE'H+]~:*qtGeNXD$ɶ UyI/X:ۓdIBJ)ѧ[BBGf~9-z`ʾƘ@DM V׈pIfNHivoHw";E^{e(Xv=^D`odytWK&B1 jSQh) ZZED0)GzL՘5SE@964ҵ:u~=D*j{j&$ԣъ뺅螳ɐ޿E;/~iҀ@bX}Lŀ@gJ3iQɔHg{rԀQ0$!@c)m1N!l ^kS+n6|0@C~)RZh1gZpMdɖV4R  gul:u}(_H/Ǭzn'@ ]jb>-I B&ӴscVw N%mʧ7+iM\| *%Q3Kr7#EP7Jc*lI)ZHoFC| B!= 5H@fqNHBwD0$0SrQ%0 dv#]mn Q<e(#9R6Mzsab+kt J)޷wڀn*ŀtJ)5$;0ϳV[u8ةj@QsAt1LM*Ӈڃ W+Mrr}BR= Jd`ڮ(Ai<h2OqicճA1•DWk4 7~Հhsn=zHq~}`hT Ȭ%%~PCT!t[bB-zaLDC4N>%I(XEQUg޵<\Bu]w(V}C1(6hN3%<(.u 9!jj@a:@DC2y]Я4e+[ 3򂆶(0r#c# _S45$xIߑ{$(qMAN2xzzj-$ӾZ훀*ĬC:L.Je@۾?BToO`HeVbWr5 5#Q78Z$KScNҎanT:p1'!(,>qѶcKS(I?L;:{YJ!:.*RH2@xyzDj(o~IHz0w{5 /JZѓm# ؁fOt9 < ~I]brSZ@a3FaT⠃M[¹¸t_H{a(Hh_!|%$vWEx+aNgJ,Nĭq r'F"?BtE'حY3%Z,>u(R8sҔZ׭&ipL𦺄E^_<%pH Çn;A=M&OGV}At{ &4jP5dGT{O^P^3,4& zMHUqI&=OeDd[ҹg6'M)uYsK2FjEKO'L +E66\F0 tCS5;؃Yy-~|3p:-kNUP| Su:9($Wp`c׳C"N/DhRȡkKxOzY6 yVT _<.f[]$46ʮ|aIJiWE`QQQ=Z݇4H4ȯǜG-{2 Y"s{rRб䉐Y 0dCˆ2g2tܥ(Jڽ2EOZԑ./ Y>XUҧ:ĝ~ #rԃE ќrNwRb{Kk|&;q4/݄|8b`g EoXHeao!F M-aáC0\OhkhwzIZ@йQ]EIۀ$[mYR)2 DtBHζ#+酈rũ c{%dX:^X=grdp*,0<+*pts(gB1>jgۓngRJ{G @3 {A+@dej]P'k=AW M2fa`BZ_ &dv%y98YҧBd}_(AVI#$jJ:@]o&V{9 <=ѕdҿx:} }*:pGDFt-R49vyUwEc$Rڗ??9 @)5/@z|%G 1c,R̀>D_\,}%X@tmN( :7:O#H^l&Y<%S`\zCzA2 ݿ< ʒkAl&t<<- i]V$;y^_NWt#8ؘK /&>S`11{&4٨'Ba &%>5HЊ½!+ӐD BG{V\G/5#F=vTzb &m%>kZV5b$ l9wa"%H*';rB/KAY90N={Rs4 u4Fgxɐ︿f\iB$P t jyhq7Nܷ'I:%"5ep Y* b_f6t?r]1[g?RCP}PO OiSb"h5O'HwKg <[Y8<$1~qSdoTB bՓW!u],ڣ֔H+x>Yv)j<\mEW 'Kh](ٿ]}kqgC$#f̼t%sFm >/*.;3O ~eP1ۍxp0I^u+(} t@d~ޟ{(>O3ӆ,+2 DtR\6Ot9)1;zTm@DyY̎/1)l/>=pԍĭ>NSӍᏮڶLiN>!@hE&jR"8͈Q,-Q 3$qy28\<~㕒ZN{h>a t>@C8{J~1Fq&2M!#fRҎ&B46V4-fowl7|GTusDϚD  3ōI}mpИSco#Dǫױ7,yˆimqrdYN"5K!MDRJ#+mS _$j[t5V |:7 ]U fOOc`y1`0@ lEB_3F LhRJ\=|'GO[P=HܳxA KbVD pO,^T? %af$BMIicxA&"{܍ $䣲t`kCwgc4?I"hSp$_(W (²:JbUӒ D=PGhr`2KY@@YV ^ z]{ ~6Y[FVt%X2w)Faz"*a~51E- " ^w2&Χ4Iސ:wU4!I" Gls!.BRemVФG6_%YZ!RЪ&X,u;X,U]_fX 'aITyDKG qR v=jεeңxA&#!WzT CFUؙϖ `b.\AsV?Y0$ITC\n f*.^QR-uAJR;dB):AF k' 5]J{Hhؒz?(AZ?^JRt)KJ<(i/[A,zoHUႶi!ra. t[.EƱ] 6rPx}OW-yJ;PUq脌njEDК}'ڒ/a.`hKk!69'=%CM+v,Z҇R? : M!Ȧy=^HAJ;=ƟhjeʽfpxRS"I:]$β$.M/ қW/(Rп+Mմez3I6lSR,ttJ8ewfѿZ4!Izk\o 2`ޏDCo]*TzU)A1MY.@1*id_?w0C /jhouC"]+A;cL @K=kgDR2O}rs<p48cջ%0SqkR⭵$Lфgſ7L>ZsQ0桌&|q⶛>3IC$>-ԒCׁ\17Hmot}i ӻ)OZǺ·ЊAD_+#p@y%֞sdUOjtP/ƕi,jrP$Ii/52jur):4z谡|1Ӵ,3~jɛ$D!) 一tijm]aJA<, r2渏 ڴ\~дgyrgN)}a 3<ZZ,Aߛ-_X$t@W$D'q{ȑơ.,jA@u+eBcxAiGkBڎ4}7;0{Ĉwy@p੗E$ǽk$/S ' 2UVqB,@ DccOZmSTQ(]< 'M9-14q \JE%LxlICW rN3rjbd$& <υcv=}aJ>"|OU`Ux6ꂑBF ;d}  r4KAk*T/-$zek:DDZH7MBcs!њH˦&uλ.64 P#dLkO-V,!Mhw)Y~_"H Rj->9ˋNqCDskv\!_o'n?j^H$] %wBLSk!߫hC4zooLŸH⽀𒊕H ~d؜y;@~7^NX-5٢"Yj C"t̊`Tюa˘z>0ڠj 8 Z(nQfIǕ$˩3uTJP40~Ə ]<MjDSWS+uE`@H A"a#mؓ2>$#H%~лbe0&gW`\`.}#5()n|Y`E_DϴJW]ay,t9 qUrV8F͇ q!?1{ Z\`k/] D<|C2: `D!3ZhÁBJ,(Վq PzYrN{du pXأ7E>ڙI5몛[Šݹ|G0&7Q\>Ag(!1JL%(@k˜JjMmr *bs$[ X9qjEY jWHמ(*Q Sl#D'Q/Rgw0EgQ?wAm>zbn\^vշFˏӹgf]݅0&hK%0O<4'`M M4Ql$wX-2 =sˢ7v`e<_@D!ͭ%<%g>x\rOϻ 4NLnD0RO=OuŸ[@-%QFB][3ONtZW6 c<5{S$ÏBt?iMJLկj A/hdB[du⪣rmq .>0 O-ĎSc}~&J;|FCei6NS* s+Μ%O<\|b#U:A pv'|6I!-ꂈMt[xp*tS'MS~X@ɟȡe׶92+Q,rK+J<(iJ v=y5RGh4i$ܧ KD?艈~$Y6KOI&az@ L~DG>!#Mw +EFQ̺qi 9hBk[T"H^IL4u^xI,Fr6"H? FHc0 Q@4W.]tc9I?Y(2!bW 2>z뤃 Id t Yۧ}9=._]'կ}v] B>;/ 6uLVzP&=60cuҦwq/XE&g승ёK2>(:AFQ1bpVld2VK88~FQ_Dn1A' E8]&qc~`W0Ӝz`Ž2#[$v8-\|1V=X0P±-ꃩi0V4?0+lQx3K⺋rQCe`2KO"r y9%7}Zb\ ymj׻ 4LDn>QGrv:""pz3Bt%d,&J cS"r{;P-m-zÜ$&G=,)f`f}'Woս0}_ήlJ ̘ZG8mccWgf͇^L KWo dyy$Q'Qxb?KX~g0 X8k̅ Ie@!d4&-dl̹XZIRLzf }aN"pq3A`c,2 o{ŠxJ%bS*s5Ix`[dB4$ DZHúyTר DĿcEnbn:q;[l`ںEA 3Bt J*9U7M=YFr봚{:""0@Kr0o4twGaJʰ[x4?QѴcnBpUt3Go$ICu%PcQ~%fV-EE֥>d}[&YL R71=^r՛ h*')0z+zÊQҀ0I[Chs$!v}3L÷g]QGO%[.[p[K"hRdy @QxS(4Wr 1>Jbv5ΕqH *Dc dR}៓f$ d]XG蠙}o$i<$x'Ѵc&foeׄ!HQgXL :Ae?ݟ U`PHD.Bh"DŽWTמ%̐+rW[kGRL9ča5":Ͱw'^ݺE*w,p3L񟾍$x9j:"OM+|ߘI2dE)1sujc(!W[_I*^sd ؆=^'*͇+N>L%[8^8 ʍzl/t&RXy(Q %'&Y^yKİzHPO#wV\xI+ɡamF|mz@&v|}Xg1@- [WЄjA1់m }f ­vxjN]>scxɿ Ã$R[]un]-:J zIW(f8p_IgIIIR, TmH˖?#llcϒ컥V ,lgBӪՄDNEK\;tI.->-SS0e.G0Z|T(ME[TG􉟪bOíB)V(%Jc&rўFsZ{7&IN GSa&UvE&ƀjهGn1Թ6wASGjEEFmi3Ę[逿P½pN|ƹ${ǴytpQ6:@9h,"/(ȍBˌ"% dpx=^nt o$>I"VA&/=hʾ4 "$49䯀g V6^eC, #^`%RcrExaOGnˢ*KHmMձrz$yguFA~Ĩq 1:D?fsɫkk / HU!<οdd^na.&ePm L?:aS,]FքlPSWCdh[c)̍ȃ2Wn b\F:*P'%:ZG0y\OAU8`G (e8IXylW̹|V$n+CTU5V/8ׇ y>}j\W@]&uS}2h=P#pIHi"Ӛ|{VJR"@rrΑehoF ^rrlJ&S'#IZ(Ud#6DA~ƨiq]}sh8UHT5rDȃ?NoWtC2fɧ]e2Bӭ /nLI6p{"q8wC.->BE?RVbMZl񧘮bl0E,h4R[Su*iy@}$Vђ@~èc'=<ɗ4.F,$" 4zz}=ICuՃĐ|a.vJk;UEmQ:e"~ 9t VP|H(lD8s{)ɏ\u{?dNW%AI^ulFQch" լ3I=V NsLozWȃN7P'!+*kBhUpOPpP8* &,ٛ\a<#ܪv|)db%;sE]/w]D>lJTYVIR-(5&ai-v Q b3 R|cы}۞$ż-@id3lqws5%xSb%@^R3{*1$m,Ha1Ƹ!@$lƀ"!/EHnHxvA u'v*4@!$nf \5>'5r:.Lښ'bvTkbCHK1j'/'ds-wa!;5Vrzg@@ֳ߇oCj٠ӧL4U B1Ƹ@\BmSŁG9E< ]'=UcETp ߌoZZSQ#KSx[U(?ZdT'MUKo$Tİβ!D'FM٪ҁ t(+,noqx@,94}`kqIZ(DiU!E9%hJVw{8c![d +S?}9\ے3HD-(^Z`sE%iGT4?yh$R[S4xB\T/Kβ[&Y#$U(M`&S)v$)8+oWk%[90@ 2MU޲WlߵίQ}7Mf}p􄮲̲0HS0вgUE3bQ͡x Ε$ֺĖ,Pi7*ҒFjk"ޛd%yP'fqlyb%GŐJWig_8gņ [*4Jlq24E~Ub[dڥp0y܎zp+& Y!+jjˣbTh K3 11Ct BՉFGͱ4Se 2-V$ ,1yDK`~uI>ݧcV`U@}scd @)-uP" =ߋ `@+̨3Ε{+5^%OPYPFjgߓ\_"Ng'BHE(MQ0MIV_3̦RhXxzɹQ(y.*AoŶ>3Ͻڋ_AI. 'S-2WK{ 'ёzġ{EdTDQ5B?l7{Şx.1 jOrݤzI:gKXd9$AX֡4і,"ru$ɋ=>Xk&7fk5# \ȋ%L8gFCR+7v}<'?o(٫ N͐m"ɽJ=¥D 4  ;DR < sn&WZG%PPJ4R4=sPjgIgBDR$[yϧ!"n%"{3`/0]q.c Q9.m<9:ΡbH,nb׼.W^}&Rg.p 4u+)qns|.rː5wxrQ$zDn9W>>5^xxQ-Y{rh$4ke1 ΖVCq$&&*'>xvܼT`:W݀a5åkYd>}ƒcH^Dr XKRaL߳kqW%>ɍǷ^NESU-Scnw ?\+Aٗ W}Za9b"%G~,t=gʼn UFEd&щH7սdWYaو^wo|'NlzJAiуu$z=v=]\Nm_/ԫdq|""b_&9Dq,Xk*UEm mg{VK&uSݞ07 eK [- Bɶ`z!"p)^M$ 0^$"bqVbU$У+8(ďl-mMJ=*c]Ehx[TG-DN*fo$if""Д+caOEX!EM{(EJTorlYwQ$[9q:Ehˇ#_$y"IF[K?DTG_{b2z snh.X!.~сL O?!AӺiݾ;Ru!Cbbh$=[^*9WL/q;)1%/JLGe8]LH4YRrAU8*w%#bRDP\xS%IFgo 1q96ʏZw޸/ӈ2B%ou!#[>98( bTD msuMEVIn'g%tpJCŷTJ>(>oDoȟM}\!Z{//m}H2R)|/H< D[O2 |.,"6'ۂ@F\;AmI+Iv$I/RL)LDgMxζHƊ3X)%wD1g>BL7WJA0k1A z4[vm\5lQkӊ}nrd'!b #׵=QsccDD`/+ԃ3?w/έQ x4_6Ė G5 H9q>Hn@:o2 b8:c!&q?bH(,yԵM76y-/Gf: 8t) p_rH~h'k %#˽x2+:|rR'|Q)5q=Qpu=;*Z7TM<ζح2D݊ d~;I0b ﯽdz%"*s|f%5b! ج,.ɣv uW~YiI)Ϸ,O%Bf D#Ç\>:Ww(ZBq8bɷ_q]ǢSUPYrq%ҝ< qWzZ2Ҧ&Y!Iv5xNo,A*řKa{ #|aڟVa_QV+)AT~cQt>~IgoaTHpl4}%EKpVp9^Y/k-lQ VTE_ez◌|ȅbjDQ!RAu^:'"0F18.S7tK4\AjRv? M.m,Or}֨ԧ!16]*vhY}{ >ӓH}QHjF1Q*\[ٯ C;s][EW%rTD`? + ^xv=Y(9ݱܹhPiO#wk~TC+9%H2L 0w[J:gAx=)# ."e1"\[Z?BM#{ ~P8ίXX+٫ɥP2n 0=Ivpą/q\ߟĸƭd?Rg9+:`TV$7YvKC0 Ȉ! f$ADXNLbLMy$OaItOQ<^c.X12v N ީ| bF4E+Ys0r؝{ydȚ3Пa~T ְIHTE68hx[F<騤}*I_?RJ2Q!JQ: $_^+IXVpW}{fQ(2ɭ/tpIдrv&^򦡰!N+֦HYRhPhQ>,o1㯈v\\Ѵ?Y5&9z1!Ԡԕ$&pNK2ǫ4 mPn ج^?fq"7pŕ+%\"\Z5?x$hr^|P>!WjakY|z#cDn2'! .8W!ُNCCLQ y,OBp&y"e~t4R)큷O2Klm)N.(]L|K~9r#m<.ۆq>r͖!+tӥpOZV m SKxthR"}ƈ=9hc>8HOOmp>?Yx('A[Qx܄^bXen=ztgo,R`|oo(C4q ہJK#̐eoɥ{SP}.I,%SwȐX<˄>x+ ez+k g\|N U#*]*v'0gmE٧r *%b`I$631B^lOD!beóyy =IzY2AIm+1 04PS3Yf1{m^!^!uB]q~ig-cJ_qӊ{p} QlT>oD$j-Nhag2WD;c-{V$g j'A뒌Jϵ*wjfGq]5FI>"#[9[p!-hmF!*Q6HSk>Z*}K_4א;{&IF,@鰟- N\]>깄si_XrQ+8"*%IM4ӐTeW" )Bx³w?<8GA~vT'DfY" ~ XKVEn7xǿہI !3d!•Q6@ 2$V)aS*5Cs\]Xn]nO;pK~Dz9D:ɨ$zЗ`%qR 2½E: "T[A?X$B`y+F%Teg%Kk#@%!--#4F^:»~v23W- {b Z#ser\qXvp!x;FAΕs岶x[E#Ud4I)>? Kђx+4^zȓ|~o]\HN䊣\RQ v&ȫ=59'٨RF*պPML~sM;E._lg.W &fwQٵ\rZf"p^b0Xa- b؜ [e~xEC-妟KVRkOKhsj2emxQa /2]\Id䊢LAtF)Pńe$7LkKa+H~B(K4tNւԽޅd5?kk[9v;0gTEbшnTb&¡f&ќkTև !ٯTQvc @7bd:cx8 E@n!GP!.CJb])Q4O+`/IZ? Ni̿b ܉cXo[{v `hBx'[}Ksv}ƙʈb #$vZЬeB gLs\ ƵqȭPȶcȐ3*UnzLc.]PsA[$.9.ލnM䮳|rըq/&ψ6QX*4 2){ZJ될 l7lsOӅ(#j~7 3dA)O]`N~~tVG0@wIX{OX`Y3^.$bF)KHُw<V>Q{_Eq2P?|!ID$6C  d`=t;L"I0%q*TF$k~=c&e(6O6\;a==Jo, }:']֐9zF.GgxBԞB7 YY$S xxYR9+K ],(N+]r"MIc6$TPKl9vhB;[#~! >,9qSKL޳.D.nepy%,A|[wL|3.\g'k/v7~E$#5VgGG,ECߋ< JF>j6's%a !4w"`6Ό`}F4Oh,"L\ W11ơm @oeO+i~()Atb>h5biqBjF]jHy{?E~#Zb0 FUpSPOlPmNb'uAlB.NO`/Hw&cd¬4U\1_*5`ZtdHRq}gh~ʼn3xmFׁONDzPtBhݸ=>C0̹riWkώq'Hsq#{g@Tܫ=AzTJFYDiF\Q|pĚc[rarZ T*-8m2BS~ui:5\x+t'1 ^9Pz~R9K۾1@2B>FqN!}kRqDI6wCn0A929*o)Dp?& :&`[n{nb>cKBizVqpesP?:tyZ krl܎,sO "Aj_˄>Tf^i-*ܤv\AUJF', bT3^AVh3nL"0DN rJPT{Nցo}2"M&пqpa2ĵ%a::S\Wck.r $&ATqKc?Z27" }5Q pG6&"#@ki-AF6LacHc7M:bser O,sTdvqz|4+ fs^G8xGKHm?!'kB?VX:^a;bOR|B|O@Eنa稄Tn:I_L\A!H=\ G;`WaPձ[H:!|Ev<4cM'ae\vYuxF&r]H{zyJO%Cbш'tƹrV-`h'9 UBfr:hqa& CuY4qL&BmW 9EKz#r%.C'+{;@_ҾNyבcx%ޙ #DWisNd~0T^!B< vEa(nCT?m{AE\Ӡ*|\fG&'"S^f:~=fn#'ڰSmDyve-\_qӭ 86[Ymw?:/%BOJ n^(F}73<)5qEao1yҌTeETb>ȭA.TMbydGBR`sT`MMp(أȏ)f V32YN#z@KЯM Hse .0I R " |!Fܯ }*q=rеtJ!AiB*saK3*HP| äuTϭLnIC~ɳ9OD z" *bNShFfxLʱ[wAKZRkG8@/S/%q[vsDmJ'CaŪ+RGҹxpCr "$^18ԓlN>Jh$ ] tF8'TLf+/AȤi U=څ: ޺Lu~.Ʈ~Cfp/ 4ӄc#A҇j$"oQvʿjnFʇijf_1}N .$CZcEnF\Rt.w9R 93I!XʱK4 Hw{?:;@ʇIߒYk7m2^?JUY¸0(FCj3L<1 ~{a(צBjv%bH$7@gAx@nB)pfKHÑ{Fq֌ϒNT-lNm!X[x%ҷ"=ӿ9WytQ - 'ϧDYӟ,=&'ܘl"u+5Gz!Ӹ^\.D] Q.cx_(fFE dHL%hRit˱=P^No\*Ͼ8ep182$*[0XNĨt*3Yj3dϜai\ 68^P>U'rKJR }zWxJQ?T,PLʉa. \wy=kCܿ /qrd'R!AdH'TIqPr"pc~~gb'M@UqeEqA)p_D\x=[MLMʒz.9;MG5pzoأ V2(<þÒ97S {AJZ{(gFYT(OB;g(o5ӒK"*1@>rz2[,gUu<78$|7;PMJ_6uPrʉ +~ЉD;n,O.$%sl,Î1G:hJHs( w1!6U=mNa[|ܓSH76@rX@F)xORǁ$"kiY^5N{iTb ©!}_ po?Fh]u$e{Oնڞ2"Ϳn4X.#ǖws@ɄuBh"(7 &˻mC̰(eʿcW ݪHLj<)䫠|U9r!܇R^X2ŏY@VDf(D`se%!4.}*EyBG<~wو܍}þ:gSJ/9$CC:ۚIF]q/&TKQ BG*4hT&/%0ݓN/F . ӷa/$ c|L,%$as%¶^چ;fvb2U 4ț5׺ =Y= ˆJMl!Ea*ٚ;Q]apPjy>N,Nq=$ }!^'<ʝ e\Wg̬BH4 ^?C2[!Ѝҿ 9MtcD76PR0~)}Qt g7fG ;zF<{+!n<%7Ig2dCwL68sg1YET'`!r 4*ј=Q^)Ͼ s,\HmRT(ԣcѮikQbo!"hخ}X$;U]DDe!wrВAi^뢎xeLla7#b!3KkiW:)y|z\9!PzP(O*ϓ1G@ieH,^P!dy0T^Lq͘wz CT4@9KblԄ+Te+TD ? ݷP᭼,pLg0c !`W#ZҲ~ yԓڣ qj3ꁂؿ,wPzoQ0#(QQ" bS~w4 D)|X >pUYgE$jYDn'>Z6&BdD&%hwٟBZM>L+_p;wZėZSl-&"9^Zr[3&DijBF2FdZM%)ƏEQ8E_^#.k3:hH_Y]`T4@iBqjlUH7OqAPj g}T@KH3Ѝ"9BG,УNjْF57K~9/ =_8%Lw4G{A&|حPi%-=24iț e*R3WvmWw5]J+KH2{)PD_n[ D[ /CQmĺ? EtK rC@]z])x9 _VT B Z"`k̲CS8p8#-'}f֗K[=Ĵ9ˡ#&hh53F 'RTRQ"}Dop3P=#KD% U,p*"k9^9l g0qe02-5P C[K<ndJ[8Wg e~|+糏_*I6MVCq[X*f{ʭP3è~uzêC"j/59ePFY82ÐNz <2kxg-u=G&,#!4ϝPςW9U~‡jnsyN&.W.F$ȺbT@ Ɉ@ X|R0lO^PCqP~@|ȭ6eJ,Ct ;e pX#ݪ"#b1 b, Vd&uT?+Q }G. c c⠟ xƁD 6`|f͌eNWn3 0'OAUfsJ UϢyv:K>L%!l=j)Bq{ 0d`"_eP|iA}ٜlhrLyO&D=ߙU:0GpnYDDHGV8h{ KjyL[&cRgGiڃaL,%f C^I6a<@Pp:ߵSpЗq >!5Cp֫ӈdR.m)>d?ՐH}R:ǫq;"(k! 2sXb9q24ŏ}g풐C>ͪgc??؍Fr_+w]X'غ#~gd${{J?Kj!>HσR7p% u9,a}jjC m^xȝ|JHy~s@r |\,۞M+ʓrm2/*nkڰ|Z׸hxøQKAU/D`elӹ簉̨<"RY:n%MH$"I ݃jCbP P\҉ .)zmLdݿo'77g۞F?z(݀pQ?{kZyVd7M:}Ҙ ?+rÞp)t߲82k[VO(w"gN >.OF=ǝ~~%i߸'?>,$jCߜ&]j!qQ=!ΊH_>G)8[^y%-S35T@ݵq݀xyn6Y I{=+tHLiS*_ȾrGV1!kL:;[1՟ۦh쩗.X5}A0{Nq#8I%HiT-""t EsYVkFֶ fx5I vwkrM+ⶕ?QPV8Aip넔aiUkIkZOQqq1Kt&~^θYKw. =ߟ0^paˆ5E~D bSuU|aҭR7jC آ]Tdg}LzE VZB 7Wo [Kツod g`X[ܑ0u'0ui@u=›_z*h j0DŽ$%fɝV=Y3#+E͹_4K) U|b<*7O4dGbN1 'E`1,o)d77FĘ[}:>P&QӭN1Ӆo'`_Pz|:h fW CuH"=%I*Aߥ L;.\XDT_i2ΏmKADeNLK}/ISmT}kأ eq 9).-:y{`]Li{rR݆&>] UhQYtd~_J2zϨ8 #FĒ(`sP=(Ѩ] ,cH:O 6PcDp 2iՂ;+o Jީآ f~pbms [BD^j% XԙklH7d#mU R*_QxI%FI bHCܵ^H I2]R<8(jT%uL"w O\lU1Rni8)SD;me'A>$kҌUDy2B3f'@a2eu¥X8)p -KvBN'F|:<8T8l5i G\H yVB agdzr(lyU; YK<=6oWVF/gE;3Jъ36"eߧHKq^'|6Y6<eR/r 9bD&&y{KfF#<f_rR\^U#TU0w"Ggbb?DbNUK 0!NRJI/rNc"P 1EG֏CQA^}I,;@ފiȤ<gfڡ/GZmPT51k-"s[$  %[Et!OhP91 Fl />#:TS:6Kr?8tJ]ʃmp_(Q3y@+,(rz:$=%[,=r.hgfzQ虃".1]hj@?FeN5TH&Lzm֝ʆLh˛e఍2_ 'r;E\йeu1>^0#o;ٌڷ{t&qr|r2+0\qRb (Z/{TE0=֨5TS 7OPd*eOvIЌ_"*v(]*.ȹCfSh;O\xi9}A$ & o#]xft4@t}"oz! 6e_A3ݓQ&.N"sNN%d(BmT*1'Kfmls\ w(p\#\3e`:--RP7\ ӯȑU~vK}(k#EŎ& "G^@/|'"ߌfot+:Y\b؏*=9xӡgxIỉ5* {̸$L],¶mF ^~*P,QY!'ZNNOݱ^7h&"v`sw'agFMq{!T7rPѶ1ZNX8]#TX|ԋbWצO"1*e J)Md t"4 124o2J]kUϊKE?pNNjp=rq/D?vs2D޴ɕOho3O1xl֣Ni'.>[4Q>D_C@bCek9HD8g ġ$O\Y WK{4q!3-*i?+Ȕpș lߒWQ$ܖfs{DyH2yT@v}űE z5$4u|j5Ln5*( T9\E͉)miz [!'Cwr|/a\!E5dv] %Kb Ԇ=)f;[/"e-0jFaF!Ad>VoJD%D(| W =hEz|֒i\jF^>lš9%hfۏAw_Әw<ޫ; .U,$2#+{cp%^Ic_W`|@"-6QOgD{ aݏxBbOYǎAj XeoTLn5)‰ȞWK&C S)8Iy1_y/vȥ—8W"8uy$u,'qlP7 HQE2Fg#RK-z>75:,;őDVTkiR5A b_4Tl1+[82e3%*K}rƮ28~iiq4Moi"$wӢѾO3d@.mYݺp}ڋE,c4U|ʭƒdЏE%m7Q e+Խ%AsY&Gtce2+䤸JCd ;dj`M)~zMzTKDutD6N_=WRmC;zy2 Y#i*ֶV&}XcA"ffCCL39CFh/]3B=y-pGΙud#B{&5Ic[7`@҂PKD};OkhTFֳ"+KJx"J'sJKX:hDVސ|ʭƟJf'MمS} ^@h׵Ioҽ%+w;ص%wDQzm',Y [lMI} H ;!O)teXNf|ˣtxHmOZI-UN6`?.[1",]Ic[K{e"mivQ,ƣBn?))b{ZK$4=V//as^: ~n#FxQ[ٱ:p"0zNQ5x@i2FUb9h}6]ԉ2@9t`+sz#֒ٱbF>փV]wD4䳇YƉ nyɜ|mُ*Gx@wa = sKry!&[\L+Z$HUF KyO1]-AdK:Xm8%(.cQnտڙo>E֑[oAÞ}oqP%bDU0}ٱ2.mc-.As Oo:Ķs 8wR߆\괈sx'nHѾ:^o,ō3"@9EZ#]F3VE״ҽC[Y@p 럕B3QyQKfWӻ@[HR$B嚝hQdSymL I̎Qe$cdh.f'UUlb_$m6'-##PGcл " aneFF9NXgf|pJN}0It+&Y(7Oqb~?5p|)>t1^NDeDb*@vo>rε(7>LDDgؓ,iLIۮR%D]!'靃":gH]2"=ZE#{›*I2Qc6'c#6i# ŧ@hkfF":l2H!ť~9Ǻ'iG]܄hӤXje0`1nr;n9qp:+{ <%H kfd"uK?ElUms&$٭'_0 UEWS+2nk=gy9 N4GznpVzBnZ-Яr׫)\J3K㦭!iaq}$=* Mc&ԡBMLd(] r*qeӬoz/0=ތd] $Ԍ|`,|Q@tAT~@!On2sd=}h(+%h$1scƛ - wckmY20K[gb %.34UU gY՟`Y*Č*taUR;A[/iGtCe0s,$WehΤw:羰@FZnGzX !tID$GXnAyWb JT[ftW>U@4F՛)D#ô+=ajpDxѤ_D$}OL|q2:_li NȰAs'izBMY!',\ JAzt^@p U5F `1#y!'ϔ1NS!Rd.VJqjdz)Uh}cu=sIyó /n >hNȌ$?ʮ˵C3(ժNkv2ҩ$fzRo6 E;櫾˵17Il '$Ψ~USݍB’=]0ÿVޭ3;$_Pv}:Lx9mN"sZ XSB( mkt-BFTwbqn9_Zטi|j7S(C¨XM:s`@D]oS|ke;^ vWo"V T 畭l+D:dgS[YMWKOL³S0:}ȜQ,!4F tB٭r_M#Q"cky).5z)X_L;zW5B(*+2!,H-9y\WlmF:xW?/I'd}WLB7po4odm"CO#4aOQ]n.].ZKA^?B[(^:#}AR<1Xj"hA@:?( j2=cQր\HB^Mit06]yڠ敚ȟ:"nivG.wmސ*H4PpH˖uNʤQ XH%nVn=C\EH;БVQAA*W ]u$Ou1&٪ÇM9NlF#W{s4}Y##*dNEz2 4OY{7Wt/K_e0n] xQ˼MBbPg)8G611&o=v>-%HrH$4It e n%W8X#6HJp'al)츞HR@b6 (Jrm=ƐmܯJe0n BGD}/ :[\tH-vёNv0 yCeBsm؛$Œ1iжTLű[#gKcb#124ӘON:v*pGܠ:$9@<.ڳ C4#!q`wG?i*S=!NzYCWGL?0kx,; ES=s$_QjI/Qs8'?78ӎ,LMlނ\g9U3)s~b9}*xMԇ2@6)y.ItѮ74]! =OR`?(`?l?g9G$1 m E7Vd^زJH ʵĜD^ 43! P}mRr>6{#2+{{h7g3<=eh8]Q%^{!ɪۊ@ÕxCh&?'E` sJ is:!}M;!Wnw, Dܛ"7dĦޠ{a %ӧ<ݪSDDU><2D Vj0v{_47}2J;XHv@hh[#"58ɤA`ܖhbomHOJ pTt Crt JLv0Cr%P;_ȒID`1 l/c!}HyXD( ff5q={w"V"椑~ZҺYh`Ic3ڹfۮ32@7)pG'Tu;yr2Dtq ZE&y""*T CZ(7Gc̪cQz@L J|3=$ŠTnfqhFI`5-gt iwzr|^Po6J{+ÝAo0xۼ9q{3AwKԼs , F>7ů7BEjN3|s s͸x u?c;I߇v\nbD;a{8[wV FSԯBVAܠݏamE\7^&j7(gZ5(= p#GD~ o&=@g0#manpcY%A3C69N@x۽eђvL.ASRxyݣ߀P?=3r=K^#b٫XV=!.Azocwk KbŤ֠c-&!F^#R|8I;4?/dp0#Z>/gghd#Dq\~OͷsZ>t.Q v bki7 >dv$˚Pq0xiu=ߍd EZ=d]`Ǜ1"(m蜫g-jCfh,n;nj[q0@ι_w燵BPS$_Y*HQ l1];!.eM}t|qSAnK8 WAM~`j/nxtz+v9$${;K)I$.BD}A'$i1VӺo:, wuFlPYx]IVYJ 4"9 Slӥml$5@zƚaJ$6:!vƃO=ܝSt.LsR" l{x8L!# 8% EUX! d VЊ׵uft۷q7N9Ae +I{\s`_,bdqnBz6n# ##ǍQ#X|Kf9Yĝ8SU%.&I1~V(لFLkHpMjJ~4V=+J/nQ$iV]eQ!.- k*/ҙQSvsмR#q]cFܨ:p(ي[~?0Tfzxq8c,YlOfEj.Ի7ڇzB<A)|n ~f!}jɒ*3>q@gGψR3U[j@$"3r[ۻmZ%O,$+Qt,sYd~$U/k'R;sjWF{E nEI2x)$~žYgxLfP׹Oo U A) ~90CBh e9Vtw!>Jv3#^ja_ɈO3IN!FyvK(XɌHݨUS}d3-\I*E3fTg$?0*9s/ {?=߆S ӡd\c %ݺ`?/gLHu12wrg+D .#./ݢqo4q7i$\x&HP܇jTBӮ82> Wp聎N wj|h.WS]m/`I㙡>G F˱zZ%cz=Wo8En֧* Kaۈʵ : oF&6- Zj)R(*:ƁXT}9!]sv${yxX,3y 3΁ښEѼqFWEs"(,y:Lkޏkm|lg G]A}9vK#"%>(9[g{7]+ +P9%@ >Yz>]$i248S*H8俅DFȍJL%ȐHǝ4+N(]ߑAWK vRFIz]3/u }- /Mֳ"Zc+v,@V59~Hr﹝5]t~#a+*װsvܠ'DD'{/[5*Cf^a [d-k',8_6x "cu|fDg;hGLV=3M]CJ 4zo!p'&lKд#fEI7໧Y,( ̿OMuNv]oRΨ0,%G^NoM: =YܞSkȩ9<;0 2Sِ N..c++,P3RlB_Y[19 Of;&gI:ҟX۫E\/Bv{NBbѨLNyYcq8ab 4ïi22cg, Csd`_3RUxѺc/_ tD>62@T~CtuGX}K+WL}aNÎߗ&I@*16!lYDYG8޷hX~˫GVtxI?OT U(ȁȴ=,B(7f>anJس~&G$x5cvOyJ ؤ D$^*6B #|6ɞ>Cd(0sU^4i/$,FR =r&$zJ`N?s8cb |5Ð!D}bP txۅ?'x7I/gtgjQWs;V$n)9{&+Yf!kjD>`^Kt #^ڼADbFUB^ ҃\ q~Ez/9HW3{ZrAn`L>(Jb^nf]ŸBFF#_n2JBK$=t_Ce1$x5PSs5efsCo %B,ꂡp84P+}#^|`+ >#t)*R3d{`9RwB!{ҠûeyERzfo>r씒5&rO-d_'U%.FȰ>'˱/ulO4&-F깳v7,=V!Q9x# T{nU(czX$g|Bښ#I0D=K>]' B2Y"}w{ܲñUj}ċ"̼%[ 2h[ ]la:+jQ/y2LPXQUdCYuXM%y_U>7A^˥y') mpb=0'J'_iqq.DQ `4$X-T)!ގaLZ%|B[D{Pg y﯎Qv`g,'q"":neFJl L/}N*G`lK4F+*,nLBWrFzPlԄD% I1Wt=SaVtCrwm{W dD^2h4GTUeJBSJf="umT"}*mً< 0[ڷ~z;Qb.F@Uit)ι@ uq7VsOEUTnjVƑ <4涴jN%/ڱ  QϴZ|Zf3 UX=D{C\=7EsmBip 뭂Hzs$l3ⴻpB]o^~+Hb-DD,6wRX[c8Kґ~j4~hQQz;x^1Oe҈V\#uzJ<zQ;a"빃 HsI]-:uLTx_ΞVgJ@GĀ"z`,#}\(NQaReȒ-o -8MedG mXW(=D`]cZ8x0:_DTfra7CGNP_$gډsd )\=$jQ[qRͽgDa 0ނJ / ,7Z|rU:s:ݔE_V.XΞYQ8wW7ګ+&|\l rj;fo=A6`@> K(UU\ !K9艶tJA# mG5vh UӍF[xh67ያڕ(e@D`. E 4YnEpZ⎫|e0cp줷^L=0uZg?&+89`{Jj>Y w?kH򤭎c;^p 6w9Nճf.2n̵{xZ_g a w>gs$$ k:Bz7,ǘ01WB>&2ZPSUV;|E5il'> /̇2;2~\fHSݮ4G,~H\G(Q_֡ TUZ8EUI# {K9w7`2.4a)!jx4#,k{y ?Șh~y v0&+8 m7UkWxdq?I!x1΀Qt4v).Sf,)>!閄S8frf=h_޳dne|ȠQTUL> )'c.w)8axFuB%G!T.lkGh0Bgly=MlJyoD-Gc{zm2z>Kk ei1?R (T$VuP) Fv\" .햫!lg _tt6`o ,Y[V,+cnH'j ȁ1\GLB399![49zx1 qϵʨa+jkc6"Gi@80v=Dg}Yߺmgr9 N8r*!:paB/H6ʻAG]Z%[KWfc >m(d'DKVhUeKKґRXN"ձw5 @6&GqT.0uYDG,e0T,ڐ== _t&b.҇RG_?b|FIH+)rCg3dxP|uGQY ӡűMgzX$^co/ sp^E 3z^j1ަ Zmցbnb_1T.dHTʶ&>rCTk?Y ,-ˤ6n؉FMDOeH}Hg72AV<{ږ<DGn@HԂ8Or)'ҖhǏٚgDFNb=WYRUpl&+#GwyizE:\Qe^2D "0)ׄh9^S6 bhKL5hB#;o,I?Kx]єp$ڒ3#K9>JiC?Gz~@n0ֆh2nޠ`jS9ǝwjy/I㎍xnUiU= Oie]zI8)aU2 ٩nC O٬#}EHldM #m=j "-ѣ9H~~S E#8Eq<$mVDNkG{|GTPШEĸwXc]|h^=.T)ƣD{Lw&t@JycfD-5c9Nr)7f  5eYB&uŸWXDv""0O=꣨Pdfu{Q~"vޠN~(9!$*q#J*)hf:%Ah 3Da9R]Lsx_đ!^SS]IBus9:yzY/nzwK2N2fKDu%4Yn{6i31;09v,)3g`0C# -S>`bQ0>(4SV !EUUUkqLAp$5YIWv K0xPUJRNP>X=zD&^oWmK1 ^ӖنۼgIS~D?]}@0R4RB6aC՞E.-"5`HmSP%)#!5!0CEJ=t}m@&q)AeOɎU;`/>v%Uu@0i% 9U;F8"^di[R gC;v aZ@ҊtOBWmDf?YJ1$8%g&* ˎn?i~o? )jc^5ȑ^ƞ.ہeYB+z`L'(Տ+:7aHR2G@tT˲zkV&A.,Ik9K IȷYVzPHQ>BN2E&W^Yo/(`)E5 w"cY.K]$jZޅ'%\P:,XDe?rOiEQI\Kmřjڍƭ\Ń.=gq+6sN@ ,P+0 G8q gIo'3VV^s4w ^+b?q݁? SUUSut΄R ýZ_]qMw8x%#_GQ5#(U:c^{*1 4]5p硸 m5^ R,Yc G&7Zr6tlڕu勷!%av&  ՄĜ^# tjK)09,.}~_iU7EQ')rdj$n?(t;nB[C9k9ˍi2}? O>xrP .kS؇å̸V$\6Ubּk1 8-*.X0qҫ%v)btpam}R,p\~r@OByL=#H= #/KMVҡfQJ=v 8d44:wuqADZw#G s9!cj"e굧=qs㬧3+a`Y:"ұalwmi&Q]\&.4fB"g3Vn< C9lܲL(Ч%2C{P$HNgpcD EҒ4ː{֟ M6rF)1|hLT!;Tfu[2Pק?kNИI*ڒJ$ jVOq^ ip L5Caň^HH=b`hV2AY҃Xeio2.97jA&D2MK'4V񆢪q ńdIunbfz$.5lS%k<)f6ל&,NH> %l͋n=+"#jZ.ùЮ2GQ Љ0dױԧ\@$ 7 j.+OV T i`R n1vuGl^"e`iBZ:b hv4Nؠri9u;̒1ޟsN-_Vk@x_:֞k)n!t gk5ǵf4CZHC80JNL?YOc~=DՒ#mj>5GrHbyA2}bA.5F?4b̙=꣫C$%Jd Xy]ȑ-- ksZf;ڃzxQ<ݜ',\.ǰz#eScfۻlark(iLJR:Cb̿#"4,h6LˎW3aK+b(8MoijUׁQ=/y imZiD(Os8 !M!f? N{Ch9+knaEglU;YSB̊il֍Ba)^e1ϭFUj3tw2łzB el>4#lL ɚfO,boZ".%hݷ5baԙRVtv1}2I.kl25!"-vZ 2EP`Gr&q`=ij}39@IV4xͱz-y^]V͠3V@=v6wsg)Ї1ZpP} 5L8.D-Uy݊#1='m"4M0qq]8&b‹ Rpt3~lp̑*z$#BU !=rfW] Мj1q ,in( :o 9 T.nyea@oX=QdO}!>_$Y@)%gņ+[!~.7 ܗ/xD(Ev9`%7&y(dC"TogrW70AQ[V] o"I맡~߭EPJŪ ѝPMUWءoǸCz4ꈸ=/2P^U:֍2dZp{s_z0d;X=QN,|֓쎳@[5nQ[QRzEf|"JiCwi As0bVlcVwOD8~H ߻NXqO5cmn' 8WD-ESK9BH$ãYk3 ǥs&OYQ `*f |c:gBu 1V/p+x7uf U$W=Ph^ޙtq`LѺd#k< (4SR"iUiϡzX""=1[oWX=1@nlWy͌ =+Jgzs:L/5֐N9ʽm ˶{:Fޫ8"d.0 QjUNfodfh7W%CYEwA-y,XkX8pwE%pФc_T':1jNi8iWu+(SIpn[;opLZCEڈ?GwCDLA7 L/b#r`D9qy4Gjҳ,EK.2O=q02^]KE7sᆢ s2ʻEgA -M] sr@IF^ * {qwx'-S/tiKodINIzp${ %( E2pwDCQt9zQ~q?WtB/ IƷ]1 \B鰘cbX8qqvhɍ,E˷ ܦ#^D~nPi_nڛ PP]&+2 & V/:/F/X+@qL/*9cp #N~.4{<{t~ rP~vHDU .\J2v E{AR~-Ӂ0IFc B;kdC8aJfy}oGo @F]\ oX?Zm`8͜6!B( PR TNL#IbRPߵ kpem{K9 t| #FAy}ID5+@s$#_Bug-$ZՋSM%YӠ>f F.H"qH7- $ #^V^(fpdGR‘؟l_:47%GۗzEۃ8[ @dV+&i1R*KIuKƒB$7ٿ@bS.Ch.w)1En)L@jdDIa[fF[ȶ6@RXZձYNi`8}7{l[дg#&A 6@\rU:4ۖy.ސ#{l_wJpA{|DϝK] A(Ki0tAUFf jg.ClҶI켺-6 s*Hbv2r19мnWe|Q5kp@:kW&-̀A;Ao7 Es4,zT<;m+RYڔ'0 ŏI/hܿ^}cyl:K.=]V^R`bI7 ZZĐفk%S-hMj9VrD~LcwƩ@羍LaG7= oyj{#E~-cݭ a.,.e"Ef1bMR_V-gm-6YHb{*ɱJ`!q7#^mw>ft4{:{PTho7W">d"z`&qN)y\DFb&E2Qlq>6c}$i}?4߀IL5Vq CgJp}Y*ől ̉3@qHyh;\ sUT;9~ň7GJy.1mA۟d؄z XC__Cv##tFT!c-4ԣP='"!Y7"u-sim)$k+kD2Jiu9Z[9-\^/% aNN%@T)R  % WFTlZ ") H)/r=b#N)EW,Pآ[T!(XݾJf[ΰ5 @zno NT,s  w6O1#'607 Uek#YP` /Fr[c63:oELCPy,֘"\l㺄2O uiĜa(@-hVҋ\Tp ! 8;V-Yg--'ϧ&I- "ix#*?@=kh$hh%.qAȑu n[s-WJK`H}Kf7=1no o$I1whlfQ%S&TVA 5"wLpL_ GUe]@[$dDC:՜!ԛ~ e d&3ǜ1}Dx{6ȷ/ T=RLÛQbۺsBhF8v|HH[ f]KO;, [}fMA:drrTӸ` KfMp`̑{Dz0#4& ޏdRPF4r"i}YǿMDH.ǻ{J bBtYPmNj'cStkIR12T2B LPi$h\2uL?kp ƄVd`3՛C-DJGL]X̂6]ڶcV$/{zo!ӈ&Tԩ@u&UOhk˺|`ٵ!{uWSzeƊYZ An ?WÍӄ=H)-0Ic { wE5LQfC򢇭~,g|R*[>"eu.% e6"A\bGJխؿIS}FA9>f]5Bs3O_a&ҷ2[gDa}? +P܌ rE@(.>^֫ Ϳ"Nъgd.r*YqLB)RŞ-Z_ChyyD/J>I maCY\Թ\n3 EB<R^թ$)Olޓ6s'\":iq9t1 #}$bgxXL8NUcjID9 \x.A\'$!TZ\(H5AQ͸'bR߃[Il?G? ٠/<'1u1խe6pNK?ڔ>">>`Oy?aل64'S<~Ь67ng=a }Y9׽;zM` %#_D'[p)(VjRx4nϗR9+й!4Χ)il rwIp2{w|9}!beotP&jQw ״H6"JwAJ0׌z?P,nSpߪ}( 0z H]zM[|Qɇ^ S/q0-GUpd%I2I0\T¦+}?P*=#pf/ W4`)ޖ0'N0O16ȌBD/zerW{+AŔrRXg uS[sMI}׹dCuݍ1MfrO3نSӡ]&jy2ӽ_۶m zТ8n%(T^SEUw/{tӌv _/ u]P{umwna/*Es[aEM!5!3SP]&mT\ɂ*y$ͯ&o3Aҳlt>&$3XqدDn巣lԖ_ոQY7`lyk9~F̴֑!x)^J]eu bpi"*X!54:6_K*/LIᣌDneޢת<:0,coELUۆZηE Z+$ɚ{K7:4&Mu'.<>ȵ5eZ"w#$h&e'KU٦+`r\tZŘfҬ/\WQT. 7&IIZh5i=\tԱ`t7 oY)/:q/DnQNhFz C$BW,RĜBAZ(GQ,Obj[\ݧ'gk2|Ӹ 6op*Mm])z­ВۼB43=/A1/H@#JTjHD3 C*#LB"lpB$ɺJ3ni?+D#$}lC)E7p8%Z"wt̘t },RnLA`i"M/)O m+z ~y? dQAtpz朗v+#; udG"H74Kݛ6X7؋MV`r0f\$d8}eT]P[)^`XP0e >Xs_of:mY[0,gl!0 lSw^nf'eP 5@Uz@]rR)Wg^;  p;u=J EL*z]I,V}YLS 'Rm)M0Ȁi5Ovf' q?Yč^`sr5  齹rP2.g 5X)StmIGk,r"A|}`d&cA:ɑȭ:\x[8-vR<ϵcSnGǐW ʖ k9)\D!o A.(%-L1@XX%O{VX^hPmw_f{SA\Ys )nN(g笃vq!Rha(I/.3 }bD>עci'Qm_YFH$bZ;잮T;I܃,z SPt4sȧQ='\,uXi$)NVrr Fdž3)Q6NJ).0iի[Y4RD]֡?;7C <'9yֺ{$ެ2J{MUЮ|azV9c$y"M{$GG&1Roό{y]pCp_7*+,O(VМvLmc椁4q-0탟 af p7O['җ#$}'0g.&xGIـm:J0ⷎ)W4pX<`Z_3ٳcNrNTbxcѿMVD}JaHmR?"%4;Ѓ!g (uyG4#;Zt~?O[BO ? ޺RKqܱyoݦ`uՎv|;`+i{m!Uq⌋嘓uH?C`1An&7=O!BU-”LKDd8lEs$zlTt<%N`,g @i!?KUHDzW"Nr~ V޳#e|tF"؜ ζ6w⃈4~WC6\ӠuZ@"⃮945m260*@"{[2p|#Ds#JtزF>E!jxnmGjQ +`\p&лњ~:.JRD߮E~\9׀YPΙoٻ|w @{6Ӆj2CY7nVEH9A|!x=z{ 8ϣLӕ<$دC+,*ډ-!=ֻ ѷ}O'.6 e r ,II%g ၲL;Z=ap&7[ʤ{BF}>g|L.fR0`_۶ %\gϭ~i% Bz  " ׆0l LԎ3fHti1+oq){PR;!{'i\D{3u2{be/˕$:a0sN)-=#BzA}%FWy_6;kS 9k5'_Ed\ ΄h}hep01{"eƖK? ex=镓HAubh&bn5au W[Q*;# :^^,cKԡU[.E%Wסt|֘K\b1-Z3 *X0:_U%툭35>f˘ZF:ZՒK\a(}D)l] }P)NBZG\,-'\kE{"_~ PRо W(ؗ|7 $-ȉ_\6({XRT=p (5K+H+4->aÙ,W4`kH1 -sa]:C#w{,Fcp@jI@0'&ߕB2_r1rv=YW"D䭒FdhL˅>ZŎM.?B H Ч`ڂ'ߥBX<=]b},8;p?`;5109@U]g't!})ofI ͚$35{Eg}{!Ũ(%9m>Ӱwp,m26X)Ǐȋg秪O(lУwPq% "!}D,J0kFҽj4¥()D+C\Ǡԕ\fߤ@L8<%0> >i<%rC5\rο:& [>b&fvXa:|&v 8ߐ-CsֽIw>bkMX$yl]?ȸ摳n$#d+"3Z3'\+`L=ppuЇ~[bT6Ce'"Z1d!wc!vzx&"Uen%۹BZև ]ZU~:L;qgBɿmFgsQKDDCc 難~r۷28Җos)PwZg$h}Њh;0[0#^Pk.3Pj0[lCE$@C ] `컛;0l q$z ssIJA#5cY m̜g %L)EI|VV s]nǵ+F<rVk\e)aVuFU] ]'&, m~!XpC7{\_$."k@UmOOs U/iE,@bWԏv$*"Q ݪÀQ @?3,%(׳+PA"iO,"9VZ"Gvx1 Eƃ\K TӸPfue3Z1TՊ|8Jh` Y0dL|6svnZa[x 6 ?ۢ`ܸ1![/Y$#Bub(KRRé*^2^ybN,:km l | $dߖ#o U˰+WR1s@34j9_b- CġGG `M qSQ# +No5s'Z 8Ϻ| pJlhG.+8R'op@Ji6D,x}qzeszW4' 5 vZ*r焓+T8VQ ȬqށMp xC$|d*G֗qR8+\mRAL.[֚嘃$oLO ^aȻ @reA57rCs9J1F"GC!C2f\p gzTPfBc Q9Y5bJȗQ$9_a2 zV}񂻬o][=)-W-Nge1t?r(E-9yDz 8w2>2lKKԑը*1BŸ$Be5c,n3I| b')D\dFףyhT2$a!(,ElpsD-)֞ 1m,zs=O:ґ#0܅@*+*x&}~!6oIW5.5 jA|@(uA5. ";j!Vq}3=huQ&NXMI,PukW֑T*ר<~5uنIc ' A; $Iko%\vZ9^q5)kSxk b42⣈^+85T~! P 5hE9]BEFc0BsMH~?Ĕֳ$82;VdqepѲs@+:ԔeOKo{# qQ4dcz\d3 Ue9 Z:Ԣ%.d ;Ah>V8|;0SB$D,@- qQ#ܕY451apТY"uةS !MC$b5J.`zwa-í8w$A;\}3)uExquo˶s nc@Kzr\A~ 8YꚘ0Wx}ʓuyP+Zd uHR>YGJ!R`.TNᤱB뛙PMb|,p_ >$(?#SG$?mS@)b5j s\aD.1DyȊUU^RzS2FpB7xdJ7P${B9bOaUJ 6IF>0Dc6q; -X @ @r%Y[,v*#Z_#^\.8ZȒ{0 {A¼fS)!3RGɑD>݄".HJJEx}7 /2t컛:C"`z26u$~%uӣ9T_5l)ťpC1SԼDxljhFAbNPxH%<В눪v &DnX3bփYlPB칯o""hYTCM̃:!:Ғ_Q%\dqJ / 3$&=R }wf=a-b}1Դ 6-zpC$L5k,;6Đ͆єF>;mlmJDWc\б [DBo,}հE$B/g產z$p,PJcp;i ڵ1ؾo.l "h5)KnH?Wؗ+7<'%OHF5 &>>*myyKHHի?a|a3D4C=/d1ڿ &'k\z*NtHO)%V>^ zS9k)?r`=_tm'S"f2=$I,x~ɏKԑwU*bdNJ&Q oSrvܮC$adz&hwQbf(ݜ I |KF+\~kGk2Q>-(ؼi] pG@CL#۶7QfnL Mݑd53wnDu;C4`cSp!/ʂέVS-9IB5Y\ 6;5V2Fބ=5+nσח'ut89^Q\ǨpvrJ sďmpq; %nH9Q}j`+C?ݰ} .pӱ#ZgQ-( _>ڼ@㿗}zUdF-z^,+:j"VSi7M)o9%#CUnsV>ր{ztpk53BMC9t+K8 n<[nGU(I `w]Ə^KwVo[$dwޞX~0'(Qn sP}X#ЁK"Í<~$c+kE70Ց QQp ܽwK]辞LF?ep/:ʃ@!7i⠸suЇk}n5cLcq֞>jVJ?j{b8J.V#̓U{Kj^)1jUA6JLJUFZLevŰ[4qڹX j9ۄ}${|ƬvǨI=):C2+'oİ$VE긻eqn=.7ŃQAMЃg@J6Ez01-yE΍aFs;X{^Q&M}j_n)J1SK=s6W(5>EY_"u[`mTPU;)LmspiB!jx f -&R=/9ejP2!|VV6Ro4'M_e;!q^snzW%x p8i}:IL6[σT{ nzv*bXK2{5V ^[N:rc곆1s$ {ouAޮ}-kT|c7NhѪ\oT'{İL/UzScBHָ].\@8Kj]P^kv~Q/:"+?u7h-/H=4ή - AʾCl8Y$'e;67v~_N׷~ Wq֛^1{9xKeNzיz;%#EU0$[Ks5UPb+O@Hvfh;p vp!ަ놆Ru-!_W{v\$R#AE";H Rq_diцn_FЖR5*.Ed$R`SuG H!8(|C^P ȿuc\GR v~ڇ2끩ڕxL,Eս~ns9-.4Щy R ?wGԑ.Gu5}[$26stE`|n(rK֕p]ORcB2sun͞s uQ qEz3?C5sJVꆆM9payT|Oo.]ET(kun2cF9) ]S)Cd$\Cbm^gXj0Rv?:h1"v#s:]7Xf˅TߋG?JAUY˘'窨x> Kwh0>TpPUdrr̍4+W5 MςԆ`#*d(&p^)ztgwn-8dds^f0h#>ܶnVv~ J]%HNש9E\H8=eVr0_bl^r2ﶀԈ ?z.edO!xgsT* m(H.}x gVXI97E.ɇԉ㿣Ez;$#AU0%!1ɺo(~!ub&v mEUeJMk~ۣAc-J4WYۃ,T6%ZoN--*,dR,>wGԑ[󸁨2df!lxݲvEE5B nOimv?RWs>ɢ%0 cu06 ^^]心j%gcuOh&zŗ@*ƞf^Y\NHߌ`P ]dHD]eHVi5"h,'mr׌5AQs!ecFSwX EUJ([xAC"e/p+}k~n) v<ˆ`N!uܧ&X:rW6w9:qm%Jk~CYq]ͯE[s]rXxZ ʗl{@z7_j!cY'[kaNH K+Gei#ef֯P7RA=_북sPM2EzHfMZtPn (_ !%d*;Dxd+l:,Hڕ;NKT qߩzi,!T.]{JOJaܭj'9| G\._'Qu3o#ܺ#Mƣo+.Gt`1tH)&`mȝ{ T"kn=j 3bWK@59%6h ׭Wԓ?wzS2QYdk[55ROey3s>X:E2e[.b{)RQN赺^GCR_)7֯F_B-i|U_/EJ1%}b5DuHUWՎ Ue^Ge>`5H#RW)>wвw>+k'l a5PA ,>ŽGԑ~QP'KNLuX mO)-;?⿿kѮv\R%[هHiڲ*o-=$c+y*| GHuYq'Yz#qrTH:MJL%<f_/K4;]]}k^d]Dnc||0p_mW*%< .IV>U*l$BjoJa1t=z%)۱Bu,HhE9"Te )s@?&Cruܧnuln8*O/W|lG(cReՓJ2E۬)!b돱A Ͷ6Ǟ559 IGzR"2SROO .HwkK2eTEV:_"VS(mjHtB fh E. ]z7ʬQY6]O>kƧ߮F 1UI"Es eTnT=+e-R:EkߗURU6vȄ> )8}}dk1{}:9&c93–LtOyRp 9]Xj\s,%aLRay)9c =ٯw4OQ'Ė1MUYł|Rt ?mRo*W7IGEfRӸReRvtE0eCϘ5좷g]>u#-K=Be$ǍHh?.>UPQP{e5Yz΍s+SmeBJОW }Ң[q9_zUϲ6n;% Uv~_H㽟 P(H^̜AU hۜm;9P8 ղShzv=(;ȄTg@{ald䳨zf/H6EKZW߹JC;QMԛzFO)a`#>VxonBՐ׷ʜkf̚=@JҲ :e= A ks292l_;㉍!uod#'Zpa]/9K 2iul 1/)ТrQ=g-M_sV9hޜeKz+Mkݞa+Bӎ1n;yޠMSjpeniPiKV@H:^ d˨m:.cJAk.Cr VCћy99gXťn/YR͎V>Ue/RN,I)_\t{ؚmjK-8]z7tJ۠TYQBܼM:!Ul%ie=0 Չt-ʘdҬlwz@*/p_OJW0GweaF4U5+_Ub@Y pkxDk_/zIrai7ʈk%ĵvH q6Н9T{sPi K#"q@<Ԥ4![ #6|II<|ס -zR]ux\[T3+kz{ x!=b&2h+R{CN7%USP9%i5@^QՄ'viT3F䎚1zRTe:?%s8!D =?[WVg:A*GoT9Ý!z>)"b})7QB,Pi޲v,YVx-ڷ4 j)$Mi"B)ܳ78ayG'Zd%CJ axgH 2UqRw[۴W+gԯWٔ=|竾U蕎y] hO:o;e@xAz)=Y˒R$Vŵik+!>hH_a^t':bEz[Iʭ.+58¨,zH٫|uA̮z4b$|R,wMZTzWغz$IJ.>BeFoI-? ^K~5:#2E}x "fW1'r!%s:jU@|bkگfPэ >} wjIEln=̀^q/ՆFWtG} ѹԟDتʹaYdG V^;ɏPH{WFgByyR=Ͻd"%"ou1:ط5VS?=nB-NR eh+!n4ɯ1-{"*}I 6ZrX?IЭc$O5 _4y#eˢȷe6K5.^=K 'N;C~ZcJGvETPlZtؙ:BjWQD_*l]QOіebsKz~FAs$A=K-m ZOB|E6S01:+O5.uVjk[&Sa[þP h4TjJ!ZQ4/iz"|EDw͈ѭ>b7X,dE1#[#@2oyB 뙳v*UFѱN'ʙuV*Dn'⴨;QR\xoڞV(#E?  OtUU'Xkh.<ٛ$Hy2rͻhk/&l\z Q ~EDkCD\ Rb_RAMvFœ|@[~i^">"2F BchDT$!eԻi{e_'Q"H6gT-hC*K>H~HmڊӢZqۚwvraeS(LxRU3Ր_ |l|S>&`g`G/KQ P;H~ ОzS~l׻!:88 Mˏ Ð,5۴VfEB[8 '>[DrȔe%J^)2:ҩj-$CGJ(fްJ {Rt`Pr |MSLVF;kp:=yOEZo:VP VO@o @y .M ć{[ 1FVeVV&g,Ip])'#zΚhPQt aoܭT7R%>:ܨlJv~>.K[8qIWR)׃(f/I{x@'o/bCYrv@oN[[MT {ð(:߳Qj.:\ PvJ]Oiao[=l~~dm>(:,a:}*frc]REݸlyVv7uL/*{G7HN6h[mSJsa5zFf@mhs|{|^O|BϻB]X>&6HA,:d1ŻW.h'< maGQ$הj Y'r-i9=.~7.ofB;t *vpF῜JE1vR|Ýq7sۦ;%H?K^B@іՒW731u7)[!_2َ:m,>oTS 50ܲ^ee`KLk4 nΰ=* y2m)9=W/b-/) _c6_T2`=<QtbX-d톧{gfdy:pkFo&_XTFʳ6&#ȁSmٙ1X-qCA^!m+;=dNN f GDVMU&kw%'3Y[u|EϗUP&ȻVQ)=kYұU)>nG|||2+ۑ_Jӥ3N33!X2?j_ Xĺw&gj/zR&ʆëZV&;'%D(3e Bѿ7*₩4 ChƽO Tx }5WoFZ׾uvpi %eADge}isuG-%Ȋ(+(KXD,"7tJVBa֩׿@$cǟߑ tFLK2E#K02 ]*~g sl5C v @04%H_EqrĨ;ef۽y1)=??)???O?????????'=_;ʙ_v9@ӂD͘VP8 ԓP-*0 >HL3h gnvzZWPƿyWwB\L_[G??|p?Vz}N(|Mq߫={?X߬_!_ÿ }?K)EOoڿ`? xzoKk/ /߿g/~1gܿx1?s;?}VSr? |8߀_~/ߖܿC?O?|2_G/}]/??_YW^w}G??m;_H?gx''PO/?{UڿU쾢Kg_Q~p?Bm7O:{?]=O˺M幺8LjY;%J14qxch#G8Lj8%7 ͑1*CBUch#G8Lj(BBXZK۰N?"QCm枙iȵo\^zh#G8LjZjS>x<-&OfzW}7!fC?!xcp*XTh-i"XXLj$NIQ,b-bVK:Z~#G0tenBԂXIM<9+t("bP8Ti<7G&X:-$j9G Y*.eo-sih#d* ^q=f9-4幺8Ljg(=8lPI\^k?j9 [xch#G8LjT̰j2ZmOLjңk[|Pm!{? K?=Dۍ̀#3=[8!4I-kbVְGnub4qSpJm!pm{ #ۋP47{Q9\V-c#G8LjՒɏ7/14qxchMd;!_yag4!~[ oV~\{=_h1[LQ4qxch#F7[g 0i:ڢc ĈLf/ir]{s{csG8LjHQ:|JKBgh@.FZۇ*ȅZ"K7"yEg)-c^:ich#G8Lj)=WġXL%/^4qxch#G)= 0,#dpfʅxch#D%Miqsr[ ch#G8Lj8Ljr7?h֌vlJW~{nch#G8Ljh#EEB嵤/b`ۑ[+G=;o 2U͌.'$$~#G8Lj],vR#^{٭8Ljk+mya Òq_|& 6bW!f>+@kZ 5b)R[xch#G8K"+{ׅ幺8DŽъhj8Vpch#G8Lj<&ʦ8yJ_Wrq@}\bEShUP4!stzj=8Dž^On14qxch#G4:&Vm[(;!&1Q4>pm׵lSFL>9%6LL Y9,Sxch#G8y~614q~vZu[#8Lj2Ne<ӘhQ?h#E,ojXq\ [xch"'7*znM?BX=^Ua5'614L4rmA^(n8qiѴqxch#G8LjѻNtj<K`Աg QyOjwmm[)_?Bw "j?MceUD/t;PwD( ʎu#HO We--0CG784bi/t2NmkW|ޫPǷ7G8LjEbh"u?_(Ny)G8Lj ",:>" (݌S9J21QȨ6XF&&\dXK ==浈zVDynn14qxch#G*m`O@*B}-ᐯbk{xɦnF*i#\PZ80%PB '14qxch#Fz^ T|ynnY@4SZjg[߮m:!4,-<«끲ޛ'#0"/|a1 O;7幺8LjjڴWG5J[xZ# *#"Jj&t4I9ʢXY̾sbHBFzQQ|Pګxch#,G^xch#G8DŽòoNCXx!N5Ywp4h>Ŵ!+xUh XLjѺ:5Zݙ䓀|0 bt D'@ deoٴ|o-<+>C|nkXOF!C0ch#G?p9We@߸z"R!<4Rx $b)xM~. 8l*,{b< KTZ~KaZCI[%sF&ѱ2ӳwAxRXLj*Nk2گ 3SQW6a$NJmٍNixPXT +KG77E5%M!v`֮m$l?D#/ "h#G8 4h#G8LjnUt0( lTz_4MqIqb p8'<4׶+ɠѳ+^fƮ#Go)m͵0&d6`!|=c@$l &Nbo6xXzP5'VСstq mFZ~[PG8DŽTCe4`{傉Z:UG*IfQ&1dMVp'&14qxch#Dm:A*t{Tw_#966ĝWR+ ׅXV{S+vw=ch`fzHQ^Z8,3ʸk$ \iWG[p{Rvj-?xcLPjFI[stq @wz"`~P!/R87u o-T=ir>m!euFjRoWCt>qxZ-c#,L<2-CO kʔT^9?'Uǻ@* lHoޢ95cX8LjqxZ`Xܶgm DXLj7MXkBayUPpua%7=} 5MyR#G8LjѼ5<߇Y.;8,N=.W$+ 8f]BbI\1ѽsrNO8Ljϖxch#GwۓZ_2d"LYd7SV2FI;myfIYjt0l:#W*%1ZlF幺8Lj!{'{|ynn14q )1ާ떉(z(-c#IKVi 3pvO`bh=ǑqM5x¾M5?YC 0 wC{[svZ<)N-"z}߭Qt1tI[sDYlJ[s6i0oXLjigEy ̀V\|QLdun=,LF={*v@Za<`z yk8v5^ iTzM`0H-KgFY)$=Dex{e$|J&12Ԩ:(["^4Q<hwMH%Mlk&'D‰湁byK;8Ljʺ`-TjC?]'Ŭc¬ԈL1ǁyjZ7ǖ0\' xchη1t#G8LjWykw&11=D agjpL /]jqS;v'&14}?21k1 S/xOCFV3`l0;.Cg|Q4oCP˶E? fuh-z Ge +'CwXdizLPذ~bhk{?G7T=D͢tZ~#G8Ljyz>| ch?Tv Cf&EP..paqwJ)K A w`iqLU=JpHa>WMce[.dpXYCa 39'Zd­YIy?]0!Q1Ya tm0-xI2H8F<*󷦪baT)U([xch#G8Ljj\"Ɇ<ޗF!VˌFރQxz8Lj<'/;r~^^ߎ49фf֧6VxH_-c#G@x@;^W]#_5w9 ||.f9_A Xc/>S9hѧFC-)#ܠNV楮 046x@mʼ2(L+X[:do{˗I^=ʐQ(*ЗԞĝܾ]ǕiܬsWpRbT> ほl4qxch#G8LjѼwbe;l߬RoËOhޞX8osequ ,Ubh#FnAz Q9W_4): x TU,/*T=TW+Bi:/rq{mn>u!{.!jS5X 6ĵߖ#.K͚b.\<0@gX !%Yq֑'v˵cr'|P6nn1RݕxGa&«Bzw25URQ4qxch#G8LjL։_Xѣv30ޖĊl$A9n]Qڭ&ch<Γr['s Rjc{Rl86OF>/L ~ {p r .2jQQOftZ~#GV!1Ԙdz>XLuuƚyf4i:x8EFV89ȣȩ僑hX7p@F14qxch#G8LjNV.xc$35>Z~#Fwצ 0J 0c"ʟchrTpΝ9nOgI/xVP/Yn?-ٛOڑӼ^`˞%K'TSm@4χn"qiP?\;aۛuuѻmaJwxmt괋AQ|?bqs$q(&eefRBT.jE 1oZT4#€:voz@vipqA-~^TX$7JF, xJ#*StVk@iG0&`?(GA((?>?9:Sv2p+*~0ÿB @(LKNe5`7N:'炄.nGxW^إT &v1@47Y?ɩR4c;fVtxch#G8Ljnߡ./ccdT9 noT^`0ε]q0oLSW`Z78@lpJ\bj FhoR˹[H'Bd=_-b@lhh#G8Lj tDJ =z 1ګq[OGȨXxJ!CoZHઽ]#d^rqL#P9*Q,_MZLǿ@p;-_F^Lr:\ mz H:XłХzd.G̠aDBd4WJ W]Dͫqϲ*؞6ʄ|o1 c@u@ʉupJzo8Lj[qH<4;7͗p[ch'>3z8̗3k>-RXF:ʬѻg|irx V&W揰QjT/61 ۴ u[8l˄3&[1+6Aيrd %Vb֜= QsymPD;+V]{1L?KJ<'kc'1yk8LjzWq`6ב1O $9Y&Px7Fؼ4OTnYZGа8X S ʇ) BQW.@ =oDܩ4āKߣ dV9(IN{'"26H^Gn <:2\ QQ(=y8LjѼm!7`aWJ!$2tDx-x='+]/2 Xs]'&hRSHWC`NEOO-^E- PT ӡ&]DUhٔX='xhC[stqxch#G8Lj< &i LDtqnkZ]Dn8lnP 9gn)IڷPh`+Vch#G8Lj?#cN(X(mSt?GWD CEH=H{/ Mh1!P#("$qym><[xch#G8Ljre&UR */73Fy!۱M͑<1qi?Q4qQ6#MLf66=^m>JՁ שWzoqfHbhMhW@F?vXy]h~% q}:^Cs$h[GW?+TNYtykqmZxe.rܯ 14qxch#G8Lj \_F];$O}ybP6eXqRk&K ׻fEۛv ]G-_*P=(xch#G8LjKvܻ.WS(v=!B|T [AЮ]yז?Lվ!OF뾠yqѕ0+$; bhP۠KV. "Ec&`$)#bo-EIIR[tW@wE̕!PHS8Lj;,(Gׁl⻀"q̔U# K<)#xch#G8Ljf`@N):SLn%Moy!ꅑ^ '%]M 47/u-c#G8Lj?vW9HgzTQ bU|yMZpc6p񖴓ךÇ򨂶U`Qs%LlJau4h#G8Lj [xcNW!HSuZ>wuڶ]9 MݩuJsj8qic>|F14qxch#G8LjZ.f/2{e<7Dp[stqxch#G8LjRPrRF4lpyF6˽ xch#G8Lj@7G8Lj`l/ #}%9wH{| N+r#Np$|:w|"*|l욭qO12J<tKZ 09i=M"2ֱ.ð9}/o>ܣEwk?\6pl+?Y]pv]uga6!@59rFI*:/M-#\x#ڳU /#'ߎ荍Qqak yTw(k>qL .ȏYljZ*7K}UAnJq: ړx96MX;jx mVb'S]`}z J&i?N\ړ4jbȐܠ, k*%3~t|Jvbp>ģ`/v2ӳj_Ԃ!ǥ3|AmuqַhŨ9?|BjM`eÔFerv L?jI1#L).-'1tv2xʳ 21 s'Qi #Het:F۸֗ hy'[͂;=/WqK@&-ױAns+/= j#.=T߇.ֽ׏a 5HwՈܜ£5e/V{`0YQEᓾpJRUz3MQ E=/i]TQ4eOJncc=!OtǼMl4lM* 2Q"F #<%fZ54!yʷ$ޔ fdK'+b}πX Ӻ$ǃַ@%qkI[zF"7z7>ig+ °]>6s^RV<߳sZ+nʊiwF"W8dc j`"Y*q8})l8K`7M݁9>KT `&d处y&1{n!jDgg{r!9djnpԏ.}VY[˗yw_vPǴl$#jO'Yi|yyNnMUF& SEǀ?ã]feyl$Ba%z @>o:ɢ?un Tnf)`UepquSh-q'%KK e>ZAX q.$J Z {:#gy/fX{Q_0dc, Gnu:;J BYKxa3'⛥#Y؂1#X5Co>TǪtq<Ƴ! PۧɌ8YjknrآY/xMKssn#Ȥ^V͔NdZF%%\{~2-H38Bk]G9T)L\`,vpk%mfd F o EDcQ = 8gGYR,6l^PQ@@ 4!mATHmk n/tel=F~zJ _ǕҰ\jBdu4lw2c{hqBU#.-sh݋IQ)Uݲ7P{t@p;qNʕKN4{#c)r_Oߣp _?4/V懡2BL+K!7K'\p^ -4+p_D Q07qmģl& w /YI7y3u:&ͰrO)U {Yn>4Bb8!9&+J&`>bMG^֝)*Lx#[]~z\^qLx^2DEIѐcD$JRV}WZx f oTNJn`WM6̨AeO2i6yMcψX6p&ۮO` {Edr"z`Nl ru!@Q{<1}0RcٲƇ~ Gʢ<}ѓZߩz/Bv1IpjߪtteSKd'kj@51{O0R ? p4 ! /*ZnPu{H@.U d*Wb]r~'R- B׷b$=rHj205%^,)t1)/:&75npC3^1ZBCIEH@A0^E*C< ^ßs- G{63D*3d@ij n"sohF;zE(!?aZca-O6vzl'2Ɋ5zC_ibXBAODqtk%N ո*Qj*'mq=];dP z9 ኎*SӟM>D@+ s+~ y5坎<"jn5΋8@z!9\smc90q͊O>nȂચ?_`Ss[q ڷΓrۣFnoBK"u(la0X 1Q=;kS7&ۇs!-Ecī6xA rR"%2 w&]gDJ0%^:#dw\!>^X^ FB ?yx.qx |C}"eKHp.ݚf3]6#GZx)VX ɴ~hA,aQ0^]BRVV6ڏvAr,OHFP3>z , &Z3ؗ dz ~uTE5-b(1~*=2Yv]Q# [F46N}Ü d + ':˳ RV6sCV5GDT>23-}DU.qs_b?hmR;OsίωDQz[oQ.풓f k6͞Y|j-Zg0{0\ ,FWgfy<%[pqsNvq)ת*:L=Kv&u&7f_%?qWUo9G6b#I` XounwB ֻrYޭ ,] |8`:z[S[֬+pBR$G@k!QRB%063ihhT2rJ!^,Sb6|e`OJZ ԭP"%δ>h\I )C?v(r=qj@hU5K:nTK[_0I2쮚I4uZ \,hxYs50@~Uep% VMIʆ4LHrSZ ɱ醼R f EK23=_=W'>^Jǣ)W݊wfPξDXa0\އG}fN5b<jAp<&μ<jylȰ5NhzC0/V%QY76zں{+0]] !O uͳ[om3Y Ie)ftsԳ15a2~ V>p!]&-c.$h!zz.;gA@X9qSn /[d{!3K;~WVЉ,dun:R0$aϪ#5ОW;" ni$<-C1Uˇf8d~aPFlbzBRqݰBy].y+MH"*bUD@zL`B]RIaftŅ"5كeBS`3+-u퉤5L-')eaNriFwd7Qx$6* 2,4YwU#>}Ey^ p,|j%At{(p9KS`@[ug!\}Ɉ`]LfuCWZ 8EB0P[q 0AjY4MOIY^ MIbֶc}j{Gb{ҸUnE}Cs >6Tc9(NQ%*otWĢey#s9Uy&!ob1 Oy dUH\#C4Gֆ7e\U,ȟ~ZRl9Wr&Ie·U9B46os^e]$ֽL+ĶK46<3~9M]m>7d["B4z̖\;4"^lʀ=,58"0HO } CX3C2߫i,S|xG0x&Z$o蟆؜[HD8EP?nOu<>gco~bflTF >Fa qw{9zn -$l>ɖݮl3y^T!]σҝ{΀לsP@CX^o#9pM*S-R]. YCvLt B RY(ԁnliС!? g>OѨ@!3mǡ5?R02w/ +wbT2YRqX8 ?;V^ȺPS87+Ic,l&эgߍ~fJ/:!vj (97G2C!3?ͪ9[b%Ԯ++>v_{U;`FonO({ [\)* "D@˺4Dybe6\76aoj՞ט>6樽ɷBJLYfɉ5[f_qJj?Hk4ܚ) Kh*̰\=H4?p\A|-cQഩSP3rHsq Q|@`)=Q A%Orc7̶%HzWG#)~R[ وY8>AaP #C㢰gos8L- zcqQ rش1F:b$A_q5!hx3{:cMD_ ^58I7Ŏ (;_'ۻbp;KC4_#'vHkE9adce/5z R T7SĞo@}m3w+bVl}K8LRg8|9P[+ޗ́t-ZM7uB!:Z$)z׾'7\2 CU"s+sg I2Sr,Y߿yHũ*ݰ|^ɅxvXX lodʖ="^}_s$xDiRgXHRsڄ5{o!\)E7B󑜈ӄ^C\:J^C:f=̽/2M@A ;oɡ XMmhe\<CѾYQ&1|83 4a],ӳ5ѧ  GVE^P~XF%x^L(t >TSn" #!lxѸk;MP\)4nM."ƨwY`q mDEMeB!sB0>RHťdgQ Q?1(Klo{Efj eNnzHZlP Cu4֫"4>$(yR8UY43Zsc۵ˡ7N"R-sϞ_}IxY%;1Ic2j|F듷\sB0 h[<&oX*mq |z}"^oD/BhW=#T0*8ͭt.18Ya-Zrc\YZ.)L@n{& >+/pڭܺ Mse4ѧ9.(œNĮwfN\ |IwcᎸdG_d`|7tǘ۴֨Wqwk9CiYr֌2Qy涉[+m{ =7Ef|S3AgtbaNQ7#9NŒR||}T7V|YrLFj.R&FJ.,^7Ep٢:l:JdG9vI,7 ֓uI^ ^#lm>ӣ625@mYvy8rFz4Ț}7e}*:6[ylnsȬH rv*!(ׁXYr\9S}qƿUȉ17to9h5ѭGXAY9]UOxѥzP*HHq/{x={[K;3]|UF氧Ϝr<N .rJL?H (20-~Թu 2owI\!Ox0FwҞl;S{alJ&s ~'/)ZmsSWϐ957G~_;Wv (Ō:di c7^D?ʦyr71 - !~AF!gkU ݪTn|3 O7sqp_C^nU\yMwP2#g'hW )Yu ^mz~L 9:bi1nvMy_ຘf92}.{/OOQ|«ӍM~ai2&F3*DB uߋZ4vbheԵƐn ?SD ü}{nƎ;-bet!)lhߎ}÷6ŲԤsT@|vF>MA]aA.Nnj Ֆt]Z V|Vh o\dI2𦙞qIFȎ4hϚcU4*Uww3#1rf?R[Ը =㓌M׬9ٶe P tUC+H0vUg!)oEٽx][A|1)SӝȂmD_9%t>F<\d^nRwE-5,6`o*JB#R0#s}AA90ws)%_K -eEpIb"1 -#@{j\DZA8yCAS 'k <5T@F7`dReN*GC<{L+W+Zcya\wM]-LH<@860|)p<"-Hv[7 [J$E'ЛGD y: .2p\F3m4G}z`Q-Rw;;5bJO.;ak8 F^ 4)AKkmv2J5\6<>:FDZ֟^mlXgs嵋hCig{+>5 ; 52!t-'])A7$/ɇHyH;7ҷ X5#iQNM@ WAY]"lE gs;86 3ktްpiy']i7V㌣XyCo@0m]f=#HU]s8\%1vBH3 ?oS3`A%NWD+,:Q$sq&lfdxFGn\&CW¦kK+*䌼l6Ai^{Jg"c4튬@?鷫G~M[dAQj:Z?w3weAs:Ex6-8C]/;g673?)0UZz*Zس#dJ G |5ȥ#>%\KCɭ( -* D JgDZp į% w!iW14>~I4KՋFa2T 2  W|%Fޣ-dw}lG0[ax\ y3J:F % [_ ŰøMhsԵv> KaB@j.= Lv')̾E& $< e9fX\+[v 2uW)l(r>o$hL@PُFz]aϺ}rzb0VnK2gT 1^Vv;E Pb`Ka†~`S?ebUFRs|rpLD}ELf^WC(-cAݭMhE4t,}a+ЄֽN{eE>lj&#Lb ^^ukbQ ]|}D,}P-C]B!lDrC&7t"f!OdĚ+ .T)fwf{OeCdb{[[4h(@xdGbezuYO ݱ)FOL[=Z\eG?$BM   @ \4{⧣.}".jKoO]P]AA:FKpVHّ3' =Cv&)mX tfh[>j {^0 j㎅&4/8o7sq^__'Nȋ,έT ˑsW]ȟ1C_T~rETF}'g^+K֞}@? ,֊-lyNE}ZO#P!? z͛[+KXHJĦpc7-MQvz p?gs%:ĹMG `;^@YI~D/ߵ Po9d6R #6244oaKM@nT,gbϩZJc 5΄ˇz+dN-͚ P%E+fCqQaFaޖUۜȣ lh_\ST~B[f֬nxֻRW6bUo_Og/.zOl%H_$_^cslأpL1&u&?0A#s| ΈU}|e/ sAtMU< {Ƅ&>CM"!Ywӈ$ʺ&\W5 RwǬ62UL[\N_ɢ ORC$֖-Y'a/Q1땿1qM`L۞TۜI0q \>&p6%뻽=C ߋ+^`#h1&)+(z=AX΋xi+0 d,+\1[d 9XvC'!6vjyB ^GF[>4FS]- gYXmDЫ??1?%B:$pY|q?#\@ ݧq@=}wEZ]TGhک5EpNNzGUe^¢EwO\H_~tVE[.)Y\{PQ;s?phKaJH# 4!r{ڥ}ε@.Sp?*.H;26AXאx4T8hU9pŰHh;[(ζ`#o3Ʌʵ|i&L?bϴǖmrK-n4u[iYMn^W;_=?sX~i\ (~Kcn0PHO]%uz /Un؎sG>hx_-Y /ܛ+T/1=Ys4 w܇!U${r{Rn%` 0<>S׾"&JƏk b9K5Nf<9xsQYqDL%Ҫ:jEm)Jn4$-9)g>XGBm%fm/?+ԨRFw1w>bi}BK泹iFYm[Ol:Y~% Fz^kHD-,AH#ssً̑G& AQm_#.{`$7ssVҟy7m$&: D Cڦ^ã}Q~GO4&H4dU&Iy4b+ rE  OVP|1#JW+*sK|e!M\2 3/G~!]]Ff#yLf+4Nv#~@b/GrU7܆p t+R=ܞk=tǰ)Sۼyj.5igHGz$M'zyCa=ߌN T97 mߗuP)x~k.PhOV.Wn% rnj;hDf>mmdpvS3k8=},ngJ//V~>-gMo0:cw*S@Ғ100탛[ !@ST#xxʮ${tpk|XP{xn;xoLs+nQ|j3{BJإeT^ N JG-X|~̔ dp8HĮ]p~CwܘcXhF[ &5DSA} ?mT=/V77M>zИ*bNtwut0%DUyOf9xUEmԾb,bC~̘>@Uuᕢjw+AW+E+nPr"VO[?r{bܰomڔ̼BKH[x"sݓ5U#C59t3:J>EUmG.`K!o^,x${=-Z6~ {+SөųTᆿSjC Csp Eg:ͦqh7Ƿiг^%wgykʳ5s@'̖EQB 1 ;?-_8PZ! Dw:=w'2 d)j|| gP}٘[pҨ8g+֨L1&x{O{I#DoK5|q-0R|=.wΗA.c$l*k"J0ZD\׆t \Wѩ33.2zjN6FǬ8k}Lk*fQ_6|GP91YfwkFfpV8uj>@T6?叕B^cEܗL)/Kp:ejgwlWGmXOY.`lme~O~SNs콜y[ ؆ eB,>L IU\ȠiPK"Avc#.0ߡ(|f p*!%|MBiPh:u{nb 5SF8YΫ7ƷS?ղ謣*מ1‚Ru;ġeHAIJ,or.s--sEsU4n2Q۩*4/_AnKm{j}]Bds4OnXX# DO1b O9c"57eh?pBZXo861V֫tX]tS4oJ9ѡn7Nϓ$)jVE;y_[A_)d Y^&FTV!愤mdkЊd]tlՃo/34|wDʐ@|5>072@{>}l=&0|'6m#RsLmpcS/gȢ%T vح 0O!\egb 26)g&acBe' /ݾw7PXSTY>S,?24+é__ѹTL+ﭐP/(t-| ZY_w܂Kl2qQbm9\r|&\r#DUΞ`CP3eCV:"; w76N!{ 튖ߏT [ə#K_eĥl4ע7f;htqS.GPF>>$a4 !J;.+F:!LHlB ?۷e w#-B^ XSbԵeAω5uevꭘqVǃD/@B+ΐ5"//!'?v%iG _.YQk _mJHJzn4(FV&qE vmysBuD+cbn땾t]j>H/V~> C9cwP9]45VV_ܾ\ T*ZRKp. M=:{O>5܎T@_>!ҷpIXt}obKL=ZNBeG-WH 9(@g?!P1N Arށ$V[ìPJ"~ސؒAG\$~[ai'd\q$FH@G3\hm* #p6u`m=%qI"\CvJ0qbb 4! jX_G-G}I<[]¡j3ꌎLk]W5\NDIN!,|Vb}CdD4Z{F%PB G\p~˅Pae> N2t8kޔVnxFԻT]UZ D8TT-{t)jf6|kUiF_dbu0]LrY8khs`gCWE&)[eJ -RU%J(s@zOsgI޹t {@-d 8YU- wMn5C,^97{x` #2vjFX2,7`PНvSV>ĀyTZw ҼٿTZ(΄i}u<-mr P_z{l@痒2ކpy :CFs扙,9/7|$xL6C4:O5f9/=e4`V\8ǧI&Z,vxi(`N9$H'<gjl#5#̼M!LH 0p:\uM!rңkӶEㆮWy z5:ɓ=U:%͊g$.=֗MS5p.$11A; uJJ,vSK"7qoE2 ekE. :)Nk n'gᴌ*m F3hSno1Yr'*Z$-!bR/ CH9CͮC3 s2,+jIox N ޔ+De ІAYcdZljˉ-*J?u]B:FA8g+KaH|x$@X<:m) #Wc^@s( 춹ɹ3T0ՅjH'ư&3T[ 7N1?>@.V5DzNI? 2x)h8ebӤd0j>j=CՠZ_IX=[$f@7b+}on~`е(YcL#v#\`wPiWYd6# Mϩ@ŽnkN}c9`m7I` Ծ g6;Aw4He\ X8)N}#wxVT7r[geBp[x٬iaf3Pyaߢ\#T6;#d5H4! {x>[`+jacZ&f/n6粪'>5Ͷ{zYD5- YMݑhKz>mf-#IUh+eC&NןS^PoEA¾a\kIȵt{ U\#aOwVU &n3SOypCN26iF'QYeቷ *Nu xLp`\) 3 c% .{v<1 |z8i:ͲZXz*&Õj5 Zt18xETGx2z G]bdZ4YOP̯܇fa¤ ˩KQm`@*Z|N~l8zD!TyɸGE!R-VG/V~>}89>\Тf"0kR 6Pݻt. 6Q@--iT$2!v-vמ- V>K喉$<<堳M4lr/w&ÏLЫ=[ŴA',~͹R= X T tyc+r ad;YL4ݳͫW.+:YX|%9# _ϙ4B8Rf*o#zABT$@yy]ߨN8Rbt2] $@'<٘u;)rҲFekXJd7qBv -' kӞ(+0tH68zgrP!ETr,gDn}ΘM}H>2#Uf?/d fJl2&ge6NpQEZTVd{{+; K"_)t9@I6xUhrwNC~ƁN"@NDռ$UBtK؆3/Iez,#vf׺jJegS7NzB@\hBsM8 aEs2 <]ѿPyu˄ؐN~DUNVrDg -08Y疦yt+|}o]V w.[Yf#MF'|T$=~! 1 ;]/W޶4a^h+KUS~Z# `OEn*TY&U§Νe厤؍x&"G<W]O_ϤVY 8s`S#Є!*a3 {UN1ݿ62&/Z`ho6gm<D;7ߏ <0G+>e 4cS(1K-$봤u,~SzakDiؐ3UXO5Z}ai})U::-,OyuxFI8aK}^6=Qq.^ddſX]ʖ;ÚҒ>N: I[)W4#1a er)*{kzldO*ȹp_GwJ1D8grEsryoي^$88'RğٙX▟bHx`u./jaT.6dU˙&ZO$8#&F9_wiTT@`~0lJd&.]x+f?+֨ Zaܼϳj.S#ЄVRk 8t_aCWz$|ߨMٺ opƆ,l~?vnEKf*1#Oq`Vx7dc681ĮZ3 !1;#cު>5fWBrᜢFr@o#ƔNW$@IJM]3uX R3+C6d( GrLX ~Os26d+(fX{>!'B =F c[!_d-caW.\MSw/ )2`(v>^K^bDD:>tLavo72+qnˆA ~ƛI!tGx8X2K2>MnwV)q21W;6̌Qt/{ SUNB¡3܋J,^0#$$A>}Gskۖ({[l/ c8VhMjз׉I2#9k̾ Nn yb΁@7[^j*q1\橽VAjHkvUE~'ȑ> C׸](3*}K0/G] Gz^pȁ[Y8, | =) ],5P8-åG-S7)7ٜoKPD Vo% m/ tEG0T+,c~úR2e\T]oc9f,#MR~N}m0],۲dzQt0E8|ҷ΀>F=[ф qU5/,l@ {Q;>f*ssE2ĐKn׍~j{qZ:fאCBz "c.+{ s, ^\/=*λ='`0EC\9V)UFT}+ ;L:fM.J^3!ȟ9$-; qV9:DX ҳUx"Խsl&QC,ݙP, #{ cplGBo);W6A`L6^I*VU5 kͱR;h{K'bGvRr\V}(d[ms{n=`E_5p {}aΤX*o^uٮiQඊTH$n?x*m< Ķu p'År$N@}m`(HPa]U|`DQkJ::~~Gun}+v9ȳFf\0:32ΣGn H.ݱt?{Bkh̯X;\͹h((4Iq`<;&+B-(Za\Ei+mGju÷شlT%*ؽ|юrW?1ox-KdfYI  E'_g1tW}{^z',Մ.d~P5$wpnai)-{ΏA7=)\͵Wc_3]]M,w9e!!K7˿GcǢW 6:<ʡj2N8,{DF>hV⤇0pzщӯ6b2 eyq^jTd#:_ po$EGcw !'dpZdiE'&G0XbȈ?@i؞Ra8&A᝱dϑkjV/$ەUU:I =C%^PX_ˋ"*bz=z`Ս%ޔf22dwgNMi\,vD ~!pLrج|ޭ (V 1׀ pr( bo2l a'%0OZk,b)ipT; (d*UK)buI4&MIt%@9Sgy8>(.{Qbg7Gi6z1lgE_Zf JiF }K|l\Ee&&\6bü&l_Oɝg, #'c~Xj9z"Ǭ`pGs 29 k8oʹ: k~{E-&iXҿzAۻ fͩpi.5;5D4ųzY:Շ<4)AaĪ&BŹj]k4EOUr? !PΑ0>MMη^dDs$%D8~F.u{H*)˜Xs, \ޏDJjZ dT]yěM0,?% Y ^CN9MмUPD&-(?-ZY'{^5sGqIN]VoK њ$ف-./u[Ҹ}"7%oO/n)$R$6tJLlƞԶ9bѥ7H0&yI3K[ij2RR|2%}DI`* ]I߸کMu΢3}=hTvOɜ0>BnEHNOfI68ZwFAs;JZܗ7^ d|iffNg0o;%s/>\mu_ a!D7/U[A$iRB/"h;鹳W3#i~nX) u+7bj\'qܲ/}3|hm0ۓ'@ λH-bqzֵqd R DaAtw]zxsB@5HaLCSސG[^(D%owX9{y@|VzlQ¡`V9–c.%4L(Fdqi RTSʘ]7;K\CmT)+[:V!f V;S` yS26Y/B|0dO +L_Ƒ v1{DV҅ƥ'o>9R{oB>Yzf]np PyIt͌l15"-N* :QV' w!0M(p@-ނK]ivHȋi"n ٙjAf>D3uU"ʇ6RLix|zߢpދt1X~ތ|A};ٷݽX>I%qY0}9/2I7ҠS#qQ|jtdC؝f? 07Uq2dF?Y>x/tGmdNBˁ>LB$9eOt~bm&| ny+mψ0~5GF c㭥p3YY"ުǵ/^=}RN-\8#Jho$ĽAPVڋo\dVA?C1/;:@"rܐkxz(r9&/ =h6`hYkU3W*QYxz6 "mYMpRC)f|Ws6 -cÁ}jheEO@ ;p3;ީ֚?M`RG ~ʹj0M h)4Ģga[C>q_LtxYc͘dn97m]gf!%lSB;04,vRz+گևx|qGd{X.TjT!L~JUȸ뗬v2KwA 5RpRQ|(ctt# }= sf d$*n5WƩGݤ+=23m}$ړO|6 }{*@3]vEĸ\-B+ UЋ_ntՍn;}mx8pWl @w77ctip^   J)g6[\d G( (Mxo?]>8k:Iͮ\~c0] wT%mH5U?NoGO~<0'EU yoN%vd VqD_r+ukOUJ ?JDY"Ւ뚶3졠X=*̧谣y,Yv .#5m:S wxSͧ'ŝ2jge 0͗?lUQ(G}\/&עC+bޭi^ϸ.Ǘ4f7?"kce|'bv:z?|@ qp&167bi1,7ka)aJ[rqNў 2bS"-.}D/9l}\Axwb@.Q{c2Q0@/ 4 %==7M*M6_d>y\ֳXJ1sx^H2g'긂Z-0Z>$I1c{S\Y0F;%-qNo"- TCۓ޹Jc@ȓPu.mi]gWZ詏Y2@irܰ\3#\hr~;Zh&hzEE+ (ݯY 1zE_1e7:4^"&^K,կMUxt4'>Fnr͜q$PB]f- +U+t#x#6#/5x\kL=Ľ,j, iybuGnA:EH)YH^Gu(-p#@i?BA+V'ɴÞC, 20H(%br>񀄾 Į|DlO&Q+KpO)m}b*J_G*K~.Sopw]0hQ9Ї0ȏ dJj S>1W,8C??e)=)6ɷ Xmhb$%p:`uȆ0Qy hKŋ|MQ8B}D6|O"<.:3p M'z5g#g ]ggroM[{g^KR{wQN_bh\ę:@+P&W,uj1lSv*'1UӮV3"GX3,'$@NBC=0/[3'O*oz|@^ 6ҵ+c*u?`Ե|X>ZHn `ӝ!@Cޫ8D5_ciQ[+ÌHʢ)e{Ti^Z7#v.jr~ؽ{zAkFpХ;|\/tGWmt3WKի>f|ٲ=OBq 篡||Ogׁ$i/iZ|ncR\]n5;tN =?@ pFDk Ѕ zkϽUM >h _CKѢ T14E빆V(&> G6SkJ%fZm|>5@6;,^68LO}$[h\@h͝BRX{A]7j#VD'o{^9)E0y l$p컀b4è ql^87Mzے8AnE AFYb(Bz˒}42{Q`uU6HOM@XDIGnuv#٪մsFҿr$l2}d AޑwqK߾'Z}@L T6J8oJU!Q8op գ5@#hʢWH]aHŴsƍ(3,$Te9R^!*yzA-Yfa'@# YztuF9u TN@G00#`Fi{FXL@&&+ՑTP4|{@a; d"mDGtM<6qw3, !ns0wKnE51%;JpgVܜ 9d6 \J'Ӹb'ֳD"9riD0YTԤAi)WL4+i=BR HRH kQce~)+f-$߁1, !'E#)C/ tF5$ ӫ2!MoZ@kJ'N`2oYeeYIBhZ6ƿ2L+;'6av5b\#b*_eEz~J~QK Xܲ?ٞ){jWSv9| Oh1nVOU&؇OkN L{gzX"C8@rH(1a?g̦Sٳ!W3);]fcgV@1.(k,>{\WXl>%x^6 V>Z A*:d|ۡ ۬ȦGA 5cX1v$Cg.H,lzá%r | 2b`)P95 7i\{i-zi>uj צ'㕌11Ԑk]2;.V5tbxs3>?p"d5r䢅0^IxWywI cȈDeA5=g^s\{YmD an1hY:oD(yhTħ1.r/ôf|X#ڡ d T^}}&e"@~ë N~ù$@5#zf=Faro PLl,`!e%ܗ3;; Hއ~&O5A$+T0+im! :] ww$£ݘ_V)2'`4Y4%[D04려-'cEnYgTB޾Sy$EʔF~eG 9 LD#m樚Gמcrvk=*'L D@ wxំtnX [<綄5E2S`>IM  Y9~ mһP;B{r.Q`//wf^EЍRϽ$~S=bUZ?/hڶ=_Kzu hPs4>Q x\ ZGb Yv#HOILʊ]@HDq~;vxOl*I*fWR7?tޡ8[BU0YʚHdJ~fM[3hZ-/GP v>VRazx#K_Sspmvi+িb& |sH?KO2}|{5,1xo\cD EBo2{ۄ셙~M+.jNXkMXqߝB*SƀD:Yb#\ً5{l)w~eLA/42cRpI\9Z}/Ճgy/= wB-蹮O})á2amh+q9~;W$Mg1z»'PM"8 Y`(}DPRh/WI~а"Z~EprZr)V[*&"w;cYhd#EqtՍ T׽ WX O+C=h걸ݶMEH^CXYtֳjvvǗD GͰ| ׾{Dkje A}Zu+‰Rm @MZXn QĝJ[hyT^^1ԑ"m,R@Zˠ*<:7!pKԻ>-!>  A"Vu@'a+iLh^_6-AiAӄ?E/L=d< SWs ";RĮ^RlEZUDBm<+"Kv/'Ed`ݤmF1jލTnȣLPV{BHh |im.BC;t8]~TV(JԠY2EUriEf-k( NDt|/H&jXlgz@k</R nC)_мsųH+4-@lқ>M\7c%obXu%'IkuqJ1HuuDA..)|+D+xqu@\_PO&0 Y{}Wn~Q%D_n$Jưhy} sfnq%qoہ;<[) + |*Y"3HWy4(_ BgxO+->SS4L'qպ3[$=U[6f '΁RM@nfw!b߁!ԁg-/lz8U|p}#Q>V-K1^"Gynst󰑺ۄi1&6(@j;zԾ1PU-#DRbĈeK%f;J`bXAnIzhbS1V0|~ke8ce_qqDqgԲn=p [Һ>5_z"k2vu jHCPIt6UU9B"r3r>w5Ѡ_v3Ϳ6Q~B!'Eki'U/l3>^s/B.`c05M?/|Ag`e O*4ލߘur| ANݖ@5ܱ'*͂bDŽ&Y3p蛊ǹ`8bV(KrOK1Q#4_a!uyΪgfK<>T ̈9 "v ؼw/cW!"5 ưQ򩻝I;f ]߭}!Ws)_.VV(A+*xtqxg[/MoL6㵦 7NSeEqH1_7t滺e;0Ǝi НsS0m]ȹƢ(T|OȐ6 @e-[$tƟfYeYu7O[Әih$T'7Y-bA h{aZĐ.d>I:^a|`g M ߀]tIaj|`dgq,GijZmRSq-Uݳu&ZMT"A}"о*T6K{=}n-Fc<5iR2} i2,5eŝ c 21;}ccl%YD&@7c#IHͭ>ʈSKw99e{ʟel~̭g#ҘuǤo읹קG-JɥthaR\#`OF YYO:c \v-8UWTORv4r['i=M<hd#?[:LiFC 12JX4N=~i=;/Dt>s= s'3=YՇr Ue@f &^$ 鵛@r9(_W#Ndi6aDkPE[RFPF;K,fMMW0tj 痙P/O #[\ŎGw uer!(bk;MigZ0Wu13dKL`vĺm.=yOK~t>w D Ѻ]u\ 9'UPhA4yihg6v BdcT]3?1y"R#0΃|X " "VaAKlk=sŻ` c4Xh?o ]9X1 / ;13љW/[?jI i_ Uy bCU {@Հ8ѽD&t'Zѫs̉!r,x[`tbOÞOmˀdl\`2 H rrS-2WvcXR)}&[A9h-*ZPx0Qf *k"BW|Yyi]I]N@:0N%(89<"F]W}{7jЩ vzօw1K;J[FuisGSI&V\%MW\7AR[]Г[upveGZe_R4d;B1W=ĸ&7s\0놃<ދou\K9*qó'"en/T?KmHo]UE%{'3dqY k &55Z13;]I/zG=x$ 4fFeόdyݨlJ(mv mS݇s.*^/Sޅ[kfOtr_ m5nzJЕ2H+|*Ԡ` F2̢PTNh>sk(Vе'~X߁3kHD4@ݢWy;7o@CPx.﫤"6Hh|@}B=;-=ynQ oh& D L5`z_[>j#^C*& 5>r,"b-9aʙbRQ&Bz1&Kv&Y.T{Bj,~G#\?m"845M#WQ6V>(Cz(et6ӯYt{@'ᶑGr6;b`Gˤ?)NyxtPz$ލފ8s¿kʏ'5YK 4.[W $A GE7֍~ KB/tJdq%G:L(y V'_xWl ׈ zN6Hemk}dz8ϱ` Y ?aW3aSe(A261%V\ 68r)7'(*~paDϸjY܇[U:e;]~ P㟉O;&AM/] x= VJ μ W'Ź3 "aBzcG "ܓPZ"az,}{\}>sk`)ʍȮ0O0KeLosl EV|žfaT_I[(ffRZZNP*q$E=}LWֽ #vvC(!V7q3Ɔԕ[x>KEʬ°Զ5D_| Dg{Y$Ƽ%BPp%rވ6TPK&aqwGNgքQ6>X8ċ`,!eG,N!ɾoH܍]C'g@X`RbW-c&f Pt rNJ꺕kf-KO(kPOFsfɖ-|"Z㥼pOƹ!I[ʺH׵MnweYީDe;J@t(>,~VbE6Rb["G Ŭl8դ6S8+wYKKV@ɬGrULby]\̯6uLY\xDB S#E3w,q1dC*$!Pv|-ղXk}j~G} [bJq#~oٮB']G"0XHICGL]Vu RiqnfOO{6.Wވs.S? VC}HK"IP ցÛL<oWgOUzpByB}E,ҍX % +.nTjݜ^+t**Rb$ó kAȯmP@=_;?'>šP?f4a0u#K;"?e}s TXm&%NunY7 74y-"[h&eA2W6MVV!Pk-.ejctiarEEf*A=2Чrj ?+-Ȩ]:@s.Vou.(_1>C馗ݗ#cd=lsaא=#rɜ nӂJ>},da:b{kAVD>%:j.5aX++{-OOKk,tK]( >sD=.$~_%ow{ PDx|r9Ee| &l3VvM-ʣdB0DCwptO\]VRp_@MX,ogܾ5hЛ܁H@&{]DQBPMIZ쮀jQ'Po22Ʈs?|ɊLˁEN@?<┎2QWH5W\$h ZPHW<.I>FaO- l}d8"^p͝EPve%uFK$XQ9|ZѮYFVmiڶpLX,ACv_-eV K~2pSHg):E]V ,TpC<PJB3ab|rTƃW̊"^lfA~cqv[`L^}`8[X vpz%|z9q/0Uzaӛ{8[8I.]X\\SF"^M@Lf2U㹍lhz k^rZQYԈCᣢ)D+[j3ۊB`1ږ(%qӻiU#KQdŧ5$ c!MŅ>X&qG~z7<fȘPwoǫ=Ac?PT`/6&@X`j+^i 0_:`-9n1 < O7@d{JmkK3a*2$kpeATxD;L8#/ :w@j낳źа?}_g?öeUfgHi0n!J uQ[a f v6b #Y`-x,&ƔzӜ\E Ov%| p?37RB .K#W}Z]44=¥1Y%BSPOtI@PXAM>#KK)WiA xЉVN1։c}+Jv^d3 K9U0܋۱='laL8ID'ҨI{"G8eGyH!R4Q~z_d\aSWVleUwZ[Ik9%$rA#oqTgE脌|N PLjoӑV{_r*3Yp:n`T:3O"g}AP 36]Sm{z2B*Ӏk;v>y+}p6{0 }a[Qk"XqUo‹@PI>?]\<CLx՚_#<|_5.x;_~TBlR2vS͵CNgWh4or^LkP`pm 2xPD\o]@Fٶ%sOv9؞UR9ڒ@ES?9OJ}UcaZXZ,/Z笟&|Kƻ@kKb#x"iolo5.tR|*=j e"8cz{e(^R$Nk{8~cx_L`UTQb.7sUm8k0Q|mm_򊙎0HczmDt[_9.`kvߨNY)ٙM%q&*]id{+Zm?2;c,8WII7STJ\_X.ڏdZRZowF&a?x6 h Q O %j5 > Ġ^7{B$1c!*wғ\, 7qv3>79VtP`C.v!>Q98˻5SHWN`$b;oR5wD~bf ]wZl|W N:f -THo_XRt82Cb>W8|OJ+6%X]y.(2hi@h^ %'80 䕪 8ⅅFp h1SC_F(4Vє %`@A;?fFu}ʷ*yjmcq2+Ҫ؊!FESɨi.5{l0AOK˻O)nھ_9t9E9\S5<ɲ ̀8<`c{ɿ&76*I鱢k+2pvm֛zK0UgUNЧ'ت*v ʤGa#|)^MO=K)] |y$q!˕r8)SHn[Dܪ5ؘ)eѳDbƢ|kƉ>6#9, s-IdԲ_?Y+~=m˨8ʶ2HQlcM([<ۭWaiW̤h>’cqv/!H;/B;#k!Ngg#@~l?m;{Ҩ:ށl$ӭyi"4]ʼncv)6XLsKr͢^NX_JL8e 3˙\Ýяر4 6#-t&h>ə&<`[|]-:\T؜l25pF@۝#5 nNĠe"̅ȞEs%/u]蛅ofY!Y!`%YTPd#0mfR/rEXSI4q=gح“s|@4ut>;y08\륢'v7N2KXa+u#9 *j]@IZ&oaaTˢmz6kIqebvچYA6,Pȓ2Hl@7[S 06ԑD=ApA4/^Y=A3uX贖[wϏ0qؕe}hu@@Z6yX9}F;/)r.`'k!QFؒ8 nGoj%A Gzya1jex|ҁf|g1KWv8s%k^X$6.][l?&\w=f$EpAGnA*ܺr;_,q_ddgoph D] >if^bd_>ы:T'9!RM aĬut܌9 ԕWRcNs#:g4yJo|nFDdĔlGH#A`ʢgȣ |eD@ ?HP=RBCD od"/3DUIc*鏓M*hxCfgXrjN%$B,aY˰n/Ci$ Y]:Oy0^4Vyó^ȩuKGkC ^c+^\INȟvR@+Nt@߉c"F$߷tj҃wVt jk.\ڞ3 q0d{v8X3 oo ْWy?`qʽ]S:#:U@.#_z ]R*s}5 DxF@qv΁\N"9ۄ gWA+ P`NZŇ`\L. $p_ яmAASOQ |Bf_:DvˠЛ9]N!Uf:.9g**$GVm: Z!?YB{ R87[uXaI\6E@QMt|G P4Tc"6O}Ý5DV` Λ ބ*dM%h=\5iNM79J $GLa=ް|iL5Z)RaXgDp ѹ!hY~`eJ#7؍+\+cn*? d`.HͿ *ǒ 0x Ü 2D z{s5$zUoAbJMP$8cnpՈ:+Rt6i[/zv׀}qzFq VL%1wZ *5Z` HmOƺG6GpK+x iWX{ ;cB.<NYG&0y+-y6&=KT^Kf7'NE)pT6t6Q( 1n__Tƈ74u tn3U6SQK]b(JJ|{Zlާv0.ǬܖYΔyӀM ٘]mJfaĩVcz`T6V,vvr[g*Um1';ytg3}ވ";W*x`_A*0=/&^jØPK0Ke-H{~>Nϥy / pZHO-CϙZ dc`-T%1=/|˅*L4khZ{%;i;KJvC)aOڐxFƣ<ӓ/ "JQG݌"pm-|ON&'"sILCȟWIRg̨Y|K7)OZ)ݝE2hYW4nȄHaw0mU"ؿ}zs&jW[A" "Uee` ȑ *m(d9/ -UÀTljv=X~YNd.qz561INqbb` iKCČl4u%}h? B]&Wt3Yȴ^DCbp|\mw0Q Q-"mSۀ /Ks< l!9ԋ{E5o3pA},ݸ]] Zwllkotd/ޫR11[fS>Ə! J#vFeo / iq܅ޱ)i bS~@F_X<*rҔ&M>^.?uyCgyΟ;MjP<6J~)>AnRsyj{8 E|˃5xy/:Wä2# PWnCh*03܊ ?4Ndˈy~*qH鹔6VmE]tnN\2*<;р ʂӨ*rկ>{[=-9\&벋㲻ZkgWOPey~eu?F~Tu*?](%^~;DsvUwy2G,/bn0/L,"3P[>v?'uP0'ކT'1gy%:H4c'}NiBCV,X:ߏ&>cfC5Xj=YZҖ Xޣڝ o`Rʨr; ?iٮJW`mK8t_Kz,jҌM v<`s--lm@xsmęUO;$7d;^A8Kҩ;,=ИA.p I @Lj^T~+0C6) ay1W$C0񋛯=)|Uҷwxv&ٞqrLhO!oUAr Gtܳ,o{ qLA;篚@V_*TЧUVVH& O* fm xտ!9j|M0`k :/~DgO!i'4ֿ~;z 1iLMnV@\T ~R_ჩi|ؚh.Q^2wmνdL $(43GCi/vr%XĔs`\Cuv|9|n8 S=]T,߯=LdpZCt*#l} /6q :{ラɁa/r$7 T,k#>THFD]f C M(P+~P2Vpﲋ voI%oQѱ|~c[e_ȂVw+9sN& i3 "KqV:Xk)b>=U v0` ȻVɅYWcz+Y/5xI-Hf dC1?EÓ(:nXr: S9,!xfSvj#6;.R;]נ2蕶"$6\ eQeS493&ƟV Qk?^[z2Y3ΦwqaTPj[ڜD&Uӟ>cm֞o[;T#ɾI/p;BdA~ظT׼QDHFn?u{&J=\ .K?Gd}*%8ǚ(C_HN³:eTJnʽQ:, n2Tzt:J_U o0͜n=gnD`@kUdUZ&GǙ9[U9O!Ƹ!(蒟5:8Xvt3KI7Q<Ѿ.I24yld ] <1 ߭ 7ƩPޤGjB (\ K$pX4+كab 0ר d !`KDnKOWObHu7^cl*LkyAnA\v|bEx`YXϡPfk.mU_B#nU_omAH=*E$)=Jx4c( :⁢9?Zܦol,}F3ou^5٦P+S Η@;1|B]3F;R^QNA{OVJTIze|Z϶QirR' cOhG[W|C&N3Z'/:]8]<_ZS 96?8xaƭmQٚX/&XdEξg Jh=5== 6Ȯq] yX&wx6Zh^0_!e򜯰TI~0G%}+F4: ur DK4<N؞g^z-(Ԣq )y TbR+ h[w3(G&"sxDe%ͨ8h)1ky|0_AA!h <=1_r}lYqRdE o1]61gJy<7 :4`15#- %(Kb8{0 UT*%~uJ>ԠzI*ƿ Rx==8qg6Dhӂ)K.3iRrBXȋDUڌsBecf~kFh(ۂ99샩~oDC)iʤAтl o<`n};̵yD0QF5Zc'+/#4!IZȔ? E_W,&(@|-+L y吧b@z\ρ5\WG3=UV_UW%C?'A!C^mC,h>"MM _]\@wpYwr:+u('$rʞH[!Zb!|tMRjH,r)H!AQhͥ:hF .M5!y[%C$Rqlb ,Yn 7%(`1(x'N׫ukh/϶sBKІ}0R6tl8H4öA5;*R~q_D=!g><COoBQ-HZEtPls!Z4%@ ҵ%7 뷿`.5]#XAAwvNJOWw7G Vjߜ84h-Owu1bE6 q ՚Ppp0'SJGߵܬw]"T#rQ5Z^mc&U*-BȪv(7A.5vZj awgZpB69Ơ@+{ ss,yvMWdmɬi$]L6425Y2@`a6QF/?3#TWKtcf^^MDR2;LZѠ;S;`*+a 8],o@u麴Y>zr_LXb}(6Hz ,&Ks9Zuq!62dsBnCVZ 3nJ[XX>Y p XrYb`K˞ Q r/,3Y1WLB7:ϴ4m5Z,Yku,'QbiLvNBK?!~Zʎ&<󱶼 / ѫDJ  [`gU5edim/>8:;D0e޸̦ubCkh~Lc:xWt*\I2 w#jR9VJmt_t?[,]hK' ɶxoX;ϙ?b.݄ [b6]̱F.ײMzTk-&kPvA7GhK5MD3K0ڮB`$TP }7bՃ ?A#Gx5_ yѴaiG"jPw$ioK]y* 0 Cun]caGME^GT{U:G@IХu^4cR38sر1<'1Kq~%078Y|cwMoXc]8 ?Q@!W0Wʟ@x/Mz%m(|jOpjC 6w#"*"ٵ f-+ F{[l/ gWJ ?jڷO2cfSKLD/\rk-͌/s|~9naªHJ+S_%ƴ.[sUnIt}o}/PO_u 9p|W&g!լY6Q ? #2 X0h `o#I$႗H!X%RU]i/)] 2.Xn(-}X ^l&nd? |5YC3˶v8a oQ{ܓ:IҮbBoYU]:*K'rLLǤ)~]dfPښB%tȀaw{Czhܡ؋97 Uio# #+S`T:X(֦/LG>vJ;}ʜ ՟Vt7u4)1R8/q|f501Gq!=ݡnqF| ӹ?(AHRc?p%\/@I-&>BRԿ{tAr3 ~j\euJlb^'gLә/=A*vlsbҩfeئRUHel[eWpc@o^ @=MU,`N,`Hs YMzU6M,Noa|yI2!QW̅gYjbP~e}۳:PlV14heڪ6n{CJ7O_,_U{&#wY,Sa5Q ھ{w._PQPkW!n$ Ke̻.?H`d RV#2F :>94k@&_F$#"ąT6>1sY.HH} K7趠!G}"bC/xA~ڢ3B`e\NZUOsW$9.m {a Uߕf\:udALY1CpNJ==ƉF.;GbW8Ϩ^4L>5`ZCΟ"Ĝܗ;Y l߯r D5S tt@ "em }Q3D+8FȘXG5D0{p(2~~'nrm k&:f1-Qp43fX(DJ潏:bx1 ,zVM\K9_[L/>}g:K!7g9qJ\ }Usҩ\9s] QWxxWQ@Pd3ah*7|ZE`Sf WxZ}#y[V"]LӍ%sEV͡}n~(^{۞"4-['tXD7?mjV_\_-~uP5DlgK{^HyINzyY@[|=y|ɮaŶ4oXM,AᶛkcS>NGlqoLڻ%i}\v0SífOct}%9trAS?P93bfj.ZcI 6!,t,F;'m3ݜ)e|_Ϡe9YrG'(v'j~X_NRynҝG`fϼI5L6GLXBuSbF[7 ިnx@O9PgϥO;[x!d^Nnb_WFP{FЕoIN1~ט1DtWJ4uBu}I1'{o)x*$3衉؅C=0}p{psmq3)% m PA;ʭ{z(I΀ݏH6_Oa㢍ŵ?2@g%26ڛa݅Yhj)gaC}|}# /+m,Yvb8%@f!fz?gˢ9M#gKA%Q&rwpEUowD] %W4)i_NsN12M sf_1+Q:km/o V$ ?3FN?6saAClI8u! 4~@}ysg/1r]VȣɃ`c۝h} |K> ! ;)Qۭܭv[4(] 0m&qj /~=G]i;pJas/ܜ/K8lEZݛն0պG"zu[HSz:ww˽}g~`sydp$:IMzL%gPN&DA>q?媳AReQ冧;3ˏV@V^ txA?0V(O=\}jd(zp4\' 'e|DLC/(G?؀;M I>[@а1LF\A2<)i %D^ҁ*z}5'-虤N2J6ߤv ۄbYymJ\=2w3TK3Ra:# XUVF 鱺1 s9ULWCOb i^pnr{7:<$|jӅ!w.B0H-2_m p}e|>[0HVB֘5+ܚa(bj[|f (;E8篻ţ綬![0AL>ya8SiSvKD H/NW%oa[h4sN+^&q@}vDߑ94KVP(THlPOVn%G WqS#lUѕ[OұG q{*2}~,$C2r:|.=勄a⥳FSU "aLnΝpܽ2`tV 0ݧh8~"gkfˬg!KbN7~P-^O+p|['xp=:=SB-Z}梯pZ8v)/CkHTWX\a/WbA ."+:fK !rOſ8HlW8&w /Latօ{|I 9+{%}r[Bsht  ˜5v?`cִx%%,hT(O@jJ~ 'zWOG~pniyi8a}_2)8@_ _ ]p[,Kh*0ʀQ%Tms/|Wf3:n ի՟ge0mIX4S%%[(xL!y%$ _B?Xm I/J:uqv=Pw-e{x~Uؽ=ZۊP.Y0LG,adH<8w`KdR"rԵ́׍25RB%}i#t_"ށ-兩#= єTGQF=K{ωSF?l[4A xYȃwQ C6|$FH<]aW%PZ_Qeפ˘0ܐEln?2!HrȨH4~A\PkH'Q(e[ eNƧ(_4\#S`ܨhȗkb%2%^`)Z^u9ZT^U+Dm|Y! MTaڹU{5Wi56+h#WH½:BS¹ֺb(yWyI{<ytVHy_MS~El"4[ݻ@OHM}*TXn͠G ALΫ(LlN}?b,4_(țԧ臬XXUhV᱀Bn= V ab\-u\LRuMP$ݕڄLQ "emcYsRR L^SFCMeYT.h\>VL5fTY96d4XEY,9 / zMt<`u 81'\Mhu$R <0 q2vZzFhsi(l7UjĆv:o]z*%B}۽FT2c{{:׼e{O"w{FL i$Zk?2p5Z-lˮxȃ>$_↕u4|4wա"IweOC@qN, UB}U%6l:PyIw̱ov[ao7Am2%,Q0?i(m|n8ՎQ[p=4Cpڟ= 0z 4XE.6fU §麂]Jv&O }_Nx/PQ $ P*' A<3Qϭ s+ ?1Dd왮\=悴R:AMڟp".fSU &Hx*pe~VW[9WY1,h)+Ɛ>VhV8zb,=q-X)Ύ-N9_{soi.+L4U`+z6/&s ] 4Cgi,\:z|vԭAfp|DGBaQH3Q\nV_\+@\m&f[17S.bYvRcE})4s6 R^!“<$Z2d ͋p.Ƣ76e!Y+!N&B&9rCͬF6>" yK >X(4 ^fiHu-N=D@CLc OgS5CɌ*`wW; RV(vKa h!PM|{$UŵG{kj pY7'Y{e똼ޛ*f'F!Bw]?]M}>[iK6OpNG\#Ӑҏ >f:!xA)~˜1ݝU*|2s?ky8V/زkjឳ#s0]O V5;h8uNꟈHx޽¨;"`4dO0Xnj="7MDϏO 0I\8U|K >7BmTn[0 !(S]#~-oWƟ8١gOK ;:~ARfd Ӷb0?3GH]GuyWS`15gh!kso:\xi9މX 2.IA&򩱃EZ-9g! ZIKJg?6kP|sQ*2]\ӠL kiJD&k'o,j'yARu܁˙1=0- )݋c>ÀcwK6 E'a\fl2-#HꅲbNkJPNzÏCi)8R-}lƴ^JeF]T鈳4Erၜ}՘X۹mxxp)-Nj:%{=ʗV ]y ErtV߽h.r"|OڳpK,LZ=jW(HUP]j쳰 [XC 8 6Q"IƠUDϺ@uo1!^NK,7NIht+ׇO vB d Gf1A#q*#%EmZa q\S}c[ rg_xϹR mTHFhܟ m)$ȼ̫TV;P(]l?;)<#>PIE6v|[2_8 B!Nx"]S -=-(KS@ !gM(uZ{q5.| mN6tb Vf;f! Ltc.# baޡ`we3м"6JxݦTrDXe%Oth=f} (8j[k;Gn$Zy܋uJw!/0 :&SNbwKܹw٨%0͠qpuL\cBԥC`QJL ]ݢ5Te߆YmJ,vc0Si~YY82vq곽96.:p}F@ȀY(A ?Kӹ>zuR$\, 9t˔i C7Ktc1ηΛȕ0o)`C÷nxt0v1WN_=Ps~H_}+rPb6FI|1[:>ñ&$͍Z-Yh$yvtX5&ruKɇ2/@U'h$ZLΝ1O26[8x~:(˺lImHyG\{xz Ei HdCu|M9٣:+sj3kDWlr0]@О%M'G#T9-̭a݇u2 ң ]e[q BhWlmF970~SiI[;6/c3Ӟ,T7MˌqEN dX$B0 A/N[yLxQ0w p1yCs54}e=+x]%ZdvnS^|{ӷ ZN(.1D% 5 HmJ'=Guٯ}'xƇsF=k!AN0kyFPnZRbeMP:L7N)|཰[bI%RVYYlx6%O]f6qTQ+2L6qkZ_hH+ťv /bEaF5,7,Sivޖz?}U 6,B˰\Wk{bDR5Sa;' jɉ!kED+d`'mI?Y w,Q6TͰd| H~-Ԍ2&@;~[\,L!I;:"ڞJ +;^ 9c(h(DFtWT7zAN /h^U$| _ ܏UYgsj ^9!yƍ,UϡCa5Q&iBEAly"ޞCr/zǐ"Wk_z7R7am"xWwV]5pZތ(:amF>̽3;S\1a\;{4 7* uanۭ_s#,%`IaxoG8]˦ީC Fjn09)Yʢ $8^68OΫHZ^3j')mHSqZH ,_&UnTɓp쎓(fznPyNO<42ԢҽH%˗x츀#5/uo`vj "qww,1tjE!G W^֡ɵ[FBU5ڵFivA ⃓Qok Vz:߿M : cߊcAtq;lV!ccj@K(a^N iU1T@Mp]Jl!dwcMN0ϮƋflM*ĖVFI=O wAhaC\c ny|JThfn n :(]Va HXD%Ě>bq?gȊ%ɊXE4U|*J[r{^?[OOrk_o?}^Xe&uhj}*>;tKA)|}Mi÷]L+@jshR=#YG&iq#.1vJT1 HJu ^6-9C-ubg6T=i|2F/1}HR=Zo鸓.+໓(( RNtQ%#,J%]'.QtxAS)ubY /؜/MfU] 'G1>QPsiZj2$ʛH&H-i<в?z'@n#1dHdP.wQkeNxgŔj>F(V(={CNThQ0gѵyj]_y3qL!%"alF|K!Ay:Z2!ݣյꇨ6hhuDN#y]Uyyږ7㔧7egV0ZWgZGLz*-‡gXFB"? +c[ʰF3u 3y}z+I" 0rbYϛXmi%?oۃh4djFYg;14|r 7 㡆f'\BWUU_OM-w)Gdey]FXEkL&;E;%Lpf-r[.%%•KiA_-fҬ0nfH_M=_Uu;2)W-E;S\Ys7ǕE7i)">a4yLw^ BkۨҲ9u%E8o HA ֎Ǡ/~'=Kx`s#$1:pFanݿTY1IGqA {/g-.tFmz07AMҗ'>Qľ͚ZD9F ~Sb FgOG50sU}8L1ǥ?) d'ߨf=ʧc`r-KZ͈Z'vjM[SWHyCChr&%Qw4ɬϋ:FQT>Ku#XRoU7nN'!< tz†rF||5QwLEGkȢj)+mYoӀ+5dΣ_[2o_}V6ϴX_O(W$<ʱU,ry&0>mn/D)=K5. KU!q/)110'ljf-h I۱ h,VZ!j& ϴ†1&f*GGw{VcYjƔOCw]U8_["סm %@5^g%(4jPDWbj oH, UO ߪ$(6|exMs[;hwN [Zy温 m"a:ϣL l%qzLn{Mt1ÐnY3߬}zws@Adf|{n?;|:[@å\mqsjJ=,_*zI ƌSg^HHgSUA8 [zvnD=iXq;bKk<"Pm$u-c ~&79TThnIL͏q"-d1Oۜ xQ{2>Ĺ {9ݚ' D٨oo7$Hz@ c{oYfS/cn @Ppch -)46Ϛ}&$?En{8 k3q+⋾`ذΓIҁ.=3,%-_SLɎkÞ"v&'sN9wաBeY >;2;o*FAwK(CAzE3 I)w~g`?y]^jCue%'Œv,D#(t9v-89c N^_ ]kF,)rKz9# =w2o~c Z Cj=_s7怫1,{^ sP}' w*V¼$`=GQAv'w3 2"bM> d~~*X*@m_ ` f!")f2L y瞜c=9xjy ]B_ n &+{8̰kR {|QS9{PP'G%[rkAHۯa(QSC\7?$y Pߦ@EO(ߪ8ؼa92Z,F[ykIDiڼ>Nw< X,J\,cu"P|b͂L0ԆZco5q$!,f8n#L)h YeK>ޠfAIs:.瓕7YeT\^JGӱ c*;erږg{D(9+-MX{Էje9#`(뀗3љҤ;%޸FX6tO ^1#ȣ!@}g\"L tn mO$ [Bf;!5Sc5#YpW[%>fݭo-% wor^%ĔGTNx>c: ݑ|xߌkqF/ϝ.sE'feQVaEj|B?Bo},x"~'Y~֭ )c+7Tu7o%le.@mqXx[2{WMe ƫ%'29 ^qVZln<{@2x\G/P`K޴l!g/RT-hC6}Zy@Ii`RoD!l">JITΐM<Cq LG4I toa ,`?כIMwu˜lߡb=k7 Ķv`T1&ށI~j)l2 Nmm u3pN\ 6%L[rMEK3Յt: D5+'OX.q)qWQ.&[A }T:hhhE֮N .܌XO eE3j xd"# RtOTZ0]oxls}"rtID@Cd"WMF9[FL[-h:4orr>f2نeHKw X@/w=ιV˞%||lv$ʝބ[Yf X1) Ōґ=~5Kvgv>\<:Pҥ<@μPA",kd4?,Vy9F⻇Ty3%>,o*m ȮGmu?lƬ4K;wk4#8_,?Oht|ٗU|ÅmIˉgMtOuhK~ӽU0czy!I)P,։wZ.>`h[ ) V\1۩cvg=rU\•&jآg;Ukm XA7w,^P92+1":ij`H5pI@-爰HKTrU~<4ѧ]AW>k|E(\XoU֒UIPZ5_Xj~n۵iy#ʹ2G_Ƙ/蠨C$Mqߝ0"0Qw7Vh]Xr"C5C%&4ؖ:pܥ6P~~7-Vh . hde=I! 2+kJ`i L5u2M?~etGKRԎ͚lϦ8gtS+y6(OxcVu+gnw;I2D} k \@%BLދOI0̮%5)AC_ɸH v#'YT$1`c?@n4(_mxqx0zꌛtNK}t6 WZvtk\ժvתh?s/⇜&3K~S[RU&Zg߮cÁ3.Id_I3V~9"!h!߯-D:?`# PhU<<l)lAhAY?0Qq K1O3`0hM%.DhZ6~A&&KbN( wr hu苰^)VBT^vM;?as 2ubfߞ.vVkN5xX"q-x`G>bIDً3jS1nV/rbK(MU?|2&NW⭻'[C;SS6f]7X  p ~#tx2db=bN?Ѫ"ف \b٬B}qsp6h2}_N8N?< fM>Eݴy #mRFtawo7LGg: gʉ'n /w0andep_^W9ói}JҶd, 6`B\czSOq}[\ #-~SHeMADy*} PbnҜMBWcP<ӓZVK(`0T_`fu՛a[ P"SKG YPL舁rI:tnXR ?}]TAC>";7ނ{zlzZ{Z )(Sgd{vÛjR`Ұ4/kEtIa{ջLbDulkK$gSth"^'au,0Z7R6]0D+H%SLcg[{gCGPt~Ϲ5ޤ.KvNVʳNZJM"#Co+R$w?1A -ղ49W$Hb[1ҡWKP+ZL Svj!uB[VxY'mR;IKKojJL \yI]K^;cI"[=;5h6E9 eW|%uĪp3AaO &-t !((zChR}hUJ#=^PJ+oOlinX=eS5] H_mzǙSK} @f䥵bR6p|w8 ٦q4fT&@C9A:I5C-!Ҋ"'ë\jf;- S~ E^gkA3 ᎞43x62f(znU˃U'yi_QMA1a<6̀sVŕR;ejdnTr)}7i+1bϔS]ͯfaz>_.+OVҖK 4q Hf{̘ aZp+)yR ̕˷\Aȹ+$> r6(wA &[a ls$BٸⲐ=G([l/h8Q#ng<[Ԏҍ 1U"Qv rI9K-(W\&O NEPmZ=_:*2%؏cNSf6:([RIMÉtrx{0 5@7ǾI[zSH?/g~$P!(ڰr'xzf>&IY]8Vkö;DzRV.\cecx@H0X瑡AsS ;hSo@7۾MH5f3`"4ɠx{wL@!̮h0%;=(RZbAM7w۫ cZ=v#&R9ҧ=Txnp/7f g[!?Xq &U6W`fh[Zݕqa&B0|ʿiU?d /aX!GQ>8`;v޷53|-̨Ғs֣(hR nξMThk5; 8kpkqö f+fj *i*0BS3~Xekq׵B>ߥ\;f 8Ȓ8ZmD)M`_zSC?7~8q.>32d@<#:[{N^1,E.qqx܉8}-P+~4|f"wEoeU8~}(4&vӅbW\J&ZeǷjD{V) UiEG^'7F*~'D%jv/!'<=l q R!<~Z߯^|wT3܋>B`r_=Bdh1c#cɾBan5Ԇ٢B%rwrɸy\(NDmarQNG-RhSLT[̐ cs{\k(Ijw6pu&34RDzmz} Nw4&ex71ZXJFr(4fZK)vm3cx-fOgurqiptҡ-[Rш^54, ZI%3z@[_5ztp}SB)Ӻ+,%,F=E7ŧ~DQ!#Ih•yB|%"%@ř6n;qW'U@ggy}]i:S&BD5d`˷j͉nT$⊍b o2a8&qxD`G#H4eGօ5Oso,gOUKp r48?G! EWj/Q^P` *Q0(/SOzF?_.thhL/OápyoHm8&.Yٟ+)3O{}8mIS.c5ZC+UTk*"G5}--^P>c1? I;o{GuBI2>XLKюTixI +3 fMbبFu8Nz,6& FC13//"eKgm?616BA})gK,!NpK]T(1ϰ4vX\2VMH͂3\㟮^wdlss"eP [p̑ڮJ P=$Li:OsD**'JnZj *8Wz%ŀ1ݱ¢  Wp-nd?rX<Uh ~øZ֎4qnQX+L`B2j,SNd̢=FB;}η3(Iܕ'']-[3:>dS \u/4&̻ 71|g Tt[Dβ_nQp9۟SO7eTs"RN)Uͨ/ګ%^sfX|WP Hm-oҮz? CR4nǢ3%'vmd##fcZBֵ#~O&<|Z< etX,LVѮd3?}ez&aΕ5sLBE2}67t:NMW  TWJOL:*ĔMYl8Ϳ =fkwyXZ«>f$6,Ѳ$yh PBjaU^.5(p"aCyWFFŌ%U̥Y?dZUL8Lli6Nu& w61l?p.2{.2/cY*m>$MxRbr#fpkF"4DfCu+?E2rdY׊uN% ];;;l7wCШg# Yq!,FVRYd3Ū8l#vڣ,7RYh6hb B>y7r6eM7mVd5Y{3Eq3C ^v=}UBcu翟Ȕ蜹s5=C549?K_GtDCdVpȼdeG"bݾ#2 q &5>!] X7dj֡j_؋(r,]b,F g\Vf[8.> +"fNIzcsf 9ޤ?q7~u?&hQQ^AnM`5:EA 2Wn_H #b@a*a$ ]5(y:t ッwY(3 ||iw 9G/b/Eq7>3L,4ݿOs Q&YWx2Ah4"nOԟJ|p9:*Ŵ(*Ԫv٪#n&GQ߆ө*%I4z"`c`I܇D0ms@ qS;9s}_W[tI\(S9JNAg63NkSw?%/ҡE{\eÕX&8 /[@-EJ{Ϟo5Ƭg&J$cpTzQ}Q+zʼa@{Ir7L*4vx_H.ed t1B[h9)YkK>EZdanhDބX\lRJye@/ѐc/[+^b!7#َ"O$II.k["N} fo-UV̄`A`%n4&=&l//#+ղZ7v 1MWb e/b7*mvi3)"k)U8 1xAԷ9$24/wDE\ s |~,H B~3UuXğ7 נ-Wa+9ʐO@wֵB'^ڙ-Zn7`&*q|`*a~XdH^c(*;[`#I?02&m44@v c ɜU,&FM 8] kuHC{"|+c"3j2[Iƺ~ԧMU.)O zunvS5m; 50{Q8,=Rq Ob 8Ju-ْn-1jh1ϻ1mϬ$y0 vv04Κ<~DRj8Z!ZB96^٩.cyYPbQ33?D ft=gY]1% > %j̒G ʅ"\/㭼d()kgzچ^JnN 3/̪v}kOQFc$"ZhnЛa*}8sk)jB?= )+ {_Q|i͓@YX_32p6  -ݖOH\qTBm?32ѩQY8h; 5ZsiuElp/5}r tF&퐯M .A9m]1{Q^F0 ی i>Юq*enjʧGxg1r>>CW$WuZՙttSLp QDG`\c|G=cXSā{H$_ p^ uUf`}YWqB܃gxub)w*tw"|N'Wy\).EBMI/NlYPjn+_ZM$>F$ 8Ԕ'  aVnnj~@eK96IRx!)2@y=#pvTD (H"WʹOUjF \#ƵV[i_ǝ;C|ZXi"o/ )ҔKd6aķ]WE%ВK,IIGU=BqK'&1-W[ &//SZ(@;k;1RDU%ހBN(F3K~@2.i e8BJk'C}|IQo#|fڠ{$ٝ ')ZW_2C܀.#H!R{i(sss ޺P`܈ N:LzSW"?M-R3r; w2* !(e ?KI AER &M?Ү @s ɩ3<]`'A*>}O:^y}6hk35wd$p|u7z r"r-J@kh{n>/& RN9Am;^bOj8Mސ FGSFyҴ환/ @ "WqRO+v[$f_[;$vĺPI,rr"Gz}*(B*~S2+ hS]pcs?4Q1(exoFReZ\.V5ҡ%$T ^$D8_Z(vݟE j(?02b)If ʎ`>0d9՘]qNQLqy>'nY>sF2"ǻwR$GE#UWB6{N܎T7s*8}үXՕ_LLxiG2R K;pʽ,ΧLON˛%mkt E",9hP)c*hagڂ\Vd89hﱫ4[?_٬co=}B}>)rߞlD LeYjasɏrtj:(eExG涠l\$wi ~Vu` ֈeKQ@VN ~ENB>k.X }stEyn}+.{ 6 Q3 bm>y3zol\k๙yJs4 0ՓPM|ym|廑whH||񓵴/\L牲AzH[wӌZL~'ݓso;US|X5|63s,o޻bl0lh@<Ž`bDÚveo^^o,O^gS-R>k/uIJĮu,Bvxdè--p)ܼC&Gȼ kg+(Y]Pmpx2?T=kP2Ⱥ3ho0WIBlKRytmAB^m{P !sҿ?"g4Ui]ѮHo%w *+KD&S|h2Cmt6N扼$ӗJ)eT;"79,zw37zɮ{'m\LA?S98~0q5Ɍ'$ ~;W4#\$z,:YY? WOTjHKHX[ ".)>Zቿtkgvn)qƖ6aK_fu9 / !5ͦY>x5(f3i Fcb6 *~jۣYǴP+?H{oIv(H̸<*$l SgŏF{gS $CSU5D3~%#AVЧ_F;}٣cH<&Dt͙~+BJ9O {sS,*dvb}wjhdD2k w \kVoN|fmUdWШ(EI Tґ{7i8P)U'Yj4) Lfۇqe@3m̯ V?or,45ojv󉄘,ƙ;*5l"G |!58 ; ^ d{$e>;;_ _Aa'\“lsM_5+o!Vd!z'OZ3 HC766O :Wͩa"$*^^Lͮ(Wz5Zo7D~|h̏uyHz!_FzĭS_` ^Odܚ\;)MO +ldutF$hk2_d>?<5DH@ѧ5HDZ};il"{(O^5{E\!%㼬9і#E5za$r{AL9dm\_@'D'?J5?rc7γk0j3*$2,+G Mˬ)v|NG!4 a;JCNc /6h$c.u;D< :fؼ3_id\؟p@Qe* Җk7ꡏ 9K>;Jߊ^Vjq5ͷslVdHo|T/PeE}):Qx׶KǪcʊ,eץ-$ ~yYn@ԌYad `3ӳ#x' 7dk1E!tͫΎעDfH/d_6L!һ6%3Wtbj&[%-Fz)͠6?$1S!Lm'K ߨhG@'jMU]"/4d( q)_.y%no&BN{. (Δ׿]"}~eQ.NO#;R 8:zCzOO"A2َ" :=ePނGr0C(Dب^;|~Pw(Cpdއr?PXziLef9vtίKY.Eewi&/HA zխIlLNרc-{-|f?Qrˣad<qo莠$ripp IR}e!ٟ,qVض1Z)",!0%6ԟˬ̈́[`g汓@WƝOMx?IM.Ḵj! rVm_WĴ:#WJM`uE̿>Jis#Vi ~u}52rc"3X ΧYeuCD!~OܙaM9]3j -ӑ.-ґurv, lEwm5B<~U-g K ZV~mX 7D(+ yCJ7̎4mk-n1K*lBc{q KOqjZ0oCt]"|sA2^K~a:*<(Q-y<،~ev xƆ"'zNi?zƉlY)m!1Ǻl2Dxj>h˾+ZvakEOpɡ)]h`+" q_kyKZLP"^읈 6dj[aa5qQm#a)^fY+$f% s1A)24/YK4+m7 +t宽\|(, 2k@0`Z֋"otkjreD3B$}6C LާA@h#WoaH>c6e fYL늨Kk󦤟֮;|8A v`q^WLi5l")I}S}xT;_BՔZ3K:,4h1}AbvYxtybzaJ|t@\.>1W8EҴmLZ5FW|n7'`)Hx>܁7֖08$&i߅r#BFUa2oawCyaUGp L} Ӈ?*\KioG=8Q'] a 7{DI!q37jaV=)Cqs0G3GJ`{rI%[%h(icJߪ7קhQS"ɾ;OX۝h&m /hU~0I9jr(asg2/ЃԥTL6ח|aܥ~i2IH"06H6!wH[+ZD!N}ڟ(i,4nxֺ8Okvcƹ, L~/G>Q~9P gxW8m [sDV׏P׀44ZS銁Wi56 m-f܉`Cg Q5lAฏl5PWi ?«ո[C! 4FUZɢh>I2dWjI\j ǫЎ.Qx1׻R⩃%HQ1)SKmnFu|_ŴeViw/5Q7'p"BN*)lɒkj㡠*܆Y2Ϊ^>f%\gmgN❘I%v~\|D{p\Mރu,(i9e8˔]V)hoVlFrJ0l48eB7`bzNX+;*eQr&,nZM4ˇl^ŪY@7vkfƷ'yY ]mK-#w7߸l xQr|AVy:Č{{Iu\1i 8 . QrJډ_E*M%jrS;F鍖|tƦh"l/'k亳%0V4L9XAQ5lA"|7t.0bm( 2յ*_۳6ԶSg#2 YuLf@{`<$6'tlF&YOnY}+_F18ztL!P17mNbڻv-~->塭iBϽVj&Teu2atQWaS1]]lGʬJŁQ&g,3=_Ktp:9jr^,R`>K۟ W7桬D Vr1 3@lN&чq@ nZ7C1x1N>zѮDo@A{R{_SN%ڡږ|$U [A ~sfSY蕩&ߖ8Gdu?Ǡa2^rIy4!e0Z) դo= XN&"vcl,fVe=`SS 97ϗ]ڂɟ*MSKo9L䀻P0;ns_oXwGtx&HD\(otܞ߽L$DɘcNp]oADyG.5mʖZY*33-$(&|wvr:DǏ.@ѵokx閟 nDa|- .۵v/1WoG=hMcS87_X#wU4A|¿c19{>7(&$WX KAWmxCTv(א_GOd'R@<]ď9z9Z 5O@; :SGP@4ubjyih*0}ᘝfK3:}J79Y{ĉz-(eAl'$FDOU_BW·|3kTK?>K '_rm*-= N"%>*N$DCݣ|+'~眠WAFfW]½j_Wwn*_Z<Kj0o|E;qJ#`ZxOPAI9֟S7Uw!7,A6&/0`ߎ$#UC[*M/oW8(9!{90G_1E_b"1Kp j br;}kKZ"R$Xػ,JK@WLJޙqt!q%gy,BrčH1KZlSU@޸a"RU2sΚXYDUn49QF.QvҔx^TQj]Yf؍il'ƺrXCou'۝%<@܄O6Fp.Ԁjj&Hk%_>tJJޏqktK%ԕs^Q_?y-N;``8)1Y`j %\+e>#Bgは Ll 6x) J$mzeʈ*ܜ"zXx2/hSZ_+Zy9SXz(:Zm.{" AݫEno`%CrU.KLkc˪&<L Bvz[H f9>n^iq.KVqYcTұdV.~s+x}+:zmlnM w&flCBfY˱Lg(LqMZð:@LQ~ X'=UMv@ލtFjm/UbA$+?ZQ : 8\_s;Oh߂iaW>w݂Au:` N1B/D/3ohْJx:5CY^u[X;`D]ZOH#PԌn٬3QStѲ()\Jiv뉙?:$E-p{ֽin3Dx]=I׺Pޮn!nҪ$j'%ANVLzdàٸ?ǫ'3$O%ߘ3?L*a )gkqQ(AjvºGh=X["nXƷ}h _Óx/ʼnӞ0Ta|2Htj"0pKss{6x@0Z8zwʖfK):@ehO![W3x Ye/-6̢ wlniTd[h&˚ &x;|fp0F]+P]S'B%yCd+&Rթ*m)W.U%Be}${^6c`K(5UY d)ռ)QYݼ\Y" r_`b n0KpQyА`$qDdp_= ]9Q0|1r>q qLE`BU yXUR&%IGOBXТsgjM;8b c0Q**$Q>O/'VdfvCeFzbFGVb6I2)az;@~qpRXqc |MU\Lb/`"W!l0Ǟ˫ M3=P k, R*L+0tW2ϤR|9ll3B iy,ṱn^vA &q0j|!gAoWR"aJ>;$.ՅVy _{yceNe{X}dMFT~Y1jé9 trY Mm Cto&|. "ͺ%eE\p&dnbV);oB_?yS%J8'ΪYgw ! }h "o vpA5Ly,ཀ XlxWC!~)°%\$e-$`y!Ua}˦ ơW۪D{J-/!AHʖWluoZEn%Pčmq3B9Ԑj_}ӈ ;Sok0T?;"Jy%d:6ǤJH)~DΆ/m?$p4G74J&,8~g/% KpHw^XAFtVQq.Ǥ(`8 XRT9Pj!Ɉ Xuޫ^O{s{ Y>}5+,(c(vbi/X:JZm!bbK}^3uo? /^~)JAbxTۏ6خ+jgq![,ٓHwÀ+zVt/Q=ZA&өfht {â"%; h*M0RyVJ}7Ut{qrn<["{5.?g u?)aGo!>Z&ְڵDdb]L,\5sU}δ*딋8ɮZ)ګ[{*V/ vXwa_P9 եܼ['WZƃ܀ X0!Rym/ >mı4 sj4鋔zw'43=qTQN;$ )a|Yt&Nɋn`%BNmc2t[E*!j89l@ ΍z+-W B(NΗb(M*}Y$b| @9vk.ѦTخ*1d6ToU0^21-&eq' .2R2SH!{IghgBTU0\,6O '`F(l/P_Wl$b4AdE0J`x5%e\Uw@:_xoZ8ӧBoEVH9B/Ry)4$6|u-=,c+:8 uT?PM+x"B헆x"׌c̨NډcsCk) \9Q;1UiDAfHKBTQA &L(Rܠ]ht!ht">v?^<th5kl+wڛߡ @=} a2O1lL* }dWV}ff-tާ4b2 U+r=QN3#*L,%ak.0$UI UYlyF]SCLwYSM!޾9I%>r* AQB|8tq'܀XhoI}S@/wd*%F{%%S4-B3AAwvZEΰ5SRNdvL`O Hϵh`ehKyYXe<sh06ߤku7D]KϟHڊuvVK]q6T 7u]<(M,j&' UIzaNJiC.1+)LnȌSjK1^.hKVkyXnW?{+&6#z;/[/IT0ѩ ·dǐit&Pổxb(w{^n/=w$ <~cFtsx&w `rAMspkq6,R[yតHioEw,T , mS awl?ᩐ<)(\0r͈ b =GC>i -A@xCva8J! (<ӽ6"Lg)w*}aez#siLXc}Ȫ>Њ%1߯A=Ly õ#챻_D_0kZdě~9ɯ-}(%T| _] %xAdӵ_X,ZmWWlMcMR8hGҋmgS,!k,U7:P 1r%+&}/Ӓ] laS3 ݊=h`\ !BV:D 5$߷J6ӷHzho^5GxqKASɺVa:,$ɖ(_JRo# 7@8&c;Iz2֕tD*w^ /Y7&˗~abOǾ@o0>Mu -7RM+Cpoff<˦H I"<\4a hJMO7%3J+Yolh5O++vM {ݻ2rX h^c9[}"ˆ{ʊqO 0xAt&VZs /HKlTn4rB/$Y>3 ~5z&Qn귐v p\PN<#քV(-%(sRt~^ 7xy%PiOx6өK(BH|YyD~P)uPNY4B]0. %)zQ7' UPWVDG>iowj{|tVkG[v4į   Վng4#Wՙ#k"3+4FsӮڊrB*]NO $"zDS =-cjEpW,ƅ%[/9 HL5?pkLbs)a|}DyEBͩyQ)=aCBjTQxIQy9o+XƗ6iuB_ s AZ.W+Ǣ{٬f2K*H @֗\LUɣ#@o$j@idbLf|)hDB PU"oiqK]jެxdyB'ÅܹXh3!k9&% Wxmz2Х;#i1pze02$,-pm͔nQi9We&zf߃k_m2(eH<($u0Y5”kcsUkT`˾+ZCu&*MaA+ǫ:>-,֧9l$+lSf{aef320PƔ<$yB1t@9p0MllGPܿY+o2e$ΉSVhJ3qP0O>WI\^O:$O ܎\8 rv/8 q턑=bPyA:Fy29Xo[0@ gۍyn%x/_faDyFBvfdgqROD@@tHNW|* n nnd%Y k]@zK drUH{ %Ot4[''\L{H.!hH+5i|X !yɹJM\5FD e ,8X 8)- X. 5v';Aɡ+_P JA\{,k#MLql2F}S?2oŒ 22"H\p4u(N\SQgfd*m8|SNm{CZbv)_42X;j޾@w4yK@ʶıb9ȯrל/9p}JΊXh-<9mI؜2N\?aAOԄ)5ST~lR̮ go<ߘq߫3gCKTxdnH/bqW?0[*[(*>?_Ep@Z힒 D) ]KKXȸn~KZqJJHhp\:-gկ"w )|tPyQ oĘB Ug$hlG./Wa 5Tez` hsW~XMCgS EU7La2Z ) (#3I|_ @(E?0ϊgށ3is 7gx]Y/Q[ј u at P IٰEd2.z؉L)Pt|$L1HnR T5&d)xy * o(Db;. v1 k5 3vr m]4%\2+ NQ*s`܈\&ႄuɟ<4o'R`@Apa2h0*-iMmВMU 4nU8C8MnMTAAՄd .xKٲuQ>j֍~:͜R6 #Y/e J![Ƭ.KTR#L)Ӆ] [[.7Ƨ% ~_GZU?&U- _ƋEۡ{<|LLZ+L+5zE?yE T:`;DBehbCtUEyŠK$_T\إ6+бe|e(Z~Pfޯ UL%{ަ/ kVJ@5N$⏜TuAޓ߲y>f6a.SlZ ÕyX nRDpS(ˌ(yhռd,9L*Yb]UE\ m,aAU`6$Wj|uCLZN0C6>#y#<4!ݓ,[|G5;6쨸lg,PugT<2B^F%u6!e3NcC'm6Ag scA`/a]VP+t{ $>/zꂆF\cŭViF=CXif $kaZ/oR$VKk{5={z;58C7 Ga,{E,k|VNmrl.h spz$z&;9PӜs#k>pHG`&3Id],g. 6}oLLKI$B.ŴQƑ)#$T9xu<0"1{ҨxQfUHijIpÀǒQ=Qm^EzZ2S$RӘ8_dϚ}Aͪ09 ò^RxAq ];(L術W ᨩ[&kScJ kh_Kyߥ*{&Wqy6ŕUNZb"atwsf߮*m0@#L4s َ|D}&XmZ&ET`\se&B1,y ̟&! f"BF!z>N"P|dOg<ѡtc 5TP뼪56~hh,COLmJ i7U.V)vZ&@ST]_v(t!|dTױy\h2U A2/Uw_1~@]sJqfBۈR!w VEj'9cBzHA A u!5١,MN  -w_/;|-DO_r>LƑN,풺-Loۧ#/tU6|~uƒhΙJ Mw|CeqygFk8M@Z,&4WB~fWA)m$Ģ>X-&WA.*i_g3R7RN\S+FK3=ffhsOK(6g=Vxd.lu1PAJJBFNYFֽ+}ӊ0p?Jy= 8AG )(XO4_2BϛnaXVPq)j22J./+^g ׍ROuD>K3ddpx"iBm8cxHP"|w邼mcz9P)JC{C CZ>$ _)S}ϑQV\Yvam <>\6Qc2q32pUETw{:%fMmR Jrb"#AAXƝP |[8P{%Y*p\]S~2-_i"+kD]lȽ%?_߼B9m% d2''@V"l皶WVDj,?b`!R<,|Ho2>$hnٹrϧAym{Z!h6Xqv^Vx?Zb´].F-Ot0KaPڋ,b]b>|2,]u\-`t@mJT`!LrMĐ[[au͋pj-m]&BGr"w!'=5Pn #,A1Uv3uֹ^^"AcL"k+#"Cz7&o㻐MR9C{vD^hl8dwŶ ƾB%?_&x[| ;G&$qQ9W?õ-P_9?C 8鄙%| 9tߺ`\igO4aڵf|S?#̱'e'{ ^黜?\J5}>TuA\ 4anQ/~hr}P71KQ]:;€nmi(|Hv0r3`t!cdͳL(]f,KI/Gd/KXmc@pdR.jm+g#u XtԂN(YqN0XJRe 9ˎ8O2Lt& %&$f~Q?-~;VS81}c]~N֪[["[d\ ea{-0ۢ\']hAܙ4`3)Y"e;K``%6'?o R H c!ӄIsIqs =e͌Y?XT9T '[4*vLv$lUw/f'vNsFّD=@GI(d{W;00a10 4$W0#%_ L Y ɆzM/)-D\(z#6u8#ܛ7m[p`67Ѻvf%jԚ+$r-T]͈|?SӲ1e-rYʃ ;,zLi0&[p͌p[zef/`!SsJJ9JVcI.AWS阈ȃlRۿKNv [ZLP@#87ޙчՊamg6suνq ⥋k6~N MPMOgܣ h@kX=  s0Út@^~@o'<ː$ӿK~Ld;j{e+ S`U!ݱ{pיm&e\[A+O0bUU>| ܒ- JV6}[APc-&fdN [GbzNbm)0[S|zqZ6xK^ @`8^=m_g3׍"8zi3 Jxdzmc;z.a":nC\t%S-V47ai f@yXqt2 p~9'vh%AOh\%Só6N֖/u Ѷ*KNUON3td@%_FW8[s`$c=t$r@`y2isuyy +L:V-DT'fL׎c=z'Snk" ~ \xsT-84nBB޿/B;3矣jPzF&;.1MgǶwOYIitJ/>Q -mfwtL^s -dʗT?OW.X웛pez# ΥUa:cb09~=,:g^+{kZR2A@a~MJ@j"$ uQ3wl^x^ |?.1Eu6|tEtI{(0H 0t@q;$pm,£ȥ(yS^2D8a/|' $⮼ea"Ғ_+hBzQ& ^U{qiX428Db [P=®@E=;OH3^ڥKNl&Aeo> 5]O绑\!?؛ѵnUi3H ˣѶ}gɰ[y_)gB]ŎߵzN9'd(*( *2Ϗ0&Xvbh}j: lҢXS%Om`ބ1%`a@dK]>{>jŭ\1}k })LTɹS->kf5 =m9 EH24?'= gk0jّaywxv>[(hRGu&\8s&ƪJvf5G1`,JS3ZWIc=@6ӰSeI)Rȃm!cW;1Yv@5.BDblFg;Vh |TY8}muR'p=-C?ghE[Jr}eqDk~U`Y>2* |zT᷷2^uo{AQpgO4E1gn<s+#yWO񒑬4OjXGP}/NwƄ\n[VyPu|-cl {@Z(- &)ySM8@wr4c+ o`fza\Ĵ]z"3˫wy4am*qGr!ީ\2PY^yG*u nܐ?c;튉HӞw-k$Ӟڥ9L%㸳;Glg(ϲw|mˉ/ ]n9i{ѧr!{ mU zϡǻ JKb՞w%[HNj5E9`e( "W =Dnx)mE4 ?{mץSa7 eyqW; %7z4%f8E\s 0VMجEAHtl(YKN"2̫Ʌ~9hgtM H ^Y 0k}"=nL^8L~_Kd1(IJ`^0`(WmjݙA # ~_Xi|ϔ.>>ŗh0>l3DL̏gyp p?G 6]+XNi0dĐ|'gȿtt5w  JT&웱Jo[D4C>J} H57J] ߶T*Ŗn{7?T.x†u /1#)cuf%w\A-nZu ZgS [%x=z,?Ʋ+G]w9kFCioO»鞎 [`K6)#( wu`qΎ5}QR6 #rE!o4j[D!fA̢\1 _׼^B{RAm+tuIY8  s:#Ty6Q]# A C <5YbnN/D1DS:Q].|>N~O?P(r!aznS,M0`c XKl幟0?wӌWȊo-We0D*?p,d U<Â[.+wAӳleFx Li;9Rn+`$dbj_u/ HAL/m C'.<¶s4xrFWBqIId)3B}Dyx344Y\9\u8ᕠTep%Sn\L:(y|#輒 ;(t)᪪jjCH2e'bc>9D*wD{%oZPcN8Tx.&nCЩxփU[SҤ͖tz9%k;,9B'"ڎV|~%t)Dؼ* ɒ>_ݥbdA ^y"""^q0  Pz/9jTUQAt!%xH6;<= ZG@HfOj]*qW PkL4v,4Z^`:Nb܈8IIRJtAZv;6YP} 0qyo\}G+vXJ*(VH)3Nҍk2!D< ge¿W(fx;^Y'ž$%BP fLXB41vo||w)tj[5i67ad1З-a+:Qc`6-xΦKn ϖǕ#1dr@ංKʻ9[yJ8F]"c }4hj,-y#O:/qX5Q<wfֽP?_zbPʄWp̄k Ea ӗLL{K6XmXo^G#a@u7I/ldEHXya/M9ԥivKglFyܿ͵l@vcvNJn~t-t5?uk-WYb0KIS>NJ(5W&G?a[95@I!nr @gԟ(ʤk#s g (xcWy2[oJ`GH<=zCu>j5ΪE(mCnU3U W4lT=cm4ZK-F&2$s;ɋ$J`<wmT5:}7u.H+iL h=6$՘M5ea+.eS| tzX5o2F_뫟 g+[n}Ån~- HCc#,jBHB&}  BȶVP_7(QZ9z,ćH~5HUCJGm+[1A.=Z/l~.JV6sʃ٠vwfh ';clACOyֱE)K{ZdBW JX56t{A>0=z[JROEXB|h?Xv9`Cwf3(g3lqB3TA¢ܷO4 s٘ pʖU~Q=*nӸ~99uO Sp:4 Ӟ= iD xhvwU9'WC;DKkhtJҕ?NrX;LǬx *ζ+8Ǹ;bJTa%CM|* i㌩ A!Ώ5p T4JCcu hrZfhCA75K&Kq8+۞ Q} [8Q =S \: 7_η=1v; _2wRN &xGG4Ppc|OgE J TyAV9IUP jT+enQ; q/FCGlt}Ș}_=o\i:FT7|Kʽ96!(O`c#NGލ%Wݨi!37zN +?2WE1NNG*s~;zahB̴ DD,t7l*0k^кM&7@O`)+K$\JɷJ]U {v$F2K^2Cb*{8թzH.8UJɿ`Vuj%?nQ<`ύrV Vp=g@1Q,o@7N9̠ӘĮ3)8I^:M0S^ 2u{?L]`T+o毿XLzjdtx-iHvWTejyÐ9=['Nȡ>!J)GD6͞F2jJDVÁ/WF,A}"U`?nDd:ܦpj[NSV-ǚ [p ú-!.[z3-8cc8QڧߵcNQ+]Z\T KfKhз lgiXqe=.揘/W!_ E qGyS'rd_Zȃ=yA{A/l6c9Z 2cGbsk}Ht֩Rը5a40V72`e^ҢJ[in'o^:E jKbYmAG`r{`sœcvLTTVS1cCwL';q ureXi $u@ >w쪡a5YfŬL?:Xw()4ķnv rv_kֱ G9J\oyM&GI68جXJ- R%ut2yJ=\&@-3z [1 |FJ(Y*jձSGBe, $gN?em_%EiVO?_v"6ci*_.=̻@;84pH, mOD*[`K ClfdvʜS*lKԪ_s@;isHן,wyn?4>fX1rY{ڿ Kɤ vR"zkkFMDڂfak2F̟Xrv8 +וkoyM \Tϯ3QJ`QZJ2> dߝjH=(=w*E^ Q(wՍcc`lUUl&p?`B)+t{|,SМwWR%K&AXOf ʧҗ<1"‚ VsN / @@esh#5 N ROwukע]O{0L,7~ۄI7խѱѾOC79=lBzEE3ns 3:b"̧8 GMxR AcKM(WK8B/f)m0zeL`ּS!ԧX_ȾeeA^cAqeR tJ}J K^p%ЈfyJnHb9fSx0f]zYB/c2e/߳FZ/8^ Y +bBP1 2tSO=tyYP7s: ?W%Rn%<ιre#)<)Gi4Y}quS|%C:E"ZzkgYdMN~vB0/[T*P[| lbFʎ=|g ܇So^Au ?奌@iK6T ` *"{"cǞ*| bFKR@ާ;okZ Wo"AIw-xd,װ&\]VOHO?ʝᴨzstՠ| l>$dq( 6y)ǟ?N\\VS,@vĉ/`gX,D>#,}>`<dgi5Cܶ%*F }G>ƄZ?Ny/oG'~kIGbV́8%h95!$3ka=YL22 њΖ`Dbwf*[eɺKIM8boc*m(F|y ZœKXS%iE6ɌLo ]˧z,bn1=WڂIJ}k^z>m)!OJ/wς1 -7YTI8ڮ3> ǔXQ^C89 `#OKGTQ[nD4MJ_;r^p&oI-?s,-Dc.T_#[s]u L#iy2Xi`U"km C 3RiV \M7fqPV\3K&,蹀n^J.@/B>h'{H -e1GKVLdb2xˬ@NL[CK>QI;F=;MNYDĉ--wPm52|V*h=|{r*+m,Лz>b Ba]Ç ԏј4ehOټ.x}y] &=!BԀ} Y+rAŏ#]\=עO)3:Xҙ߱hbP;"G5", mL>0 -X5 "3RVZkKc £D`/h  <'˥cDz mL[,7_G iX+`̎CdC#JE] 1b`5(Vp;p1=1 8myB-FRr l$hi>sVF7̮cR\|5x~dJ f11af"ѯɩ

+7!]N D葘q.bB)MB Q[#"C3+Z%HU5(L.mX 3XeYg8T%Pw#(69TAJ v*or,xkSC 8^B1Iy)Ɣ: 鹔 qd{F]#+X?z.ȸ L\R/5,} xk|8K^{=mh,*YV-B=B Wo7{poO¼[((H`XdQ ]X=bd@§ ǽ_=F=.ZxDr]ǕsDƊg.H>u0mΩ|a@iM/@r(w_u(@&Ԡ5rm7RE Q,kky@QG| 6j;uՃijEP}tHdUlRs%Vv*FCT:<=)uP7ܫ,+Tx]d%ZnB޻?hMy 槧V1˥nttyqޛU ޥz&$[Ny;LAmCw[V,o<-:iBa>xT/Ry S48#3c{Rx#H]Q&PV!{$x{ V/֤+ ". Z=;hBFM/QmFD2nt+Y44kĕ9$1)E}>T]! +#W=XO1ڌ+OV>⧀~a޷E\qAi{ll"S\=H)qb*G3f= v º_`Ru ̢J0w"2P&򎅸:oqY9M<,KcSjLu¼Het%q8j(z /fIb%ZmAjuH)+N i!P[Е橵fxΌo璶$H_ 7Px5qQ@P*C meLl' 3'P:f1%֥c[⛂pMڦa aݣUI5x HzũzRwxOu]cbcM7"'uasph>U7]i@prRLsc^$^fEM>R ]x5;@ C:1;RcJceH`U[ ӔB=Boum@˺_ }5UtɉLq9a1*+\X:ȝˆΊ1gܳh~bW22)lvUxIzՙhOk!$,*QU,pUo= !;jeǩ3/3kpy.3rhj/9xh_IgN' Qpa @K&Lxӛ-_q )!y~3iOƆݴ2!kqh}=m) ؛!a!wj`?r%`p8eшW4%^[i%`bQ;Ǫ'PQ]@RcC A˨ɧy-XC[&_qFim_pK Ǩg>m@vʕf{'Pm9 j'n\D4 rYgksɔI(qV݂",d$4>AMbdiz?q'[N^̐o;q8Y"R]\P$J}qi5]]$qIHW ërj*bwM6/?ht)dgSt)^I9Aa.p«P>8 x٥0;O"k!r_;C!тŎDh)˕PhooṾN5'41xj Rϔt^D"KCSyRe냄crnؚly|^L4l{ ^oCxgeDԴtxD8(;[iEu6޷K𮘽Wul?7g ZL3c\uԨ8--_}NӲ + zX#WJy %O3@Ysm(jB$= 7* D I]l-B:BB4{yꑥ6=ǐ 84YۙDŽ|{;TMugDx80 "D*WJسPt [&~T8,6˥8`ymHOŊ*C V)oٰ&=1|)UJseuĆ) 6Իjs@_I}9sp'5Fza%&BT}x?7O`Jڌclj?S:]j_n\m4qt3SHU+ ̭̀ZZOs…h$v2Q^6CR3$TIe S'X9ߚN&Lr0ew~g{.Oa OaڋɴQ'lZ![=Ff5` 7퐪Hl`pn/My§&sa{8OwN0(5~R&¼:ӈ ^v2KD2׎O@iG$XVL&戰ӿ)^&jǣlOE!qb3cr8:mb%2| ^vYH&ӳ)OAlpׁA]Wdjm&]~u,z$h.K\ҝ)wt!MV, QCWOLU@kX禁3g bA+ofX^tJeب U}֝1H.P_1頬-D CJ"=8U!d0ERsБ.g1̷2F,8v!dUi A߃ORt]B#"+n4ж&=dB8 HL~ah)@ I#tXG~E4PVU,sgz0DG+?t1¨u i  V}ltVntS&y|תUՌ*+N? .MPE|ذD_rݥHV.+zّ'I‘s()@aW#  3Љ6p"'Kʾ;WyGծw_p@!͐y>>ѽ#} ado9N {`V';JU'q4Qvxz+ $?+t9ũnT{7SCd(J_CFF[}?,eXjq \.Faڰx<:pFxҌrO"C>:d<')S !cد߆}fqn%v'e|54s& F䣙ܨq7 qQXa6 Ӣ@pO٥u~iMp&(lϼdGe/=x ƈl)ڸ.b p1Zz=r8>em/y |uXV3,9̳R8jMPL2?8ޮ !"=1ȇ =?:1n8-b%By{kq^G: mD}: 뻼'=]{T2 9NSBܨ辦Y"qG4pYeyH dcq(eAIw:1\''T O? չz soZl)ξ2%Ɗת#QvT_!q>+l p|<>,#2.= _  츾9h(CCۮD)|[ =GΎ‰F1s"#ar$se@B1'J^%WJNUoɟMu5ѳe_mZ,)m_`ʮM@6PNvE`rQfGC=ZFjMF=^Y0 zG// Ʀm56IBӘJQ,#aqZ]2gDjY?7S8b ;\[nT- *|ٞ=`o%L^UC%<*Ƅ" Q_:9sjj'!/}-4!|,Ta>"@\.?^ VI D1>TX W|@ 6H&)$3Yn۟66t5> zˑAQkGh=7܆l/M9⳶,$7bDH>7wJܳ D-#V[ ojFԂW AFش޼,#(<0Պ+Y48P-_a>eהsYEjԟ9 ^h< IU,@A/~9)&d*ݹ{UF 'qEe8HPIO.א |"[- YHt^lDJȫ@^^J8f+T+Cb'@'9 1F)fbRjީe$aPBґs/s'Ya8. 39_ =EV0B@[T+L~򚫜&rnhn}74>{#wɷX2jnoLr~XNJ+-)W)[{0nЙNԄ [ |]|x{dش[ J]jywCɻ`W}B |C7f0[T¥6s#;ڟH^7Ap.$1MOe 0c3Zw!f{'2"fsa]FfVPJX|2JbPnB},BSE?.9 r.o m0-^QZ|k[\GDer!G_7`[N#tQ+lG%Uq.@*xGv-Or Ouэ"Q(eC? zhꡡBQZlY͠HSfvM%C/?)Oѿhivع֒fT•v@2m\`vZE2N xI! je8,j`B5&.<҄7#=6|~C:м}ËWTOX5X#`\sbK]VHIC*"ƺfz UM5=$2x…_FZ4@o\w)V IQn!էey"n+NY>e\UN:>3"R$+Px._濙a:afٴn!/mϟ(j)d$*r8+J )A~9|_QT,$LUSǫ"$2vU$RoXMǢN[mK.>(^A9 jc"}P O, cʫQO^=-?F:6`j QD*A#3qMq5(DVN_- /NЎ` J~6+$J|Jb+;bb .]>7!Ndd,u UcaC'ӭ˭"-!Ayw-đC^]Km:J&6ʮY1A$SEhgخIȨ wGͻc5t"3;p$3岲a1kS'XE>^VZ =~OSRtÑBb~={fzY;9?qĕAWj6P6*Qp,0/=kP>B0gjAу',w~CL& Y(pÒK iO:(ga8N]T5C԰UC_!ymdjNޕ &遴TG/GF̿mөxKi"o)Q^NǮC/{ FwSI"6 EQՕp{JeIХIr@>abIbXt̖ɟ\D {' i Ѯ۸x \ӊu,}&o}H@gqnl;|8(pV.8$aY "Nki% ~hQ"]b~'Z>MA)Y!uΫIk==m*oiJu07U. " uuMB4&%c:q <(aqd)Y&pWC#j!=ZB!ApĪa c?~ o}4|ڣ,Y+) 9Ň%M2t$7جuN(sF}:ʍL@p$s} 1/q?Ч`e5 Շ` tǪl%|ff;@ Dj\FT!Ur8`o? M+j٢G`xP69?18Mb`~n/U.LrAr/vrV0`A\+rzБZSn^3vP;!Q&^>IJP] htC/dB|?e:H&}4WMN-Skz2,:vLs0u+4BM4u _!O+9r'&[_q"Rac34b:cHz<s:6ϘIS~pvuDቛy(N@yx/Wҍf #DLi߄hw̒xvjI0MA99|ZQ*$ zUS; ;5>0t\c(2J>% ͨ^#|t:yRc%P_RQaϦ=^?C,̎h EUt=8qOoCP"uO4WkyzO3ז/Kua+䚱W~T/6 .i1J~?[7r/0cOe>kvjJ\ʼ@lt}Ui/pO,xCPapkTEpʐw9vBf[dzYg]3(UC|>îF5%PgMu99%neeO?KbXsU_ܣk6=t{IP &RAfqțg=mPNAFJ`BtMބF' JlьȘ>LWd a<̯l G6F=?(gM,ߌlsߌObhlIj6H?j[x%mڙ(ڏV0;y,pR #榝T4o!%@mSYU_ckZJڽ`㷐i T!t*jEvf]E}zGMnC"@J/6 $՟UA( _O,yPww"myl9y9Qjx8#iK+&dIYrd L/)cuIQ :A7R2B+^l~\lMB&#m`,<^)U8n&}".4B:^s\ytCPMjo"dFjx|%i-!6'5U0@nl /b-5nCeg SZ~&f,~ܴy`~ s[Zܖ=)b>rL}2JG&Q'iXJsk>.HS Atd- G0ŌI9ty\ cF07'Kߝ(Y@AH6ʔqRpXo9k/\̃H%,^J[P!c#$|$HEa  ׍g䙼iP91j JEc([ M~צ|/Ů:ĺh>8=P]cKI#y7%6]<F]O40uݥ8Zbg) 9w'!57 *mg?QO3h gR(0Pد_˗.75K˸xGNYg|₱]@ bÏo=Y8,IjwZP¼vw`)6z)_ ߖ[߱]:8֘X*[oD> oR2S/x[@O'7fgՙrE!c(X9voV{+#AT?BX[[D]ɍ0;9 caOsjXw `ѨoQz2Uǘ/\Kg7) G[A 7Ύ`*xwHX֛+uL Qr8ښZz._m\.YZU3ZD;fѾEmFdsvˆLxǫx'$uףMʾax>g$ѹ݄'PBا9O)wk=~xѱ`Q*/B8m:k..i9yW+v △aeB(p(2 ةV~^j2S]BOqO#,'3OۃU#>Wyˉb /&*ؾۺtp " ޵a\K◳pg밝j:oVNDvو`X˝ot5.pF>8]:QZ(]f3qw`8n\ go)nU2ħ,?[ؐXTR rG@?eV/9# z$ܤatT=xg,%LX⚺ E= kvu0{nSxv㔙U+f:#(r7XD^f7 ()Oz]j9. g$)Xo 1ÎW}Z݁zi=TI툰O;-9b_Tr(2/m3!$ӓHeqeVtݪF<Ѡka$d&ަ5ۄMS*<[qYؼ|idZ+#PfI~ł.|%~Wm''y 4@[2 !Wz™Ghӝ~^+"v^BALٺٺb>hnݣ_#W0+H S9M) ̾O5+锩vM$\mLj*vBM\ ( d2{ٻfM DZ콹"E`C1Adymͯ )DOjѿt(sDjuϾga*Z~ ޠ%qlʭ_uT+FWmYG)}'4RXߎ4 ɍtv(B-z1*P/Jmd=EO1$e0/E XhX˘þK)mr;`"g({O͟' Mg# Vӿv߿ҭZ1=@Js,2s; 8e 4޷2#!d-}MpM>Moa rC"p\w'b`u8 g3%XK5=M\p!0dd6gb%Gׄ߃h~fBѰ`,cHjV_d1`hZP5Q]r.aeRԋOj)qiTxښ7gr}I))('`p}om#AOG둋W}N Uu[oSƈXV"3ok'^&Vxbj"osF-4 % (R@GkMt;Ti̲?#P^VW.к? C8GtS 6꾪ʈnfflWvWKvauޚ߄"82/fj[\hnx "{jgGF e7\2vt8VJ`”HW[>Q6 HF:m49*_!/-^3kZc*|切j '!Ys=OK :C^3"~cIS.6PiKZNs0e/wP~ಽ`9 f,%΄! KyQTp4pǗuGDU r?'O:͋a~{@_[6syI{}ò;J:s.*PFu7,D!FWԹ\.H!9)NjGMxMl'|dT'x4yu ]{?}݁7.VQc_L^3䣼ʴ'L$O0 9/@ )t$ܣ:j)a50R|Hrx'D\Q"~3˥üޒbP_f->_Yc$QJ?uN:n4y\QnK&I(x(05o,r9@$!50Re/ڈJ}2C(}~nlC'W#oD i @ZSdNO@vNrx:L%o+=nNsi 06-}[>2SP2i-i3bhhP.Cio$ёӣQwN:_kKޑN<-es6%YYz0Ư)x ~l5%|AW,G\/Z-Nҫ_IH56&fHMo/,Q/1R } a02>C_#u`~MDnL9d"CL#` Nԫvι}!SAuS$!ǡ>sx†vU^ICLNfG { ր L - 9+Ҿ9qL*:;.rƴ1YA>v*y d w,: [=Xscdc׻ }=LsԺpܐGq|sJA뇂uǝ)qKoAoͨkfU#F)S*־seǾnGJcfc kKe&(0\N068A`Bikv=QȍH&v,F~c@C+bWZBJdd]רc@/5%,duxhˬ-V>J74֖% ɒ - V=T2 Ϙ>:a,>é6F90/a"T+,F=}AGмce&ڏ`DaED T@P%S2 ä+$"A~^t%O!6FmV; 83Ή qsDąPe G]h9n H j8]Cʻgd[ڽõXx z5묹[='ٵ?%'G?₥᭬D}$H0B|; jY"4&LPz֋dr7#S. EZGMyC3Ita.n7sd<'FS!dFw*&z ɭSk!%4H ;wE1>(0\d"Nj ˶J\J\q=mn,ˊHvE7hWVuZezfvS1{i!궯6Muv Z'pѣs5a`oNWy>'_9gt\Ug!u+g#|+kuM z'frluN-K"9̄[k}'t_ד9>IMkdreŠk5*ݽOϋ`NRuc1Kה5JL6jӀ6H[&b\YVd{ݤOoퟷ!B֢DXNGp ;mXE7"Q7Qjf?} oCϒl % 8P.`u5GwB*ǞmGH*1i $hA݄lfM>DBB^:}<5Rs^j Ž?pu@) gR:-F;^ ż nfδ>IdʲV%{nw<Q,VuMlPSi)R NqKWnDnpo~1v~W~%cYSX }Xmm%2S&(R= ~Ź˟) '.cqdpCOb8sGI#d@TGZ ofSu*lHU\ӻEqC a 1_g#d ]T!(X%Љ9y;.!cýTBTrsM6uH~ؼzKז޽ [(y;=q QEQ5P)Cg&w}T*D "@7@&,ZWNJ gyrQ<{LoUb[ P܋w0l.ӞP GnHIa&>SS 29ŕ Jq_ pT=KUIA* =*Y<5D$>5# lۃWS!&0n5l9DCf8į)>֮q?kfgwslNC`<?~3нtoҀ>? H@;`gAϷ( uEQ'Apg />ҍI孇!jc#-O;ZTgȶ#t#åױ>񠮭ҠEMug&>oGk`9;*ю[ Xb jk+Xꉊ#"G4`LgH2j ^蓵bD)d5!0봒\X[Z0MSՔALtxՊ(W%"G`KrvaRU0R&s\ t=P`<4g}3S\?x? 9C6|$ Q+EEʈ 2 B4e&P^;1A!Y9;?h-m1 }w5^Րiaޥ$\08t v5wݰx`G '͉vr%#A+N Rlqw]oQN~SIl^{ZL;ؙ>H=J|V } dtN;Mce޹"!G]'Z^W!,]܅ eT5 %{& hU_)xgV T! M |0k\-;5/xN\QN̝<emZm}Fįb:8y 3nu+J5DeyܨMk7?4"W9k|h P6cg;W6U%Rq_D9xqpd$=+OH ?k^07¼Ø!P"؎BvYP}|y7iߌ0yKy,omXXXq%;=;P<0!$wrkPﶖ zjF`pvr7b[ˡauvY Qvk[ZefuJQX)b"fSp-c_C5}(BA7@zxU_)te0UG|a6dzOrn,k]CJnI$YUi>AxFa.RN9K%u<yS MҥVMA֯T 1:ſy TJMH!!`>D[+W_Ұ{ࠤuboHF3̶8&Ln~WfpZM}<=`c p=VG{bg>p,+h gl>۷)=)bk6#JלMT 4~NCPLQ`$:$ww,N03:Fު}f|./ێr| *,\)T!Q`Y `fIw9 oA^oݟ׬V0 q=&|I*6:aDHq=*5~.3pR{d Z@IFc.P1]5t;Fp2z_i{ !%}smt|ڿl|qa#kmf]> hݶjI7 ==녂"@y٤Q;jEJאu|1F3'-50* T>^ɱZN D12j0!)A'(Rg;vZ>*8w[FF}4A*mCU Ddyr`mHȷ{RnKfA*,ag^JBP_~HnYjŒ' .>k Д hx;#հ>E\gIhPog.jH XAѼsz,B>^!iD.g@^h,I( Pmx(/pk !Njq&ߢ7R''^$@?Uyy9Sy DpuZݝvOT[v}Wxff6E!ӷ4jᆰgN#R$eHuӥ̅ c\ B$^KR:~Tk׵и6V`<@ԩg׮? =ֽ7ynึ' z KMF'ect3 iOr\toe)<>Hů="uhL!uM"CuS~B>U7H~fҹ@4q;S"Hͻly*/n0@Awe[#K?TzU? *c@qx+0-9 3>,,WFM.UjQ~ˊ{L9.Z=Ȉ& vz; =4#rA2]Џ|͂C>60=h>;\XWVR 8DvfAa![SD.m=*]ve/O-Yy1Bǐa3O:X"Ljn}9ȡ#yf e N ӝmDr4 +!'9i0Kc[mhqrB5K2@z;Pz3Po/"7?(/vO$EZP(2nyL^#/zd XD U#NW}fM^uNH*{"iah?Bun:#yHpDdUĬY0V&dyDw‡iWӉ#63l\)"qu/@ &$J 3!P{q{u &igӘ6H") Lvr@TXhwjuya\_hmg}`W:c*|8,OTNDOk03о, 1U 4'O--*є?b خC#fP#2Jɛ㣨D 3geFiQ7 lFE+JbZ*HT?5b x~ZHduF"XZPXX,M| xz2D<~깲,j8SbBc<*V,9(l% tVXP{Ew{I )E!y5y'?hYTuNOdYg =D4+~T)qɀ/1k&XKoe֟a5TB% b658[ś{u;'u;Վfj`ocj`NUP >iBn=AQ$24}E&N.i^zlNQf{tCiOIӓa^&5UM+To۩r8.:@e8ᬂ1Ώ}.Wn$5"6 d_%o&ՋmнGuq//w/,ƭr 7H>_6&X3(D2#{zM jDQ;F <9"l Z k-/'P)`%M,.TIdzYj_Z4DHÀJLB׽d}|!H2>r^/h-qSUNvf]X#t-Et qޫnMԌD1>RlioPNd%4t)+HbU[ caM)fHԝD%u}  {^[>c3ՈXJ(Hzu#31zrɃG31uJ C當ʁ|ȒF;[Pe`]U/29"$yHqؙ6)R2XU-y#z_CSޑ6)pB d Qؑra\RR.Ӿ:|aXe<0lZ%ת-b#ÎA5ml5=!nU3יPc*ݩ42ȃA)@4*(5i{ܼ(٬NU/Wum+9/$U۲V˕=(q5ZǝG[4o/Ik/E\Ye!4^&5z\XSzGܫDy(ժCh+vRsUێD(P }wjEBc8*­:q_]'q: sDd^{Nk!C=xTY?g bϱMaVZP?/0Ru= u$XQDdh@v.NdS➬< G/̑e\rZ>nl8B!(C 6mPyL(詸y$Xw9T¯ :s67x;<&[ k6A!`C3Mǟ%seguߟ>`uA/sQqS\*tno?)gS;Xf1Cn*h^n [dkQP=/Mӫ;Bmzxb@Wx.۟75-ﻗl~ ڭ{<i%b?IcdOm[{V t֕I=-+$qrrSF'!~F0\Qi\=VA -2ȆMP //gdYq% CowJaie3Nx.DoDn Ac(CH5_,\ұTH%YLJb΅DJ"Fߦfi3)uu 5q9W͒8p79ymN/,Zwl)3o h^~:+A2hP Z@ˈeѯ/߯nU:9BMjV@z1J,F|пXQ<:W ިpoAg%aĀdžΓ(3k."d&VVw$OqZ Cn5o91qG3FJ?WeVi"4~.< $qQ ]O/ZvY 4&NyYsUKJ&y~k'RJ:T'#6 a\vj;W53>6\zCs/e ;1?Le@[ sr'*%%:Gu FE5ȹAe6_q Ͽ;إf4c^fy?D:@uT6?u\`t8M<=sqs D<2E| ţtc Jyfe/B6IM87)CXM^YS%rnϪv#FdaOIk窱w)݁LHdgJnA/%1OQ|Vƞ^ܻN\]:/al\%ϦE v6hVYESOKȝ= bTB0fST΢Qw4sYe?3W|7|RTv-ڂ}"_9qJ>WXcpI;{l{Gz~SLtb&YKK%L&'*j!4xsR3̼۔=@M4``dVP?A.0&FHLHneW\* v;G* !VkmiҏC.)SлR'9%-9;hPHZ>P _݈R\`"d[Y= [6G4ܠࠪ::Í`gB70C~hi~3ecl _pSӷ~Ӟ*uqf ś!#G(7Rsycnꛦb,4֝#х;*yY m PP+Cu}btseg/?~t2iI4TWZ4Qm\ptyQ3DHeO;CP\|zMɋeqҮ#6UY~t&@l\0nԶU8=׳v|)Rؗ(jB0HϨ3U7aB|B S}ۺ)aOG }ϦYc- ;RjjV?RVR'jB##g-_@HR.A$@ڿ%;"3h\Յ&ŕr #3KZoHa̩EO 믒Х8}qt`фRNJ7bٛ1м؎B7poߛ9m/yq 5X"fuo.OEz}V g?UwfaJYN)I>N[MYe7aj-/^ vWJ+@psZh52G.6l/Z:4R z o"ڊrwl: Jbo*ɱonoAІ>&fp@Mt kw.+ (K=c>A=E))6?YPX\:'Ce@²|.F+Kzg>(aa"+J>nśnhj&nzĄ:YSEvgf''xN]~͂ic2KREaE(*1LNe/B;ZhxH_ V׋o(wjQ .ó#I@Yf"&0;wD!ѯ-?GqVc*&t&o6M")㣷a *_8{ QOKgm, ؜{,y)UG1uej@䳁x~Fԧ* WT;>z֍$-B7ίM?;v?Ϯ)Et%o7SP)2šfSH$qhbϖL8qBxb]-IKjjs +tI/ Ig L5!%6Sf@șOb P}@*sݼΑ`H :RP%h,r9yVGn!ӜXs1K ™)O|~{Yw Xj9sY\uĖ,"1>6Jv79/kaĵdⵄ2N1IFK kiYFQj.Y!%b n&pSCpt .; ѺY- mĺIC\ x4kض^K] .O H2]3mJZ2Oᾪ9?¤s&zHiɿr> f]N-Z# s">jٮwⰍؔZn{uwvG|kvuhoZͮpf8WK8Zl񬚟%s&8 TלLpUv#բ**I<+8H,%nݤ LM5_di^c|>ɀ24UHr@7A{HT.}Z|2%Q<o {ZáOS澹Q*uZR m g׹nz|˚̺g.1r5! i55۷zHJ9VƘg]@Z*JJāX[ /~ʇ=%˃Nj$1hn0z*Bg_ 9N) a\Ϥ1dDdĥ3BHeDG &p@Aee1m=@>t$oo dC(8`_p(=Bϯڗҟ=+}\EّDqa)8AAwn.2!{0Խ*yAʵsE;=b;xA}@s 38jiwN4ZYgu23-?fwtY{בn ==+zfT7natŮO[$b SvR^LWA+Ɂp_(j'\á:/а0eiC9CO\h<[Pt NKJE@_gҩQ\adHE!mOp3 xHx?})rZx@{&_>?5ZGwy3f`mzitgf dDdl0EP A^,e!3#ԫDѽ )6+nYn^\y wxD&䟬tq,ZɆw֚* D\0S   #7f,gjJwah "LcB{OV^)9=#fr#&F aoV2|ς9zZ34K= ~Һ*b^VɴL UULIqJ5׶nF_i᜴_Ϝ6de@ ]05w^B+Qv\5zhҐcJ5N u@tpDZ1TX.V*&12!V֝ [1qU+lI?yV 1SVlL|$dʢf] GO-' Eo$6<?·N5{:7r vmOЍV{ yk߽2t3o[$z{HG=@r;5LJAybea΋턴ehKӄn/g8z/7JxoZ[%2% |aVK}4oqȄ"fVj!39A %;soERA@*b)C"CΥCm-j#eʺpҌɛj>UϧUPvFnb@O>?ٝINTR 5/E\1O_0/*SBrGƷ|oG #8NqZeyd;tH]*˖N_~&*O9Qyr({ۯVlqXr.<%b лX[V[Z!ɀ_;NŻˍyR 5|Dj6 G4 EwDҜd.0b:R=c;Z3NYa0=uԜ|襾K%"L58dO-7We꠴! Y sǤ5.Ȯtn%A*ܸ7 ~3tb[Y-^1V)}%vr<Bn2LS|EК=SV w3 LѽcҼ. Ip7] v<@S#.mg>X? }+ -PzHE⃓J3Z< ,dXw.)O4{VasZ#?*C HGၨIp?c'DJ 6v{t`` qf᪸YbiH,KC`@wRCI!5X&!#) yu?W j{ 5Ѿ^q*=陰γJ%.<_*QQ̩h?W>{H5~0F֝ h2&Kbk܅O,ݞ_;9'}I`2I^\-)+^V6[U<ą>QR)pCC3BO*( #yUEsI^IPRE/59jJJr w'rH4s(hmu`̔~nP\dA廳;zOF#|L] ݶ(ʫSI+M C49ZޝY^Zy|Tr|cvN3~H8T%okއ'b >A0 8v֙?Z<beL`e?>dxn|s(I"|V+s@ޫJ:?;*(@>™Z1F&H(!jB`8umCAr)ey1eb&7IņN1I}`.)(i.>b*9&!W2بŠ+u0I5 2 ^`S bd؉1Nrw&#v$A]9,陘dņ>'Xi=K!2He)bGt:yM[>,ڟ zD˝ENˀ ~TjL7 V _6`vdUu1}_.?렩NhTȓ,N"78ݗcYyy2#|J<1 f˷(]扥8Q]h1I: GU">r¢#7G9 8Rh6p\JDgI@fCW⦮;70Hk-=)Hbt%/)^@*9KmmU|c637[J_T( Ghit?k[)d2oqLCo, ; n@G8&~6{ܨ7fm9w~$&#H^#T9ǛtgMlv?ANq#@P[ lA). dͰF*:j gJ3!N -o>_o| #-2w_j3N_xb6+J)y4Õin41\eKnjV<"@%v6crI^"6f"H.Ģrh:ځeLinK#W#l̀JX]բtm Gi=Gs k)"Z,.KTС~aoQ/a#d]U p,yB3| 1G0?Xu3-h"tlh6T^@]7(Up|ZAptѹ؏ 8掵Ga hSqvWo֊_}DCHkNF6o{ȩXs v.ߝ| ?^q!?0t r ifc6Ky.\}<' @3n&Q:װ=ӏQQ>ppb3]cE:ϝ.;Ȏ1ۊqȜ$Mx'{渆~xozn 0o}uxʒwz\'q E)ƥ_Ɍ^qt[Gwg$/,Q\ 2Vϙ?C4і>`jV!l&K,LM,a}EoWKZ+^'96j" UIbF K= qMZE޺b'OjaOMPܓof)\x\8!QE8;bZhx]n8lt vR9ˎw[;- Վҝ Pz9ջ3ּVQ( |lJ%!GToBs>$:CZc(UNE-?$y2 XmK|)DR`G(^&Ny u1=z5xyNN8K6dQ!=ۙ$NB XB6 ]| sBK4Tm.x9J6jIsC;̬eSKZ3 q{5:wx[E6(VK|MɮY@JݢN*=%U8j+1L{>P l4&\*}3`M; s?5ՙrΙ[MQ=Cy(X!?pfɽx}# KO4O@D#VBh h NeZ+ 9N6$pGc]72ȼ+ ;0bz`)+:2l?P> Lu]N -o^%x(:=.>+ gq3tK涏{ѧM"f@疠|W!NTNR%Wx>9+.ѵ]ϧ^;S#ϧ_YUTf]$;HÀlVn/) 4^޹hV$2``QU3"{w;Tmzr[9Pfλ7N3I\$_AAT2;Ohс- vJKo[{f)UqV\㕅$Bdb}l:JPO Md:T[zxj쓛2iYqLI\\`K&֐+ 'vw#:#|s 9edzsmJV?z;f"+x-쏺f6Jm ~E4Ď_uq7껥UqowڶtLsxǧ@oڙul?lЍ;='Q,!(- 3ҫaK"L|CL;L=ֽfMnsR^lKrH[2"0賨@Tp q^ـ;DbpyxV`F|?̓B (_.co.g~RBi:Ig)V̈h8C;f[<,]KANe^+OL6ӬӻoB%b0'(WF&4JƝ$Hچ =[|#%{$|qPkWqxi-3VKh28d\+|p]Ù"1LB*ږls@6m3z`,(H(Gc4vZ>JBN &i>Ix/w]M ;fsq t2NEsO\inS { S?Ǖ`=e^lx&0E\7FMc1](Et-`{AO??O- -I -0/INJQIUb4#Dև/}v$PcZ䪠l>_ 0uHkVvA[Nclv5ҫu>}Ö ezBkxܘԎL\wL@ێpVm, 5}UhSvw :T}P1вf){έ8⌿Qd fsA`XA +?fN}BDJNZʐMmitU}tTlµ/d c{R^qw&fȅoDc^q%+ޡCc.5cȉI +' 9 jboiʢm# A1S.W۠82v&cnĶ\,3mTFu9J5zY#v 43fY<D"ek=!jXLh% ^$SHZ53a1l_Ӆ|1)rL}'؀ȹ o'֎^tJH- =8LޯT+"(3Q_ 8]$K4kg]x]KP5bۜ@[+6[ڑ_ iaMR#%X&: 4\(..u&=Ŝe"pDjc=xnR۾CH}u0NCp.`ݭVnBefPTAϘ7ŞV'z+5tl%/o)}_`uE!Jz.>!!j\>Bރu< {.6mrorA%t_qPp`$1ܾZm"0zfplCbLJH,LQ6,s@RFZe.͘0* axƽVSL c M Bgby\<2mKAc .*YNjAѻ2Z wXz lwExY]!5k3-KU`&};I Kf%wr름;W`_VGhgh-k=ìb.(F!,~ C z&I8Oux4NB8YҊvWnj_"UQ<6Xv G4{躆GՄU!d%H$]woĦdwJİx6a"~5Q}*/BA/Tt@>VVOA.nf=g(MȩB՛]2 'D#UOco7ihDֵuê|̇`뺕&2d"uܢ@@)m\1iZ\(l y L$&M1ŗۦl,ˋ*6HWuvJu<_f} HNqx>q7(%F/1)J:gԨnP>a3)Ϻ2iA8oV(lqX|lk8xJ<&$5>.^ajD_ Ͱ2\@58g YC2aKRg/&SUrBn]S5\zQuz-i :o|v{WsԓԼ@\}omSMmWFÕ!,^30YEhEdxƨbiC8{TP]5\yf)H/&/ah[^@T\&T98Ypؚ=q s;%'_eY^/agMS{^؀$Tڼ"H/F2%H~%6!#R!΄w*  Ж}xpݭ9+[Z^~{: '3\[@hk(􈢱Wvvi|-aj:;82'ED<ĺBը~JF'ɖBm>4KDX_6p9a2љ'e5ž2o)W~L[lY;KNܦ o\@e,ë1 ϦC} Y}Ԕ]q]jNi{(~#p YNRq;@megYppCJsa pަUh̪БEYęGV8FwFYXfdbx؋d1Dffs=Wa:QlK*ru19wN(MUW[td& Ω>nA{DzkEjHz j1 u^:G!9c$`Vlz/J+hGgjyYZ[P/fzg ")Qlkє8bRegmͳ_[T?D cd^O[UhdֆAw,aZwӲ%'RN; r9(omDˢH\$zs9@iW~ TDsAm{l0/qeѻp>rG_m_$*GePz 爯Kx;)xNpp{n6ؗ[ɣ. -Jj񊢜M&\Ŗ%0bS+zۘ~lT= Z)4,Sfb){ x YJAat,?L톊  C|߻>9x*y~7+j)vyn UtxtFW_ve^KIFwp&F,5nkr2 MRFZByHc<Ɓ妁u7,g@vD> M*ǣzXEwooPGuG{x ,s|C0gA11R/b;۹-_ nԇ` А ˟J06bπ11,p=ѷ=pJZ7X#愕wnƵ0g Pl{ (F+xz^։BZ ̯;hNxt 5koKo=::4pC$$aY/hwu#48%N7c鈜j?[fDD*A3UZ7cu4ʹk7,x@L ]7/޼o)7'pGw,Sݐ<x,ꑶ8֯?Ow3Z,?͞vDd[Eh ?9F2V^MMul2=4Xtm 7Dڎ8Ltw@R׼ˢAXaFN* r:<4UƳC}b~ {@—|C^pCmF>VhHk4i$Rn%F P?7@ 0͍ƲSa F }KXpӼ NxNװ3I :.-,V$|l{"Qm2\#n H|NVWmBB;7+8؍@CX Hb(v2jRp F짮sҶW?(-Ø\ń=l- -qB]9@QJGTNf/~B`!xQ$3g -4%j(>{-a !SPH,߃ML=3"eˤsV430$1 Tca #ar-Jp[PUXw$7K65-6.DsKd/WS%pZ02^؈|$'Z%s׿0|MH*%B2Ga]6!e Bha9U=#WG2VAK ی@^ObKs=ynêS֚Sc;}<0,+".910OHje9DFz2ZoVܣ&oI7o",[,s-1RG3kg)TJՈb`>~ ZRt6LBfR>GQZw}٬yAį}W2-$mn(9^nM{ dF_X@ZM+\L@JMZ8hwSZȰEAMuHݼJ+p9Nว-Uקb8aږ]qjpVBaZ=A}f9ߘWdѸ{rV4mA@] ˒+9rfR |JeǢ'Nf+~5]/#z;Va 5 ܇OYȗJΓ[ڀgZp3+O[3˰R?'N T{_f"ۋbY) v9b,Y4.c=eshDcvwh@kIJHVƁ {)ݜꄅFAd+~M?쵀 K5T$֤fzgb E ֕Ad$>73:|r^>F DBuI̙QX]m,tyr Tz037:СD:sq0ّ| rb@ˋ( 4PjG.poi/noZ2~j"81#]V'ut[HZ϶^/ɀRf Nu8ĆDL k/cF`)psrX/+ >f#r,h(\LBֈdeA"ﯪhbjwqqSY9 =\ [{d{~Ty9%y U<6v3PRǕ0]Z'!ࠅ+yzp;Sm?l/^a< "1%]N Yh_#112Y-Xy}$l>NY˒y||/~d`;GB os`FK4u9ͺeT.8xX-!Qhٵ>[!  ̹ApzSŚZ !J:59y^H?<%9ę**`47H^b؂CZ$w8>%ɯR9Zq<3_zk8/♡a'oWawSS47eax/49R?Z<w; gEQzn,u;aL ۝#wMqWgUZfHiV,}&g3XٚypS>ݯDh@]ԭŅ,| ZJwpX,3)[k$.`4 8^y׿ evBchEi.q G߯(Q ku1/{ﻨ/u>x@ZzXi>|5 ZY|D"c2vӖ/ѵKhpsa5 >!#OXpͮ&{;_y}5MdOF 76ۅXLn hNɟF+8eUNZW!~ҺK7Ç}<IT q r, .=^:LMbpkk{/R 3S&3]4ԆDȅBPPxh@bO ~׎ClLD< *L˔V;OzqF!,x;_nqzl%*˲NB|.0HGkxyuP쁓gOEr47];9y \))$:oJU9T}wm~5Ŷ }R o&}!f%kRF X?կmm.G7+.HzzE >cГ2<6t3TOv?r=%`s"Hag2.Sg0N!@zѲ\7HPVWuzAv};!woE$b64SB|$:7Yq1r̺+E!;^]}kmbP9aӸR1B}x800*"d&A,F"hxP_sShI b?d⇆l1ݪ.9plexK R]l$ !BocK%1)y8<`NWg\Y.;*fGۘFTUd9.h:fu;r# Iь\ʿ0@h2( iO l/Wq]r+G?XR6|!@u=!bdȫFTѱ]oY/?~;jΗsۀ!Έ-<}v0Ӕ5?Jиё-hJ D: }ꍼVbd̸fun|q)ɡB3:16YFCxzLy'U/p wkx̻mkHխ^+7T^jg3 7uc"*Q7Z[O+nE@KU{oPهhLHvճL7 CjteOdRKF|K޷2+[f//v5kdt Ƽ=uΟW7YΏ'םpqgY0 ϐ&Es1L_KM %6Ú$(r(SUnk&xn }䶐ELk2GDw,;-z#7lV#k0uMEڟc=LzU& TfElRpzx7sIn"maT*4|@Hc I֙TXf2jSHۍpC,fA s֫3mʪ ?m$\s' W2\Q|!VM2UlEztCxt~nTΐڸo',a0QQHRK,o}&a); 81 2pn?]xdjv+mkAw2d%0",af Dψtn&7<}阦nD嶚Ga\,f=D$sk*ZR*UC_ja@V:`)#EEÏ4,4NsGϮ֔/8a5#i;PVCw-'j$u98x0{9E ][GodF-6GF,?9,O0-"e;=N w0OO .}BAɉ`ݾR ku!Px^{i@BOG)!j]\n8n܉~ Ox~f7OVZ.ѱGrVhIh0|Pb큍*+YPV[ˊb?q&b95I" 7dj=6{9/%s0iЭ\`jEWyѽO1*-qW*Py3@@=bEL)y*O2~'9Dդ^8#\2ZLqo,7(:m ^@Gq'/ɞ(b (M[ &e\^{zAM`-%U@>xE0tAӈ@ͨĬe{zI_p<-2@2X Wﶖ}?#;F|ҹ5W"ᕮ=O%Z;U[INͼZ4LF;\])<(T,3@U*% 'k\siI1bz ?à!+^hv?Za 8RfڎwǀءϢБaޫ(x80Xph"=MN(&B- %Ъ>.o/ERBhrBe?Қ6rs7pw[FoRpm Z`sn(}D'QnMք2ͤH );m'^z%`RZ:wErZ%9!dpr۱d`[WK:{^yʬx7h򄭓T@;U!frw}]7f-ZyX`h҄NI'G`̃h\%}.jsMV;&{ؚgTQk`_kfX͂T~rXD%hc +gM'ޑ*]v*y3χrkB`ez`ʋEdli"w"?YOk}nm ^ݡW\@ͺھpUZ)CRO3S%RV֒ /qtg\'TS nsQ?ER;)uFW@iuRsOɉIIСZʟ17B"]XO$ Ҩn+deN #<MaI16|yͯ6Yffwj&x5XC=oXRONz ~3dV"p~FpO, ',~rc@qLU Z~ŭ/NHC =a@gŦfQÊg `2w0D}, ~KL!)6kSȺʾ5_JԸ)k^lګiƒaMxj>'C6yNA?yV?y70Z 4N],n<"6eV?jDέ$'wGF0% VSĽ/t WJ-1m|lx4>DY̹ CNWgi ʨG}K#\>jʫ6vR+Okֵ8]̀V}uIp7G{4WuHkˈ|U~ad?f{dd Y^"qC" Ȍ&=!۠ զ2: GIݿB#S6[:V|||+|*)j LJ +A_6 LFJܛ4r /WOeqh6r[2+X}sEgpdL}S?G~Y*!E .Ty`x6U{'z@-q;aJbS[0LV36` 'jS?ߦJNSƷ9=jXZ乊 (dHѱ+bGmIz #]}\Q6L4 $%hHU1`#HT{~40C8n( 'F3ݽvkw J4%+Mx&t¦r8dce7ô[Xˍ}1X2Wi]5CQG,o('.wc%AM>OPV1B\_Jic Cb HdP7_qNФ1]gS8 rFFjeAHЭN{:. ԏDhN+RC߫rd77Rm=|7`2O| c6[rEV0)< +ҝ[|uՑ`e`V/L6.:,Q;pNǃƃE.e?Sj3Y/Iյj@0ΦhCj&ăc @q\9+`PJ-@UlSݖvWaE-%OH\9b,Zqmy(,T$ZdT͂w W|,{BO {HZ1Jk;챹WZOa|1uLa8˪v٤|Rxڇ@Py]&SyPrzЪB)t^;)d)ہШQ&8A>8ZxW+c챿*@i= \Yњ>eCm6$n9Mڂ] l#?}_nfKC-hg-i`~i&:;Stܦ3}D*"*9f^֡]4[aӊ"T:-Kn. j1fgV³hr|hi,6~. >P|7Z A]9|K Sq V>vrsܪPMKޔ~>{Eu&M47^ȣy6qaJ#Sˢno͈n@*( x$ 8K̪θr ӎԒa\H-xƙ"Ҥ]p3W}?`Ҽ slYc{+j m=h.Zyr IK N[ͻUsh0Ť;]Y<I\ p՜N;Q*_x,>Zls۽$1!ɇ;(k|ߎ#ucbt%6ZjG0y1;ȮJ #lg2{Mӌ#E3k4߀C&]WeCҏÏcE?\O'MP9W0I#eV1:vx]G7!>A{O8"Qೢ4XVetMF#$&H,60ƘxC )ej|t*ɰ;D{Sm"R y7H\#ՓU  |Yq]{z'j:x£0L|:[=B=5{a,9@{=_g-Cx[W%rkFQz?&4L^-Ɓ2ҋyQ&Ax&6x$@[ `,uc2; >W9tBFb ĿF|kdfpC"(.7#+P3 k͐~ }D4rheU;ʅ4 IXuI'Kt$+'x Vtj TqA(͐eޔl16/jޝma=![J.2)~PDJm"]9A:9I 5~Ͽqs!vZ<̚:o5֭܃tX<-=Ҏcƀt.a[`fʶ:A;zW[rb+nIGˈkgjZE@L4Q +j&3졁&W2.rKjɣ;aݘ~vP:EGz"]66u2Qd$I&>CvAjX`54x ?!tIylO{byTjf+Ll,CJ] f^g;ormBG^NYx;OXI{9s0 ^B)ӗbnU |L]SS{G+|[A,"ibrʡo_E޹+T/JL+ӯsr KaEq ]n Q-P덊´xB+cXܹy9=,D]?Aa~YBjsPnG8ɵ, 7KM }-Кk / ւg缹M SՌ^ԝ'7QְS<&#^+.hm7s7lLw0bqzeB1xJnj;|]w tږ6~>^v{6Mo[k{qpW8zzku3Pb|ۂ}N'潄+\x,x"] ЫNR,ǔtIhzk *L]z$( !͗Hk u-W@UNF:jEȷ3?.~څ`L&A!~9x+F~b!r[20F2YlEmO'c=B F K!Yk`騫BϪD99gn0,5]zDk%O" !lQQ*dA^րX}jOp}noب%Ӟ]6.>Q,id$i7Mϙ:`Ǟ(A,9yݺgFow;t9pbߜe([><ܹ94'KEؽ~e5 Ē6G ub@)-&RjPRTUS?YwDbb޶!i\q  Nt)CH1DvfLNi%$oi P^ G]L~*g*gI*-F]{\{dHo3gO&Y]:1nK 5UI= ~\Qh^B+0#WUWnܢ@M[`E9 I+\0>㛉Wa6rA$Ke`BhB]Q;Jɽdw.Qjx+=gPPZFǤٱO7Ӫl'C`5*2YwILn2deT1PIIC⍀J8@[)pp49;vPf` QUʏ媀(JV4Yߢbpk? FK@֩ƨb0,?5=.ٮ(4a (k$^87_PM@ k.n8vIњ 15og5/%p+6&ED\e #Ӆ:T[/&i7mЉl8q@(ض4j2&3,sQ5jHӇ:lP uH-pQ j5\G Kmf02bM&pON(ɉ {Xk a*uզu( ψb.Aj3=/"Em0}7giEG/L(e߳zQQ^+*؅>%OZ:!j+<$;%>0u&b^u m,U?ZQjou1?ݣ,FǾ0ޅ9`$e^0bswnVJcQ=tRVxL^sSz3{+B34_C-Lc@|[$u+0ݮk.νor+,U"} 20%DK kjW2,%puKbuDROKT GIy>=&5x7n6{[lw8P A'Qhwj+^B99teX2& zI6S߶QX`_ rITZESNWJJzsqk-)lJ?+F1I1A64`^kN3UlZ+J(pt,0ZT6d^@+JK, ,IQ%9CُTZgJ&a F> 0{ºJhyb2\B\2&J~`߈aDzbs]0ؾtݰqKWS\NyQbکxx$yXU@o&z02lкGsVD,.5R4|Fy9u[)X`Bem9U]d7UYrlX3&9i+78ìZۮp54t@ؠ·x5RT-yd6/` B%})ȓZY ߗb~~K|q1V† -Crl)W'RHʧ9 ~Z28ct;5\2^yTh^r Y1\e`&ZPm&j@4԰@ ].IA5!$ムP}]Ycj=Y@*j骜^-*m[pUbީVyUUt4K, E sV K ʔʡ;3jY7 )EH]Օ\25+Xo6\s)Bh~vHs>xᲄ?ZɿyMg̭ǛJiE+:5Wn9Fal"Wg%fn `|WgF-DD(o\bnb;]9iFp,"& [Ag5Ng:Ԉu.s/9Ȯ7$BN͢ ;{G֘QH>ZAtt!Pߡ%f x[?S~EIHTL}vQ#es* /~&&@^Md(km]8>CټƏ٤ D4~9qcµCH(`pL"^'QNCFzx -f ^ݕGjX)ON+rx9}tㇷ a?6.7%? w!cy09iA5O}lXVAHȉV+!g̞ˊS7cey 0w95 =ڬvrS귣>ϴ#ٍ3}c*.w\5UUZ\::'W o*_< ^!*\f;X7r甛SԐLi`2?ʆ҃\`CI*ط뷩,9v[Ro1!,Z,.Rlڅ|fQMk^`pvB#{@{(0>)}ϵLka+]ʕYS{e62M%.F.Vޗ`S龬x,Y6rq֮"EXfZqݸBmn4<bp3L $ϣQ.թ?[",-;[M~̓! akpPѠU5E!-WQZF$]>R>|rv,5sބwS7YpU%@[^#0CR<*TN7 f'lx1_%KSH'-s]+Fؒ\ۯ_bz`iF.⻨VR~`/r^1 ~-=\ -С"܈ @r]h,C2VS.΅"XJB+FjSs᦬R^4,8LKPΏž*D|Lg!0 ԬkhAsZ/UDk:?Yܤd#>/,NgٶM6gvU4,Ęxwu60 {pf-(Hfrl/MݟTߣ?0X64R4]`ܡSiѴ mf WoN0 ik'_Ila)Y)vX.Lޓ? l }-BI8 #儻TfQBy !*NRr rӠE{sG=c 1u~o^J\ SM0FYi5\ % 9^sWe<9NeMmPOGg~ OUXEo֡({jnBw0.z0XTu6f?LQ+wdJM3t4cMSvE]OFCqdpY$+֠s) ]@ ;+V @ε_X'&Yؕ\gS%8k I"3q\OJIƇȡg}ǥ$Nt^ARtpY-۝l>w%f xX yh*3nq+ՙFX..5`)(I'PD8#ъ0,,%pV 1̐b#ۈYH~w[O p/[q ?<)* .ho6Y\nͤbJGbf 7>CǼ752J=[jԴFF:ڀ[w|x~ . +ޒ6B;Dѱ|svG\}딫wӨ/JnNG'YѐmOhDiQdcGP) Xvۤ?Ҙ_'}&%Ԥ8rf@y}~a#z6T0HKv1/hQ)骊Ȯ<41Jm!FQH2 x|` ȹZ2]GtJ>: DF1>HS-ܳak ;)!>x>3W/'GM7I7R &/2z?,xS:--ψļV!{)R aG8Zb%'Q^W_4Zñ7]U~SX5nKPspf>GMJqB'uh[ݷsp8@'A:d'rnAqhlKqWvX坧ja-(I]h"}TrKbAA+w XlA1$hg޵xТ 9Ɛ4!%7o5xr/֫<l-NsET;lǃȆ1Kیs#Q:rWimizb451.4+ aL鄿//-R:~"esfJM6AfkFI'g^ .PsvH7@Oٜ#_uD)D5wa8,] |]XDo|4퀀qr_ӣf)IM1WMAƔZ'|P28=ښZy譹ܬ@:>@/zOoӃSl,NR 瓞%Z>a.d0GJvMokBKڬBNO6R)}>4e:g~'r@IM% H  =2^C3~^/0rav_r uiJ*լI&6rIzpZ%ѳQ..~{D'2ſmU1` ^m[fɪxw*(Z UfmTXi~ r^ kzK00\b/}-ξPJ-;44$P-5R65z$hr`1Riֱu[qwZFvrDL& {VtYG2T6D(}YGM n*)`BFHX`i2pfdx:z`{4Ĥ8^qW7HڭHw'y˸QT@+Sp^*K4};Ϸ=Η>{zV׶}"aTZT͞4sH;[I\7ၲɽHNS=*"e<MGU )# xYC_62M۬\,t P%rTo*FQ)jޱ+.?jlTeϨ}CtN5$F^H'!xFj]跑i7WpG%iL:D8v.ϩ1d\{:@a El1e Sy"!9`>#ǧ깁i銕VX` 8'@a^LVi8}BYk+XCL"b9RKnw%A3qA(s-r~ Q?dQ}z;]INdu&mGˈ ex}Y}O?d<9<2Y@AJ":D;0@SK]듇V_;,T&ٹVNkhγZaƏt7ZVۇfG͆JL' [X lSLmү8Z ىVh2(k^ eyP~K,s }ҞS:@bۤo#r"yYV$o7Vw+^miyj~iN4l݁)&a;̹{`b, =(IJBWAk۞AsMe))i4[*J9ǟH~p?w M=POhVn=(Z1Gk*в2`..' M5P$|7^4Xa#׍nWtZGн@n{ʛ ơm\i@,e14S_ճ{p #:(eޠW}WUafhZ?X|[G\{nQn>[<Agz:b/2]Q~cg4yo@%Kr+oŞ+í"eأ_k0ב,-N@W8\_v,("fYT c pڛgjVނ=#c.cs/<[;S?YJ+ ]zΨ3rKN%#PU*lN3*o(GT'twܻ-`7)o=FE["Ͷ!R*Q%{P'@ ӷ)SN?b9 AN6 4ruKZrW{(2n  ގ4O7IϪ2Ý#:E1lP B\*Mʚ}n6f boD@Um_%J~D)RH՜,k繰LJW%oai_lǞ`oY|äMp* VvHn%VʋNvW.8LSɈPV#3_©]/w[7H:ti H}]@@.N|=07ivU'~?P._D+kFq$@(@C1Z8o_m(ØwJmoJcڈ9PGfԉhjfN(3a*疏ΌZA( o)B"u' L|pC2MP#Ս[wɐit!ĵ:]ʅ&nP:!otxɵ 5U3lǴP,83_+5Yh(Yn3\z7z$-h_C@rϚ}@!<H5~ 5JhBY[h;N7*un @P˧a|lJJ\mΐ=Oڝ~8| M},8/ ʹ>!mծLTϨ聳* oVUbF-ʭW{@A5So()$(s#e蒙/{RN.M2j];;:*D2w8HF ]wy*c<7w LҺ%㮻llhΧP9:CRhRue-]a䄵b҅2>b(l_)b#:?+OX{s.Mťٗ<ÕA bH9T\ٮW@'3զ> lxgija^, ;UBnVf9F5|˹+p{{4u&<,ƮʹMmN0)éۘ `S`,dPPӹL?]@[qJ;N#+Wؒh[ 7͖Gu F:$fW}6`wUܐ}x6XP쀐v.oy6/0d|%z DJ))q$*hvʷ|Vgl離~$3ёmykVPZh @h$iѤuGвAUmK";OX 7E`6P O e5{Z j[[W@і94lF)vƤAe*sJuZݛ7?r"Po&iPW tUkENщto7޼} JwˌtlQ MÙ:ܕ yL.&i;4ڥk+ 0}HD:?Q3\Jݷm{M T4R5(oj@Lm<9*Ԋ]YT/akwo^b\")Z]Ʀ6C{\E*v`PE&;;h YZLtk儺 1"4hm]^_k7s_˃mu*IVe" FҘ6n0tzO\amZ&K,&Q5STCf|V' Ln+ԭ3vnsiKU~VTE3(=$H9UK閃UH#Kݱ|@S0UjFR3p]Z# -H]!~]TѦ a]lQ}w*r֮6͹N,&?=03,1Pwc>uF@#K5) _3=*nbF_L' oqWC_R1,'qWz!/@wF:D]2CA1`E:YG` eoY#6o4vnKRNɒ]:++RU!ThtԀD:AU=ۥ$ݪב% csu)  4Ǵk.mKDIX-ާ 2x&HzZbB^85eOS_Cv{}זz0qs3kqIҔf-F!F⍉\C }x&0)&iջRߟGCAbZJC۪[5;G劸"nn$Z $6RT ƃŁZ=دѪ/:.FܧYC-~Î.kIl07<:ֵM?t98hذh(\) Û5u]U+,5/Ugu7Y_]a=J1ryZ,Kfo a Դ5FRN^eW2K3j{)@X˔C5kx52LU6Ç-z}?l%хJF,뼸7C]fι)"9۫uAܯ-[&8Qnn,soRJo۩~TqFPnM c<֨ 4UKY7,'vKe qorx"K%0݊@ k KC|RyR-)57rR:s,%)? /N8$ULהm@+Ґ̆MqmYDDqk#VR.H~o%"u?"h%ϰ*k:[10M4Pl`ZM~>S@Qτa^N%IʈdF)i`d tsppw^9l.hޥw"~mA x$ YԠ-q7̂\NLeI/e [K0lLMj_e6VPp~i4eԅ:_2eXĖɊI.IWIhj,*FKwq/H?w#Ρ@: Rxoh!d.$#g =}9۹ͤTV.0Gt'BNs?&R( yCCƢnrcfE@ܪ/lf6?Ȥâඕt(f@W .VSF궜5tăRuVWBn$#6-jg%v1h܈?p y`C!|:{ņ q|2)-m[ǹ]+N8dA(6@ .+ +pKQH<&zfg>I?v2 "B `zXvTBfH WoI1wU͐}|)ϭ^v>HE)(HCU)#$uRVz}4r81H.| w0- Bx[`}"| -(jBA(;\D]ıqȂ,@r%Ku(@&&cyY3q;}"|Xkbd(}QȀhZ",Zt1b`]}8 R~N `Bll|L4KgϏ'鐸^"H7ib"MNg%i-V/ '8 OgBP1^ dPMdhe.n N̆>bEC>|e|F=%#ye4Ұ ]~\< &핹INrvxx8) Mo?XlWDDdۍAp"@{JÄ&1[cN]rNK#3Z5!$'µsn@ʔWX2d;dS Uɉ4z$rS͖)n٫u9D]t$M ZkOý4/gvJ7OӖLf=iJlW@~t[עс%qPM*2Υ78秲l}Rj@/^TF|Bkn!gqcׂ[BHܰ@U%璐\_"Hk*#Qqݭ)-v/Y^ALx)?[b-dNxTQejpux!-\ڕ5oؠUc z,{K 0I3.aٟmtqwMqפ\Q]dvI=7o@ÚYh,Yh@IKզn,6*͸Wăgqx!d2aZ>|)6mRҨnU];\k6mfq>b 9:? Źpgp-!L2#ygPє GgDU0JA Ѩ-cnA՜)gq1pcݤqƧ`^n [ _s46ǴN0ՙHMMξƝ>ǐcy Y!|bgit +iu}xu31˪>zgh{# <ڵ!CŲȞ]ߡG 7~?yL./Ecsb3ELyOq/8 RGa;Ѿ)CMf}˚E)qR6c%1.9 J2Wd D7Ȏ:t|74d\>1JY##ۣ~>OGs)~.r:8ۍ.rDtԉ6^kKW 8É.F[YI9O!Ǝ]ix]|-lQ&(#d=5xFgԈb8wmnjn+qmJmφ65pnpz F ĺbGOP-vvȪI.oW4UQk+&qOƫR-*Q͟f+Ґx"_ȎP}-g;JX f f6$/M@!`rS;3=h/Rqb9)7,jZcV9_?N17`Qr3.o@6Nˉ.B}0Y?ED&%& nhI.zz??(^j]u*iVi~_$|Ib*V+VmVǿn:CQs l*W{~-࣍%JuEv'G9fQ#2{.a)nx6/[fJgV:7aʟ"mB'Kt?#L(_ g͕۠.+%!&q^0M "E8ƕ| ag꾂,|?y;mA  eD tTDп.lǵ h>[=psYMC߱EFPXeޘE@xI#—nvP $$ŝ2*ח0h7T[|%#Ʉ`g4;5 Dˏ" }5Jb p׃B.V=*:T ƋaISnUYYAeX=&q0HK;A~rack-3.1.12/contrib/rdoc.css000066400000000000000000000162351476365375000156460ustar00rootroot00000000000000/* Forked from the Darkfish templates rdoc.css file, much hacked, probably * imperfect */ html { max-width: 960px; margin: 0 auto; } body { font: 14px "Helvetica Neue", Helvetica, Tahoma, sans-serif; } body.file-popup { font-size: 90%; margin-left: 0; } h1 { color: #4183C4; } :link, :visited { color: #4183C4; text-decoration: none; } :link:hover, :visited:hover { border-bottom: 1px dotted #4183C4; } pre, pre.description { font: 12px Monaco,"Courier New","DejaVu Sans Mono","Bitstream Vera Sans Mono",monospace; padding: 1em; overflow: auto; line-height: 1.4; } /* @group Generic Classes */ .initially-hidden { display: none; } #search-field { width: 98%; } .missing-docs { font-size: 120%; background: white url(images/wrench_orange.png) no-repeat 4px center; color: #ccc; line-height: 2em; border: 1px solid #d00; opacity: 1; text-indent: 24px; letter-spacing: 3px; font-weight: bold; -webkit-border-radius: 5px; -moz-border-radius: 5px; } .target-section { border: 2px solid #dcce90; border-left-width: 8px; background: #fff3c2; } /* @end */ /* @group Index Page, Standalone file pages */ .indexpage ul { line-height: 160%; list-style: none; } .indexpage ul :link, .indexpage ul :visited { font-size: 16px; } .indexpage li { padding-left: 20px; } .indexpage ul > li { background: url(images/bullet_black.png) no-repeat left 4px; } .indexpage li.method { background: url(images/plugin.png) no-repeat left 4px; } .indexpage li.module { background: url(images/package.png) no-repeat left 4px; } .indexpage li.class { background: url(images/ruby.png) no-repeat left 4px; } .indexpage li.file { background: url(images/page_white_text.png) no-repeat left 4px; } .indexpage li li { background: url(images/tag_blue.png) no-repeat left 4px; } .indexpage li .toc-toggle { width: 16px; height: 16px; background: url(images/add.png) no-repeat; } .indexpage li .toc-toggle.open { background: url(images/delete.png) no-repeat; } /* @end */ /* @group Top-Level Structure */ .project-section, #file-metadata, #class-metadata { display: block; background: #f5f5f5; margin-bottom: 1em; padding: 0.5em; } .project-section h3, #file-metadata h3, #class-metadata h3 { margin: 0; } #metadata { float: left; width: 280px; } #documentation { margin: 2em 1em 2em 300px; } #validator-badges { clear: both; margin: 1em 1em 2em; font-size: smaller; } /* @end */ /* @group Metadata Section */ #metadata ul, #metadata dl, #metadata p { padding: 0px; list-style: none; } dl.svninfo { color: #666; margin: 0; } dl.svninfo dt { font-weight: bold; } ul.link-list li { white-space: nowrap; } ul.link-list .type { font-size: 8px; text-transform: uppercase; color: white; background: #969696; } /* @end */ /* @group Documentation Section */ .note-list { margin: 8px 0; } .label-list { margin: 8px 1.5em; border: 1px solid #ccc; } .description .label-list { font-size: 14px; } .note-list dt { font-weight: bold; } .note-list dd { } .label-list dt { font-weight: bold; background: #ddd; } .label-list dd { } .label-list dd + dt, .note-list dd + dt { margin-top: 0.7em; } #documentation .section { font-size: 90%; } #documentation h2.section-header { color: #333; font-size: 175%; } .documentation-section-title { position: relative; } .documentation-section-title .section-click-top { position: absolute; top: 6px; right: 12px; font-size: 10px; visibility: hidden; } .documentation-section-title:hover .section-click-top { visibility: visible; } #documentation h3.section-header { color: #333; font-size: 150%; } #constants-list > dl, #attributes-list > dl { margin: 1em 0 2em; border: 0; } #constants-list > dl dt, #attributes-list > dl dt { font-weight: bold; font-family: Monaco, "Andale Mono"; background: inherit; } #constants-list > dl dt a, #attributes-list > dl dt a { color: inherit; } #constants-list > dl dd, #attributes-list > dl dd { margin: 0 0 1em 0; color: #666; } .documentation-section h2 { position: relative; } .documentation-section h2 a { position: absolute; top: 8px; right: 10px; font-size: 12px; color: #9b9877; visibility: hidden; } .documentation-section h2:hover a { visibility: visible; } /* @group Method Details */ #documentation .method-source-code { display: none; } #documentation .method-detail { margin: 0.2em 0.2em; padding: 0.5em; } #documentation .method-detail:hover { background-color: #f5f5f5; } #documentation .method-heading { cursor: pointer; position: relative; font-size: 125%; line-height: 125%; font-weight: bold; color: #333; background: url(images/brick.png) no-repeat left bottom; padding-left: 20px; } #documentation .method-heading :link, #documentation .method-heading :visited { color: inherit; } #documentation .method-click-advice { position: absolute; right: 5px; font-size: 10px; color: #aaa; visibility: hidden; background: url(images/zoom.png) no-repeat right 5px; padding-right: 20px; overflow: show; } #documentation .method-heading:hover .method-click-advice { visibility: visible; } #documentation .method-alias .method-heading { color: #666; background: url(images/brick_link.png) no-repeat left bottom; } #documentation .method-description, #documentation .aliases { margin: 0 20px; color: #666; } #documentation .method-description p, #documentation .aliases p { line-height: 1.2em; } #documentation .aliases { font-style: italic; cursor: default; } #documentation .method-description p { margin-bottom: 0.5em; } #documentation .method-description ul { margin-left: 1.5em; } #documentation .attribute-method-heading { background: url(images/tag_green.png) no-repeat left bottom; } #documentation #attribute-method-details .method-detail:hover { background-color: transparent; cursor: default; } #documentation .attribute-access-type { font-size: 60%; text-transform: uppercase; vertical-align: super; } .method-section .method-source-code { background: white; } /* @group Source Code */ .ruby-constant .ruby-keyword .ruby-ivar .ruby-operator .ruby-identifier .ruby-node .ruby-comment .ruby-regexp .ruby-value { background: transparent; } /* Thanks GitHub!!! */ .ruby-constant { color: #458; font-weight: bold; } .ruby-keyword { color: black; font-weight: bold; } .ruby-ivar { color: teal; } .ruby-operator { color: #000; } .ruby-identifier { color: black; } .ruby-node { color: red; } .ruby-comment { color: #998; font-weight: bold; } .ruby-regexp { color: #009926; } .ruby-value { color: #099; } .ruby-string { color: red; } /* @group search results */ #search-section .section-header { margin: 0; padding: 0; } #search-results { width: 100%; list-style: none; margin: 0; padding: 0; } #search-results h1 { font-size: 1em; font-weight: normal; text-shadow: none; } #search-results .current { background: #eee; } #search-results li { list-style: none; line-height: 1em; padding: 0.5em; border-bottom: 1px solid black; } #search-results .search-namespace { font-weight: bold; } #search-results li em { background: yellow; font-style: normal; } #search-results pre { margin: 0.5em; } rack-3.1.12/lib/000077500000000000000000000000001476365375000133045ustar00rootroot00000000000000rack-3.1.12/lib/rack.rb000066400000000000000000000045121476365375000145530ustar00rootroot00000000000000# 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 :BadRequest, "rack/bad_request" autoload :BodyProxy, "rack/body_proxy" autoload :Builder, "rack/builder" autoload :Cascade, "rack/cascade" 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 :Deflater, "rack/deflater" autoload :Directory, "rack/directory" autoload :ETag, "rack/etag" autoload :Events, "rack/events" autoload :Files, "rack/files" autoload :ForwardRequest, "rack/recursive" 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 :MockRequest, "rack/mock_request" autoload :MockResponse, "rack/mock_response" autoload :Multipart, "rack/multipart" autoload :NullLogger, "rack/null_logger" autoload :QueryParser, "rack/query_parser" autoload :Recursive, "rack/recursive" autoload :Reloader, "rack/reloader" autoload :Request, "rack/request" autoload :Response, "rack/response" autoload :RewindableInput, "rack/rewindable_input" autoload :Runtime, "rack/runtime" autoload :Sendfile, "rack/sendfile" 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" module Auth autoload :Basic, "rack/auth/basic" autoload :AbstractHandler, "rack/auth/abstract/handler" autoload :AbstractRequest, "rack/auth/abstract/request" end end rack-3.1.12/lib/rack/000077500000000000000000000000001476365375000142245ustar00rootroot00000000000000rack-3.1.12/lib/rack/auth/000077500000000000000000000000001476365375000151655ustar00rootroot00000000000000rack-3.1.12/lib/rack/auth/abstract/000077500000000000000000000000001476365375000167705ustar00rootroot00000000000000rack-3.1.12/lib/rack/auth/abstract/handler.rb000066400000000000000000000015221476365375000207320ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/auth/abstract/request.rb000066400000000000000000000015251476365375000210100ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/auth/basic.rb000066400000000000000000000022341476365375000165740ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'abstract/handler' require_relative 'abstract/request' 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 ||= params.unpack1('m').split(':', 2) end def username credentials.first end end end end end rack-3.1.12/lib/rack/bad_request.rb000066400000000000000000000002441476365375000170470ustar00rootroot00000000000000# frozen_string_literal: true module Rack # Represents a 400 Bad Request error when input data fails to meet the # requirements. module BadRequest end end rack-3.1.12/lib/rack/body_proxy.rb000066400000000000000000000031221476365375000167450ustar00rootroot00000000000000# 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) case method_name when :to_str false else super or @body.respond_to?(method_name, include_all) end 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) case method_name when :to_str super when :to_ary begin @body.__send__(method_name, *args, &block) ensure close end else @body.__send__(method_name, *args, &block) end end # :nocov: ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) # :nocov: end end rack-3.1.12/lib/rack/builder.rb000066400000000000000000000217761476365375000162140ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'urlmap' module Rack; end Rack::BUILDER_TOPLEVEL_BINDING = ->(builder){builder.instance_eval{binding}} 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 # # is a 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 is a Rack application def self.parse_file(path, **options) if path.end_with?('.ru') return self.load_file(path, **options) 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, **options) 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, **options) 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, path = "(rackup)", **options) builder = self.new(**options) # 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.call(builder) eval(builder_script, binding, path) 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, **options, &block) @use = [] @map = nil @run = default_app @warmup = nil @freeze_app = false @options = options instance_eval(&block) if block_given? end # Any options provided to the Rack::Builder instance at initialization. # These options can be server-specific. Some general options are: # # * +:isolation+: One of +process+, +thread+ or +fiber+. The execution # isolation model to use. attr :options # 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 rack-3.1.12/lib/rack/cascade.rb000066400000000000000000000042441476365375000161400ustar00rootroot00000000000000# 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 # 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 rack-3.1.12/lib/rack/common_logger.rb000066400000000000000000000062121476365375000174010ustar00rootroot00000000000000# 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 } # +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:]]/) { |c| sprintf("\\x%x", c.ord) } msg[-1] = "\n" 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 rack-3.1.12/lib/rack/conditional_get.rb000066400000000000000000000057651476365375000177300ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/config.rb000066400000000000000000000006321476365375000160170ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/constants.rb000066400000000000000000000051701476365375000165700ustar00rootroot00000000000000# 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' CONNECT = 'CONNECT' LINK = 'LINK' UNLINK = 'UNLINK' TRACE = 'TRACE' # Rack environment variables RACK_VERSION = 'rack.version' RACK_TEMPFILES = 'rack.tempfiles' RACK_EARLY_HINTS = 'rack.early_hints' 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_PAIRS = 'rack.request.form_pairs' 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 rack-3.1.12/lib/rack/content_length.rb000066400000000000000000000014461476365375000175710ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/content_type.rb000066400000000000000000000012671476365375000172720ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/deflater.rb000066400000000000000000000130061476365375000163370ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/directory.rb000066400000000000000000000140301476365375000165530ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/etag.rb000066400000000000000000000035671476365375000155040ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/events.rb000066400000000000000000000114641476365375000160630ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/files.rb000066400000000000000000000132451476365375000156600ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/head.rb000066400000000000000000000010141476365375000154460ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/headers.rb000066400000000000000000000113021476365375000161610ustar00rootroot00000000000000# frozen_string_literal: true 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 KNOWN_HEADERS = {} %w( Accept-CH Accept-Patch Accept-Ranges Access-Control-Allow-Credentials Access-Control-Allow-Headers Access-Control-Allow-Methods Access-Control-Allow-Origin Access-Control-Expose-Headers Access-Control-Max-Age Age Allow Alt-Svc Cache-Control Connection Content-Disposition Content-Encoding Content-Language Content-Length Content-Location Content-MD5 Content-Range Content-Security-Policy Content-Security-Policy-Report-Only Content-Type Date Delta-Base ETag Expect-CT Expires Feature-Policy IM Last-Modified Link Location NEL P3P Permissions-Policy Pragma Preference-Applied Proxy-Authenticate Public-Key-Pins Referrer-Policy Refresh Report-To Retry-After Server Set-Cookie Status Strict-Transport-Security Timing-Allow-Origin Tk Trailer Transfer-Encoding Upgrade Vary Via WWW-Authenticate Warning X-Cascade X-Content-Duration X-Content-Security-Policy X-Content-Type-Options X-Correlation-ID X-Correlation-Id X-Download-Options X-Frame-Options X-Permitted-Cross-Domain-Policies X-Powered-By X-Redirect-By X-Request-ID X-Request-Id X-Runtime X-UA-Compatible X-WebKit-CS X-XSS-Protection ).each do |str| downcased = str.downcase.freeze KNOWN_HEADERS[str] = KNOWN_HEADERS[downcased] = downcased end 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(KNOWN_HEADERS[key] || 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) ? KNOWN_HEADERS[key] || key.downcase : key end end end rack-3.1.12/lib/rack/lint.rb000066400000000000000000001177641476365375000155370ustar00rootroot00000000000000# frozen_string_literal: true require 'forwardable' require 'uri' 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 REQUEST_PATH_ORIGIN_FORM = /\A\/[^#]*\z/ REQUEST_PATH_ABSOLUTE_FORM = /\A#{Utils::URI_PARSER.make_regexp}\z/ REQUEST_PATH_AUTHORITY_FORM = /\A[^\/:]+:\d+\z/ REQUEST_PATH_ASTERISK_FORM = '*' 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) ## 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_header(@status, @headers) check_content_length_header(@status, @headers) check_rack_protocol_header(@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. ## rack.protocol:: An optional +Array+ of +String+, containing ## the protocols advertised by the client in ## the +upgrade+ header (HTTP/1) or the ## +:protocol+ pseudo-header (HTTP/2). if protocols = @env['rack.protocol'] unless protocols.is_a?(Array) && protocols.all?{|protocol| protocol.is_a?(String)} raise LintError, "rack.protocol must be an Array of Strings" end end ## 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.errors].each do |header| raise LintError, "env missing required key #{header}" unless env.include? header end ## 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 ## 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 may be a valid input stream in rack.input. if rack_input = env[RACK_INPUT] check_input_stream(rack_input) @env[RACK_INPUT] = InputWrapper.new(rack_input) end ## * There must be a valid error stream in rack.errors. rack_errors = env[RACK_ERRORS] check_error_stream(rack_errors) @env[RACK_ERRORS] = ErrorWrapper.new(rack_errors) ## * There may be a valid hijack callback in rack.hijack check_hijack env ## * There may be a valid early hints callback in rack.early_hints check_early_hints 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 provided, must be a valid request target or an empty string. if env.include?(PATH_INFO) case env[PATH_INFO] when REQUEST_PATH_ASTERISK_FORM ## * Only OPTIONS requests may have PATH_INFO set to * (asterisk-form). unless env[REQUEST_METHOD] == OPTIONS raise LintError, "Only OPTIONS requests may have PATH_INFO set to '*' (asterisk-form)" end when REQUEST_PATH_AUTHORITY_FORM ## * Only CONNECT requests may have PATH_INFO set to an authority (authority-form). Note that in HTTP/2+, the authority-form is not a valid request target. unless env[REQUEST_METHOD] == CONNECT raise LintError, "Only CONNECT requests may have PATH_INFO set to an authority (authority-form)" end when REQUEST_PATH_ABSOLUTE_FORM ## * CONNECT and OPTIONS requests must not have PATH_INFO set to a URI (absolute-form). if env[REQUEST_METHOD] == CONNECT || env[REQUEST_METHOD] == OPTIONS raise LintError, "CONNECT and OPTIONS requests must not have PATH_INFO set to a URI (absolute-form)" end when REQUEST_PATH_ORIGIN_FORM ## * Otherwise, PATH_INFO must start with a / and must not include a fragment part starting with '#' (origin-form). when "" # Empty string is okay. else raise LintError, "PATH_INFO must start with a '/' and must not include a fragment part starting with '#' (origin-form)" end 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_stream(input) ## When applicable, its external encoding must be "ASCII-8BIT" and it ## must be opened in binary mode. 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 ## any remaining input is not needed. def close(*args) @input.close(*args) end end ## ## === The Error Stream ## def check_error_stream(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 ## ## === Early Hints ## ## The application or any middleware may call the rack.early_hints ## with an object which would be valid as the headers of a Rack response. def check_early_hints(env) if env[RACK_EARLY_HINTS] ## ## If rack.early_hints is present, it must respond to #call. unless env[RACK_EARLY_HINTS].respond_to?(:call) raise LintError, "rack.early_hints must respond to call" end original_callback = env[RACK_EARLY_HINTS] env[RACK_EARLY_HINTS] = lambda do |headers| ## If rack.early_hints is called, it must be called with ## valid Rack response headers. check_headers(headers) original_callback.call(headers) end end 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+ Header ## def check_content_type_header(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+ Header ## def check_content_length_header(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 +rack.protocol+ Header ## def check_rack_protocol_header(status, headers) ## If the +rack.protocol+ header is present, it must be a +String+, and ## must be one of the values from the +rack.protocol+ array from the ## environment. protocol = headers['rack.protocol'] if protocol request_protocols = @env['rack.protocol'] if request_protocols.nil? raise LintError, "rack.protocol header is #{protocol.inspect}, but rack.protocol was not set in request!" elsif !request_protocols.include?(protocol) raise LintError, "rack.protocol header is #{protocol.inspect}, but should be one of #{request_protocols.inspect} from the request!" end end end ## ## Setting this value informs the server that it should perform a ## connection upgrade. In HTTP/1, this is done using the +upgrade+ ## header. In HTTP/2, this is done by accepting the request. ## ## === 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 ## ## 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. rack-3.1.12/lib/rack/lock.rb000066400000000000000000000010751476365375000155040ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/logger.rb000066400000000000000000000007551476365375000160370ustar00rootroot00000000000000# frozen_string_literal: true require 'logger' require_relative 'constants' warn "Rack::Logger is deprecated and will be removed in Rack 3.2.", uplevel: 1 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 rack-3.1.12/lib/rack/media_type.rb000066400000000000000000000035031476365375000166720ustar00rootroot00000000000000# frozen_string_literal: true module Rack # Rack::MediaType parse media type and parameters out of content_type string class MediaType SPLIT_PATTERN = /[;,]/ 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 if type = content_type.split(SPLIT_PATTERN, 2).first type.rstrip! type.downcase! type end 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' } # # This will pass back parameters with empty strings in the hash if they # lack a value (e.g., "text/plain;charset=" will return { 'charset' => '' }, # and "text/plain;charset" will return { 'charset' => '' }, similarly to # the query params parser (barring the latter case, which returns nil instead)). def params(content_type) return {} if content_type.nil? content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh| s.strip! k, v = s.split('=', 2) k.downcase! hsh[k] = strip_doublequotes(v) end end private def strip_doublequotes(str) (str && str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str || '' end end end end rack-3.1.12/lib/rack/method_override.rb000066400000000000000000000027101476365375000177300ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/mime.rb000066400000000000000000001012201476365375000154740ustar00rootroot00000000000000# 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" => "text/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", ".mjs" => "text/javascript", ".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" => "font/otf", ".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" => "font/ttf", ".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" => "font/woff", ".woff2" => "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 rack-3.1.12/lib/rack/mock.rb000066400000000000000000000000771476365375000155060ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'mock_request' rack-3.1.12/lib/rack/mock_request.rb000066400000000000000000000124231476365375000172540ustar00rootroot00000000000000# 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 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 = {} 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 rack_input = opts[:input] if String === rack_input rack_input = StringIO.new(rack_input) end if rack_input rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) env[RACK_INPUT] = rack_input env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size) end opts.each { |field, value| env[field] = value if String === field } env end end end rack-3.1.12/lib/rack/mock_response.rb000066400000000000000000000063061476365375000174250ustar00rootroot00000000000000# 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 set_cookie_header = headers['set-cookie'] if set_cookie_header && !set_cookie_header.empty? Array(set_cookie_header).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 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 rack-3.1.12/lib/rack/multipart.rb000066400000000000000000000035541476365375000166010ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'constants' require_relative 'utils' require_relative 'multipart/parser' require_relative 'multipart/generator' require_relative 'bad_request' 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 MissingInputError < StandardError include BadRequest end # Accumulator for multipart form data, conforming to the QueryParser API. # In future, the Parser could return the pair list directly, but that would # change its API. class ParamList # :nodoc: def self.make_params new end def self.normalize_params(params, key, value) params << [key, value] end def initialize @pairs = [] end def <<(pair) @pairs << pair end def to_params_hash @pairs end end class << self def parse_multipart(env, params = Rack::Utils.default_query_parser) unless io = env[RACK_INPUT] raise MissingInputError, "Missing input stream!" end 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 rack-3.1.12/lib/rack/multipart/000077500000000000000000000000001476365375000162455ustar00rootroot00000000000000rack-3.1.12/lib/rack/multipart/generator.rb000066400000000000000000000047061476365375000205670ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/multipart/parser.rb000066400000000000000000000367171476365375000201040ustar00rootroot00000000000000# frozen_string_literal: true require 'strscan' require_relative '../utils' require_relative '../bad_request' module Rack module Multipart class MultipartPartLimitError < Errno::EMFILE include BadRequest end class MultipartTotalPartLimitError < StandardError include BadRequest end # Use specific error class when parsing multipart request # that ends early. class EmptyContentError < ::EOFError include BadRequest end # Base class for multipart exceptions that do not subclass from # other exception classes for backwards compatibility. class BoundaryTooLongError < StandardError include BadRequest end # Prefer to use the BoundaryTooLongError class or Rack::BadRequest. Error = BoundaryTooLongError EOL = "\r\n" MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:(.*)(?=#{EOL}(\S|\z))/ni MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni class Parser BUFSIZE = 1_048_576 TEXT_PLAIN = "text/plain" TEMPFILE_FACTORY = lambda { |filename, content_type| extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129] Tempfile.new(["RackMultipart", extension]) } 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 BoundaryTooLongError, "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 @body_regex_at_end = /#{@body_regex}\z/m @end_boundary_size = boundary.bytesize + 4 # (-- at start, -- at finish) @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 if @sbuf.pos == @end_boundary_size && @sbuf.rest == EOL # stop parsing a buffer if a buffer is only an end boundary. @state = :DONE return end # 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 CONTENT_DISPOSITION_MAX_PARAMS = 16 CONTENT_DISPOSITION_MAX_BYTES = 1536 def handle_mime_head if @sbuf.scan_until(@head_regex) head = @sbuf[1] content_type = head[MULTIPART_CONTENT_TYPE, 1] if (disposition = head[MULTIPART_CONTENT_DISPOSITION, 1]) && disposition.bytesize <= CONTENT_DISPOSITION_MAX_BYTES # ignore actual content-disposition value (should always be form-data) i = disposition.index(';') disposition.slice!(0, i+1) param = nil num_params = 0 # Parse parameter list while i = disposition.index('=') # Only parse up to max parameters, to avoid potential denial of service num_params += 1 break if num_params > CONTENT_DISPOSITION_MAX_PARAMS # Found end of parameter name, ensure forward progress in loop param = disposition.slice!(0, i+1) # Remove ending equals and preceding whitespace from parameter name param.chomp!('=') param.lstrip! if disposition[0] == '"' # Parameter value is quoted, parse it, handling backslash escapes disposition.slice!(0, 1) value = String.new while i = disposition.index(/(["\\])/) c = $1 # Append all content until ending quote or escape value << disposition.slice!(0, i) # Remove either backslash or ending quote, # ensures forward progress in loop disposition.slice!(0, 1) # stop parsing parameter value if found ending quote break if c == '"' escaped_char = disposition.slice!(0, 1) if param == 'filename' && escaped_char != '"' # Possible IE uploaded filename, append both escape backslash and value value << c << escaped_char else # Other only append escaped value value << escaped_char end end else if i = disposition.index(';') # Parameter value unquoted (which may be invalid), value ends at semicolon value = disposition.slice!(0, i) else # If no ending semicolon, assume remainder of line is value and stop # parsing disposition.strip! value = disposition disposition = '' end end case param when 'name' name = value when 'filename' filename = value when 'filename*' filename_star = value # else # ignore other parameters end # skip trailing semicolon, to proceed to next parameter if i = disposition.index(';') disposition.slice!(0, i+1) end end else name = head[MULTIPART_CONTENT_ID, 1] end if filename_star encoding, _, filename = filename_star.split("'", 3) filename = normalize_filename(filename || '') filename.force_encoding(find_encoding(encoding)) elsif filename filename = normalize_filename(filename) end 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_at_end, '') # 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 normalize_filename(filename) if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) } filename = Utils.unescape_path(filename) end filename.scrub! filename.split(/[\/\\]/).last || String.new 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 = find_encoding(v) end end end end name.force_encoding(encoding) body.force_encoding(encoding) end # Return the related Encoding object. However, because # enc is submitted by the user, it may be invalid, so # use a binary encoding in that case. def find_encoding(enc) Encoding.find enc rescue ArgumentError Encoding::BINARY end def handle_empty_content!(content) if content.nil? || content.empty? raise EmptyContentError end end end end end rack-3.1.12/lib/rack/multipart/uploaded_file.rb000066400000000000000000000024441476365375000213720ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/null_logger.rb000066400000000000000000000022751476365375000170700ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/query_parser.rb000066400000000000000000000145671476365375000173070ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'bad_request' 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 include BadRequest 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 include BadRequest end # ParamsTooDeepError is the error that is raised when params are recursively # nested over the specified limit. class ParamsTooDeepError < RangeError include BadRequest end def self.make_default(param_depth_limit) new Params, param_depth_limit end attr_reader :param_depth_limit def initialize(params_class, param_depth_limit) @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 < Hash alias_method :to_params_hash, :to_h end end end rack-3.1.12/lib/rack/recursive.rb000066400000000000000000000034361476365375000165660ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/reloader.rb000066400000000000000000000060271476365375000163530ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/request.rb000066400000000000000000000624371476365375000162550ustar00rootroot00000000000000# 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 rr_query_string = get_header(RACK_REQUEST_QUERY_STRING) query_string = self.query_string if rr_query_string == query_string get_header(RACK_REQUEST_QUERY_HASH) else if rr_query_string warn "query string used for GET parsing different from current query string. Starting in Rack 3.2, Rack will used the cached GET value instead of parsing the current query string.", uplevel: 1 end 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) form_input = get_header(RACK_REQUEST_FORM_INPUT) # And it was memoized from the same input: if form_input.equal?(rack_input) return form_hash elsif form_input warn "input stream used for POST parsing different from current input stream. Starting in Rack 3.2, Rack will used the cached POST value instead of parsing the current input stream.", uplevel: 1 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? if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList) set_header RACK_REQUEST_FORM_PAIRS, pairs set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs) else 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 # like Hash#values_at def values_at(*keys) warn("Request#values_at is deprecated and will be removed in a future version of Rack. Please use request.params.values_at instead", uplevel: 1) 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) # It would be nice to use filter_map here, but it's Ruby 2.7+ parts = header.to_s.split(',') parts.map! do |part| part.strip! next if part.empty? attribute, parameters = part.split(';', 2) attribute.strip! parameters&.strip! quality = 1.0 if parameters and /\Aq=([\d.]+)/ =~ parameters quality = $1.to_f end [attribute, quality] end parts.compact! parts 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 expand_param_pairs(pairs, query_parser = query_parser()) params = query_parser.make_params pairs.each do |k, v| query_parser.normalize_params(params, k, v) end params.to_params_hash 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: rack-3.1.12/lib/rack/response.rb000066400000000000000000000273151476365375000164170ustar00rootroot00000000000000# 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 # 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) raise ArgumentError, "Headers must be a Hash!" 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 # Body is unspecified - it may be a buffered response, or it may be a HEAD response. @length = nil 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 = nil 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? # We don't add the content-length here as the user has provided a block that can #write additional chunks to the body. @block = block return [@status, @headers, self] else # If we know the length of the body, set the content-length header... except if we are chunked? which is a legacy special case where the body might already be encoded and thus the actual encoded body length and the content-length are likely to be different. if @length && !chunked? @headers[CONTENT_LENGTH] = @length.to_s end 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 a chunk to the response body. # # Converts the response into a buffered response if it wasn't already. # # 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 # Convert the body of this response into an internally buffered Array if possible. # # `@buffered` is a ternary value which indicates whether the body is buffered. It can be: # * `nil` - The body has not been buffered yet. # * `true` - The body is buffered as an Array instance. # * `false` - The body is not buffered and cannot be buffered. # # @return [Boolean] whether the body is buffered as an Array instance. def buffered_body! if @buffered.nil? if @body.is_a?(Array) # The user supplied body was an array: @body = @body.compact @length = @body.sum{|part| part.bytesize} @buffered = true elsif @body.respond_to?(:each) # Turn the user supplied body into a buffered array: body = @body @body = Array.new @buffered = true body.each do |part| @writer.call(part.to_s) end body.close if body.respond_to?(:close) else # We don't know how to buffer the user-supplied body: @buffered = false end end return @buffered end def append(chunk) chunk = chunk.dup unless chunk.frozen? @body << chunk if @length @length += chunk.bytesize elsif @buffered @length = chunk.bytesize 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 rack-3.1.12/lib/rack/rewindable_input.rb000066400000000000000000000061761476365375000201160ustar00rootroot00000000000000# -*- 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 rack-3.1.12/lib/rack/runtime.rb000066400000000000000000000015461476365375000162420ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/sendfile.rb000066400000000000000000000130731476365375000163460ustar00rootroot00000000000000# 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.inspect}" 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 rack-3.1.12/lib/rack/show_exceptions.rb000066400000000000000000000336541476365375000200050ustar00rootroot00000000000000# frozen_string_literal: true 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 Frame = Struct.new(:filename, :lineno, :function, :pre_context_lineno, :pre_context, :context_line, :post_context_lineno, :post_context) 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 = Frame.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 rack-3.1.12/lib/rack/show_status.rb000066400000000000000000000071161476365375000171410ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/static.rb000066400000000000000000000141131476365375000160400ustar00rootroot00000000000000# 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] actual_path = Utils.clean_path_info(Utils.unescape_path(path)) if can_serve(actual_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 rack-3.1.12/lib/rack/tempfile_reaper.rb000066400000000000000000000014121476365375000177120ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/urlmap.rb000066400000000000000000000054751476365375000160640ustar00rootroot00000000000000# 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 rack-3.1.12/lib/rack/utils.rb000066400000000000000000000517121476365375000157170ustar00rootroot00000000000000# -*- encoding: binary -*- # frozen_string_literal: true require 'uri' require 'fileutils' require 'set' require 'tempfile' require 'time' require 'erb' 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 URI_PARSER = defined?(::URI::RFC2396_PARSER) ? ::URI::RFC2396_PARSER : ::URI::DEFAULT_PARSER 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_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_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 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(',').map do |part| value, parameters = part.split(';', 2).map(&:strip) 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(';').each_with_object({}) do |field, values| field.split(',').each do |pair| pair = pair.split('=').map(&:strip).join('=') return nil unless pair =~ /\A(by|for|host|proto)="?([^"]+)"?\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 # Introduced in ERB 4.0. ERB::Escape is an alias for ERB::Utils which # doesn't get monkey-patched by rails if defined?(ERB::Escape) && ERB::Escape.instance_method(:html_escape) define_method(:escape_html, ERB::Escape.instance_method(:html_escape)) else require 'cgi/escape' # Escape ampersands, brackets and quotes to their HTML/XML entities. def escape_html(string) CGI.escapeHTML(string.to_s) end 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 # :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 # A valid cookie key according to RFC2616. # A can be any US-ASCII characters, except control characters, spaces, or tabs. It also must not contain a separator character like the following: ( ) < > @ , ; : \ " / [ ] ? = { }. VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze private_constant :VALID_COOKIE_KEY private def escape_cookie_key(key) if key =~ VALID_COOKIE_KEY key else warn "Cookie key #{key.inspect} is not valid according to RFC2616; it will be escaped. This behaviour is deprecated and will be removed in a future version of Rack.", uplevel: 2 escape(key) end 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_cookie_key(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 :same_site value: #{value[:same_site].inspect}" end partitioned = "; partitioned" if value[:partitioned] value = value[:value] else key = escape_cookie_key(key) end value = [value] unless Array === value return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}#{partitioned}" 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 delete_cookie_header!(headers, key, value = {}) headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value) return nil 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 # Ignore Range when file size is 0 to avoid a 416 error. return nil if size.zero? 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 return [] if ranges.map(&:size).sum > size 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 # 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 -rcsv -e "puts CSV.parse(STDIN, headers: true) \ # .reject {|v| v['Description'] == 'Unassigned' or v['Description'].include? '(' } \ # .map {|v| %Q/#{v['Value']} => '#{v['Description']}'/ }.join(','+?\n)" 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', 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 => 'Content Too Large', 414 => 'URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', 417 => 'Expectation Failed', 421 => 'Misdirected Request', 422 => 'Unprocessable Content', 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', 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] OBSOLETE_SYMBOLS_TO_STATUS_CODES = { payload_too_large: 413, unprocessable_entity: 422, bandwidth_limit_exceeded: 509, not_extended: 510 }.freeze private_constant :OBSOLETE_SYMBOLS_TO_STATUS_CODES OBSOLETE_SYMBOL_MAPPINGS = { payload_too_large: :content_too_large, unprocessable_entity: :unprocessable_content }.freeze private_constant :OBSOLETE_SYMBOL_MAPPINGS def status_code(status) if status.is_a?(Symbol) SYMBOL_TO_STATUS_CODE.fetch(status) do fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" } message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack." if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status] # message = "#{message} Please use #{canonical_symbol.inspect} instead." # For now, let's not emit any warning when there is a mapping. else warn message, uplevel: 3 end fallback_code end 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 rack-3.1.12/lib/rack/version.rb000066400000000000000000000011431476365375000162350ustar00rootroot00000000000000# 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 RELEASE = "3.1.12" # Return the Rack release as a dotted string. def self.release RELEASE end end rack-3.1.12/rack.gemspec000066400000000000000000000025551476365375000150320ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'lib/rack/version' Gem::Specification.new do |s| s.name = "rack" s.version = Rack::RELEASE s.platform = Gem::Platform::RUBY s.summary = "A modular Ruby webserver interface." s.license = "MIT" s.description = <<~EOF 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 API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call. EOF s.files = Dir['lib/**/*'] + %w(MIT-LICENSE README.md SPEC.rdoc) s.extra_rdoc_files = ['README.md', 'CHANGELOG.md', 'CONTRIBUTING.md'] s.author = 'Leah Neukirchen' s.email = 'leah@vuxu.org' s.homepage = 'https://github.com/rack/rack' s.required_ruby_version = '>= 2.4.0' s.metadata = { "bug_tracker_uri" => "https://github.com/rack/rack/issues", "changelog_uri" => "https://github.com/rack/rack/blob/main/CHANGELOG.md", "documentation_uri" => "https://rubydoc.info/github/rack/rack", "source_code_uri" => "https://github.com/rack/rack" } s.add_development_dependency 'minitest', "~> 5.0" s.add_development_dependency 'minitest-global_expectations' s.add_development_dependency 'bundler' s.add_development_dependency 'rake' end rack-3.1.12/test/000077500000000000000000000000001476365375000135155ustar00rootroot00000000000000rack-3.1.12/test/.bacon000066400000000000000000000000001476365375000145660ustar00rootroot00000000000000rack-3.1.12/test/builder/000077500000000000000000000000001476365375000151435ustar00rootroot00000000000000rack-3.1.12/test/builder/an_underscore_app.rb000066400000000000000000000002121476365375000211520ustar00rootroot00000000000000# frozen_string_literal: true class AnUnderscoreApp def self.call(env) [200, { 'content-type' => 'text/plain' }, ['OK']] end end rack-3.1.12/test/builder/bom.ru000066400000000000000000000001061476365375000162650ustar00rootroot00000000000000run -> (env) { [200, { 'content-type' => 'text/plain' }, ['OK']] } rack-3.1.12/test/builder/comment.ru000066400000000000000000000001631476365375000171550ustar00rootroot00000000000000# frozen_string_literal: true =begin =end run lambda { |env| [200, { 'content-type' => 'text/plain' }, ['OK']] } rack-3.1.12/test/builder/end.ru000066400000000000000000000002321476365375000162560ustar00rootroot00000000000000# frozen_string_literal: true run lambda { |env| [200, { 'content-type' => 'text/plain' }, ['OK']] } __END__ Should not be evaluated Neither should This rack-3.1.12/test/builder/frozen.ru000066400000000000000000000002441476365375000170160ustar00rootroot00000000000000# frozen_string_literal: true run lambda { |env| body = 'frozen' raise "Not frozen!" unless body.frozen? [200, { 'content-type' => 'text/plain' }, [body]] } rack-3.1.12/test/builder/line.ru000066400000000000000000000001561476365375000164440ustar00rootroot00000000000000# frozen_string_literal: true run lambda{ |env| [200, { 'content-type' => 'text/plain' }, [__LINE__.to_s]] } rack-3.1.12/test/builder/options.ru000066400000000000000000000001771476365375000172130ustar00rootroot00000000000000# frozen_string_literal: true #\ -d -p 2929 --env test run lambda { |env| [200, { 'content-type' => 'text/plain' }, ['OK']] } rack-3.1.12/test/cgi/000077500000000000000000000000001476365375000142575ustar00rootroot00000000000000rack-3.1.12/test/cgi/assets/000077500000000000000000000000001476365375000155615ustar00rootroot00000000000000rack-3.1.12/test/cgi/assets/folder/000077500000000000000000000000001476365375000170345ustar00rootroot00000000000000rack-3.1.12/test/cgi/assets/folder/test.js000066400000000000000000000000211476365375000203420ustar00rootroot00000000000000### TestFile ### rack-3.1.12/test/cgi/assets/fonts/000077500000000000000000000000001476365375000167125ustar00rootroot00000000000000rack-3.1.12/test/cgi/assets/fonts/font.eot000066400000000000000000000000211476365375000203620ustar00rootroot00000000000000### TestFile ### rack-3.1.12/test/cgi/assets/images/000077500000000000000000000000001476365375000170265ustar00rootroot00000000000000rack-3.1.12/test/cgi/assets/images/favicon.ico000066400000000000000000000000001476365375000211350ustar00rootroot00000000000000rack-3.1.12/test/cgi/assets/images/image.png000066400000000000000000000000211476365375000206070ustar00rootroot00000000000000### TestFile ### rack-3.1.12/test/cgi/assets/index.html000066400000000000000000000000211476365375000175470ustar00rootroot00000000000000### TestFile ### rack-3.1.12/test/cgi/assets/javascripts/000077500000000000000000000000001476365375000201125ustar00rootroot00000000000000rack-3.1.12/test/cgi/assets/javascripts/app.js000066400000000000000000000000211476365375000212210ustar00rootroot00000000000000### TestFile ### rack-3.1.12/test/cgi/assets/stylesheets/000077500000000000000000000000001476365375000201355ustar00rootroot00000000000000rack-3.1.12/test/cgi/assets/stylesheets/app.css000066400000000000000000000000211476365375000214200ustar00rootroot00000000000000### TestFile ### rack-3.1.12/test/cgi/rackup_stub.rb000066400000000000000000000001541476365375000171260ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $:.unshift '../../lib' require 'rack' Rack::Server.start rack-3.1.12/test/cgi/sample_rackup.ru000066400000000000000000000001361476365375000174550ustar00rootroot00000000000000# frozen_string_literal: true require '../test_request' run Rack::Lint.new(TestRequest.new) rack-3.1.12/test/cgi/test000066400000000000000000000003211476365375000151550ustar00rootroot00000000000000***** DO NOT MODIFY THIS FILE! ***** If you modify this file, tests will break!!! The quick brown fox jumps over the ruby dog. The quick brown fox jumps over the lazy dog. ***** DO NOT MODIFY THIS FILE! ***** rack-3.1.12/test/cgi/test+directory/000077500000000000000000000000001476365375000172365ustar00rootroot00000000000000rack-3.1.12/test/cgi/test+directory/test+file000066400000000000000000000000271476365375000210520ustar00rootroot00000000000000this file has plusses! rack-3.1.12/test/cgi/test.gz000066400000000000000000000002731476365375000156020ustar00rootroot00000000000000dZX~=@l9q5EA 0 ut84E V_ou${tQKS NN֡4,eyDʿ׀LrkXV2:M}`dwKG .r\mhpƬ@,7H^<}4sJ7tHrack-3.1.12/test/cgi/test.ru000066400000000000000000000001601476365375000156030ustar00rootroot00000000000000#!../../bin/rackup # frozen_string_literal: true require '../test_request' run Rack::Lint.new(TestRequest.new) rack-3.1.12/test/gemloader.rb000066400000000000000000000005111476365375000157760ustar00rootroot00000000000000# frozen_string_literal: true require 'rubygems' project = 'rack' gemspec = File.expand_path("#{project}.gemspec", Dir.pwd) Gem::Specification.load(gemspec).dependencies.each do |dep| begin gem dep.name, *dep.requirement.as_list rescue Gem::LoadError warn "Cannot load #{dep.name} #{dep.requirement.to_s}" end end rack-3.1.12/test/helper.rb000066400000000000000000000021571476365375000153260ustar00rootroot00000000000000# frozen_string_literal: true if ENV.delete('COVERAGE') require 'simplecov' SimpleCov.start do enable_coverage :branch add_filter "/test/" add_filter "/lib/rack/handler" add_group('Missing'){|src| src.covered_percent < 100} add_group('Covered'){|src| src.covered_percent == 100} end end if ENV['SEPARATE'] def self.separate_testing yield end else $:.unshift(File.expand_path('../lib', __dir__)) require_relative '../lib/rack' def self.separate_testing end end require 'minitest/global_expectations/autorun' require 'stringio' class Minitest::Spec def self.deprecated(*args, &block) it(*args) do begin verbose, $VERBOSE = $VERBOSE, nil instance_exec(&block) ensure $VERBOSE = verbose end end end def capture_warnings(target) verbose = $VERBOSE warnings = Thread::Queue.new target.define_singleton_method(:warn) do |*args| warnings << args end begin $VERBOSE = true yield warnings ensure $VERBOSE = verbose target.singleton_class.send(:remove_method, :warn) end end end rack-3.1.12/test/multipart/000077500000000000000000000000001476365375000155365ustar00rootroot00000000000000rack-3.1.12/test/multipart/bad_robots000066400000000000000000000425221476365375000176040ustar00rootroot00000000000000--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="bbbbbbbbbbbbbbb" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="ccccccc" ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="file.name" INPUTMSG.gz --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="file.content_type" application/octet-stream --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="file.path" /var/tmp/uploads/4/0001728414 --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="file.md5" aa73198feb4b4c1c3186f5e7466cbbcc --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="file.size" 13212 --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="size" 80892 --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="mail_server_id" <1111111111.22222222.3333333333333.JavaMail.app@ffff-aaaa.dddd> --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="addresses" {"campsy_programmer@pinkedum.com":{"domain":"pinkedum.com","name":"Campsy Programmer","type":["env_sender"],"mailbox":"campsy_programmer"},"tex@rapidcity.com":{"domain":"rapidcity.com","name":"Big Tex","type":["env_recipients","to"],"mailbox":"tex"},"group-digests@linkedin.com":{"domain":"linkedin.com","name":"Group Members","type":["from"],"mailbox":"group-digests"}} --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="received_on" 2009-11-15T14:21:11Z --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="id" dbfd9804d26d11deab24e3037639bf77 --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon content-disposition: form-data; name="ip_address" 127.0.0.1 --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon-- rack-3.1.12/test/multipart/binary000066400000000000000000000640531476365375000167550ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="submit-name" Larry --AaB03x content-disposition: form-data; name="files"; filename="rack-logo.png" content-type: image/png PNG  IHDRbKGD pHYsHHFk>fIDATxy|uߓ)fr$mR" iJz @QAA@Et9@T䐴E@( BiZp=6M L&mi&# !陒|G d^>?q@ Dgz A D@@ .@ ] @ @t $ H@% K A D@@ .@ ] @ @t $ H@% KDz NR)H$P @ $ ~p: (..FM~~8iɧBI$k %I q3=xFh[PPplNiz$qry@ 8|*99P(+ a"[T.s$IB3ɗ]. pO H111nlillj&,;~- X,$Hq$ɓ'''_ jz)>1 tQ4d2YN;222( {KK nD!pRZZ:t͋|Qq[T$Q9ȋ D@(z oK#(~FR rSEE0 #6 r/QrFY֡^lbʋ"99&HTDOkK4nh/*xQQ:>`e%Յ1G+м‹IPx*99]Z@zP4L:0XE%KT<]S[pI$>JUrr2%P:1,a' >iK4zE%eZ*eم9W-*XQh!H$HTD[ 1.VPF{2a`4Mi:d&m@ \.7>"IҌ~!_B& H4:EQ@tTiitm0F1 34M+USx B 7T@|DCB ].ac`͋-4M_W/F}" v+,,ThQTzEE%4RAt ;W}E_DߣGr޽{]0HpWowU4|nZh^T ,--΋haF2ܢ=}l}4ɱcp}ՈDDQ)((XY__?#Z0k֬ӦM+W*Ajz5^B*--MTihQ!UWުT*^uzT@-[|/`Pqzꩊ 333/8ތ*/**++QVVQh2\죡?:=- ._ߝqF.[p $ *H&$,]O'}]T233_۲e;"(E ]˗ \OKtF.KA$I{ bh!lƍ'~AjRjG]!.wbe%narZqX)"e V[j,:R CQR___V]]u7D/'$x]dddB+EIJb(7999.hw{ReJV[j+NgVfl6G 3РcYV)ZD@rn\'"Jr%KV[,~`H`f8˲I^==4>}M os5/,ZKXZ慥5!,((H0xш Wk4.:::SF  $ajyx…l$0"0aaxaIdY6j*Y zOT-!MrGq^rY*煥BVR%, K4F㽼hcYV ] 84K,˦A #gDD@Kvvvq:.\, a $ $R5i-`BCD4W{(J/x KP,2$,hIFQϋeYt]48/h4??o֦{@t4 '/, 鄅 z*&Ih2M&S"m60-,e iZFt"L &JKVrC!*((#ruMiiizT*%HgD/&TYX]yZ$Y Efq0LR4ᥥ.+j玁{H4 naRi,[!.$Itw@^4T^44,tsMb\RHOO߫K'A@QzB"D"q$II[0\uXJ[xxEO@^qͭkY*jYΓ$y٫>!)((ŋĆhtgnWbqJ:^Nd2C@QO|;||e_@=[rUXXX,Noa1{IVzI^Dne4'1eA=`qdGSRR̟?(%%R&]ek@ }^@NJ4egg[gDž+WÇ>HpD }Re%8-,ڈJ9jeف=p,Ntz*X͍(&E !@WL&.J%K^U $`P= aYVbXTq͜f֬Y{g͚}\\-^u ms0)==*>>++x7X"_hZz.UbqJ*LOOߣt2F -F@ձ+p%;;2s:|ad/,p/anZ^i愄SNݣt:L&shk@$Pxoy #=p_*s8&i8|@p f|qL&;©SV$$$xE(@Q6mZ +]'_LxH i_p82XT*Wk@ Ht,$I$j8 K^XKKK:wL \nٵkѣGšZ~  iY ;Be$+Ng_0(//OVSS3⣏>z qܩRG[k@ Hʱ{Wyȑ}@ ɭfa~qV pzp83 #}Z0 n!}$!sau@7poo@ 1aY- $O@‘x!У.,76o|ׇ wp@z@  zXkxYR_VVvV\ܭ-b,%%p%%%#].h:eY)[˲"^T *yQH8 @t>% ^VFlQQXʸhk,6\~V"8jh4fXg \1tdii(5j놨X*xTZp"\Dž@ :HvaXI^ -y{VF oeh4MMM=qZ"\.{BQTRZZ:tDCDE wy?RGT)?dž@ A@x+#dLQ.Hfdd|C_Qɧ(*a1/*#xQEQ+|r5EQ 1,@!ᭌ񼕑[BhqN&tG322N2 /*NVT D%T_ N 箃eYaίч K PeX,.WTG322kȨQT2Q9%*v,6EK]] f7@LF儅V2{eLii['t:FsDќtd2YЭ`%*ٍ^GT_Eq\ݻ޽w]SaQAiD%$., \._ʸZͲvX,>[GbccK222Cee?r'PȲ{C˲bQx+*Ӽ%ɕn%ra!}X Fqĉ'9v>nWM[Um p8D6msMwwDT;JgE G ;x ~bgYI$ JUme0^͛W74}p?/0:+k'* GIYC^\ ^lҤI愄V 7i\^H*~W1 Ì`f`0(4 ]*? Z*^ic(@BDoQKK#G8rH}5Msss-~JV[+Mm +""`- ~ĉo6>PvpHD^N(\X3p4'ڔmykiii?֊eY1@ #@ʲb P\^^^ bEDk#I"oҭ,,^4"F] 4MZAmBBwSNݫtjL#ч {V0jA$I$Yt:㭕 A0L'hOQhz˲Q@t MӣrrrXpS~OA$kS̋8h4L2i|A8_d%:n^/:u깄fo10je@Qz Zi8t:_ƪIb ?bH$P᭕@ueY1 p[+$9?NDvx)>>lRmmqo`OOOO?RAB~ ѧ\X ""bcc9&ڵk_t ;w3 3;ث E^A/ԁ|A$ ү`4/D^^ޭF~>~X1qqqg͚o֬Y5vQ4z5@ :M v-Uv@}.+DV,{+i,6-eYAt ,өS/^gP]c:A&uǽV(aUN0 f7;(} D&ZVe ")j:l6xx'>WbqJ:ċw:"ɜA C@Qz ^QBC?;7\997:gƔ 2J :GA.ֈ\gtnDzweS 2UTM!*V(h ɂHǛd2Y)vMzz*b DY );:Q91~|5vzfXuh$ !HgWY]]}z֭' 87TK!m"6M{ +AMcC ݤ'wtF,KwNV[- (JgX-0#XMZ*z~jn]rrr(T-`4Miz _,ǯ H$-:V;EQ'DD8\acz8WXX3􍛧x۶mEDD۸q:$Zb+{} #n_|qݻAP(YSL{NOz1βhKӴd^2bqF9z$##D"i.?O-Be$+0EQ`"p'L&d2y 0@0e١VUgDAqHXX,8 &pRyl|WȰl?pM)G]XXx$@oxw_ܳg"p_} ///)//M !,XG-^gVF+cD 2NK$Xb+mD 6$m$IaBa gf(0 0a,;jƱ,+⻉7siV4=,**[o5!**$F |*j˟Rilláw`5hqa?)iiigOwgP^^.\x"aA83cƌw,XPy a/ ^VF+#:nei4 +#(w=;;~t:pBe  @>V iZ nai+^c418N暽a$I^䅥#\ry_q$:Hd8΁a.3 2aTEiiN*JJJ|6rHؼlCN]]k3˟_OVXR yHYX>V&??.줕q],C,7t]F{DDD@\\nwTSvvvfq\h2$&I`2z P[X~Ͳlb :iǞ8K_RgxX@nmEďiiio222~+ɯ p\?V-q'M}+O,ǭ`[-W I$I^#༰L&S,iӦ;,cй,ˊ-K,pM!/Ix׳hZ+ ~ߓOח]DmΓhUU)))jܶm[.H8hankFRRRBJsllo5kVP(lqu` `&*??hL-..~0܀\AAuֽ#pr>QmB@8 \._UVVeeZ, 2|c!2 /,,$I$IqF< 'H~&gY֓vܖ;"iz$մp M*Vy׳,[).$ygD/#FX;~xL&cТT*+Vl>~g8$`mXc'MI&5V8p9g)5ꫯu:]M0`\p%Xv3]T5cƌ-X=5L(\XhԞ8qbci:VXF ͝;?Oر#nxGïimY4yT뉳L:eEu):\h$ ,Z-KT18 <#ƕ+W>]tv;xv> Dr>|nRRRSG>?ѯ |[֭[7Onm+VlQ*={],/ YYYSC=vV˔H~ܖp2']Zvq-B’H$Xll, rv@+ۙ quSO+|F,5҅Vh`lxoŕ*0==}ĉAFFbp@ӧ$%%uzݒU8t¯=BE[w()++i\Aeر566vUݻ]n]eˎz^JZ*eJ3< ^vp΂~gJ+,@O@v;VXX8h4jyѸS &$$|7uԯz!NW-ɜP]]-q<\[A &ЕvذaCz}}'{@А E"Qq oL\\\[ol6wЗF>!m0÷$hᄈΝvǎC@[nQJJm[8L;zO@a:΂  //||~gZ|sqf4'>0ow\d2I}L:wx̙?kJEGGsg'k: tV\\0eYOk{m,[gwdڎs ޜFDqqqhw׮]+**v\q:h*@zz^_ґ_i.ux+m`JIIVpLjTL ֭[m3f\MtBKKK[-xq oCyyaы/~1%%nYI3PN8@%v1MpIԩSc-G@ZZ \(*;zV $NƚXu]t [hq|ZVX\RNF;hqv(++~ Ç3 38X$c 78η233+rx%e8[+͛]揤ٳg]|D]x7nE_ b1T*}塇^LP߳e'ϴgϞ1۷oz2Mӣ~28%%LIIdWn HWillʺ]ORRRRWWo>>5xyp1a„uap~O=3M8yh4u._ DF/4tҿ@ ٳK6oiyy+}KXr^^ޱ3gV"WPea+6sq5&& h;; _GǸqʋd2o\qL&^yT*|td^D޽{~VVVٚ5k)JomڴIaÆ{iiiִmyyy:NaۍFcT*=qܙd0 b|=_7ܠsȾ=3Smw0v RRRjw_sa @h4Oemg\oOKߵw_⾙5kYf܃Yc}N@pwqeq8?8EQ;wt\#bG"ΝaaHѰ0<1///+(//͛,[l_ 퐀"@ {U㷬%oWbyJU5t:Lv#vkm@ 'y޽ݻw7ï/=IKK|?Ѕ h~QXX8aA8bݺu)))'C=P\Xa + QSߑUpdƔ=/JIId7]Ku+@tyrOH$~{OIIɿBÞ>s-tc_k׮V]yӂ\XңTSu+\4iV^^T3qsaL> 6,//^O\bb+VX__?·dPYA8dݻw_n]ѲeˊC=PMz"$O~||<3r쟖-[VbŊ^1LOΝ;Up b1bDIIJJj={z0pL3.YfB!oIIIٳ?a֭{ 佌BIO Hsa1 #`Y!"~\~llDBC5 mfGf̘ xWq~׿XƎa<#? z补ٳg'&&n ]{6lxn] 9 CbjƲ$I{^[Cʤ[(Jv͚5f̘Tw'脄3fh! #Rl%K裏f.\ms m}=A6X xOzO}AL 0moKgϞjS^ ºkܾ  $3..;Z1 9ŕ85R*׿6tc&eHeLv"%%e߼y 'Nvɔ .A^LNN 333RTo?,s8$%%9^~[zvlqxjjrVKW?: 4?D :g_~=Pk왙3guEE1o޼g- 5}΅ŋFbhL4V d2YN;jdj2!,Z =9ڪm6a\T2 uv>y։8F*6I$p:pň(%(b9(v\+\[.`$~?blq g%9G.]:ܭ9?Z}JlP]]=`0XpS#GX,_X,>R>x6''֓'O m65ǾpႠ8u|ʕ,Y˗!33sY\\b/Pάl֟ .8^kR;n,Ze˖=xb]555ѵ^ziǺtԞ:R ?c^t{ժU111+w䌘cƌF_KLL\P()}111_̚5kEQkjj3F3l6>cJjڹfYHBrCILL\P(v* 駟j<㭪k|g_z'lR({ Ŷ?޽;e6UQQQC=\EPQ(.3.>>scbby,Kx=wqDz>h)S)/]n|/^B_UU%gL&{oժU 4 [l\P|P(|F][[ItIJe>| EB#˷8'111O?[,?z'OܻwovRR#й:vϙ3gX,7mA>==(Jʟϡ ~zX,= b7nܸF$v?Ͽ{겲64yǗ/_d-[l&!`J9sD)LEر֑#G7nvh-..~/xwї^zogϞ}… >*--H$2lv;,\p֭[:thٳ#HZ=:a֭,;r͚5P*EIV]v W*zj._|QFٗ-[VdX"|7i9gΜ'*)RL&Gv A d$ȑA]4N<)gB*pTo?9r… ۵kPүG f:ulʕ×,Y_|7VhpW*wj פ,_ŋ߹k׮555"f3},Z>glUK.uqx˖-`(`GN먮.Zr8eҥxIT*7GEEK., dff.0`@і-[s,bm59bɓ':W555޲'KsXv˖-}k׮{&113gΈ|ބgΜ@.]z'\ti2q#϶n*|;N&O>-歞z,*BP,^-ku8]v >^,`Kv4)nߩS ޤbB?禰@xKCZPP0h4ꋋSư,c8bJ:^M:N&uG-<>;є0h4h4~M?6swO9@^] w @PJwsRyeĉ}?~|qjJKKQ(G׿*Jp}cǎ}m۶L6Nh/X(`׮]0 [o=77w8aHaŋ/]߿ߒHl)))yeفp+9s ] ҶmۦM0$33H$Bp$IZredogfssssM&Sn?7ŋSSSsv#G<l!I ݑoo|G}ǿ(jAN~f͚uJϟ-[͛oqrRI' q<"))q8y9_hYY$s-اOyRR|nycΝ]@%%%V-K.裏6x=""{9JBQTUUE EE`Vp8T|SFﴬL;gΜnRt͟?ˢ9[l!">>ޤwfI||q͛uĉվV)NWT\\|lokwTA$Iҟ~iq|p;>wm'N:o޼'NX aNgOt:Qedd(H.rWFGjR $I^&I<Z  0CM6=hXC%+`4|mK-WHte Z~jaS?ApgoMq\QZT*U 劊^_XPPX^^ވiӦ|ѻ~III &,...d2E^A~ iii{Z˗/~8p;(ׯ_K<״~}X.<@[{l >x<8~}ݺu=%"!sa\.Zd>ݶ#5Nd2qNWT*U x`ή999ptRTt:YVW t eف%R=Ǎm_J͛7wK@Dz2Qcc~f@PW_/\|,OV,+0 $V\iol8^-+W|Pʻ ###A(^'xb%K6.9hl"H'N\W /۷GFu$ʕ+ E.^8D̳n~{/EQŋzH;vlcdd'[d6;P _}wdYV)Hp[sΝ{tʔ)oqVZ5kb0|AW_ɳeFAum]8. ӧWA|(9t r??fff֋D"lݺu&q3\.࿳TVV&2 #dYvHDDD5kv]Oĉ?+xWرo>T$;zp8ݻ9sNhx8~VR}U4: Ans/9(6 v`ǯpbن4=?O4MyX-Njl6po:K0j8ڹs<))jÇr GOю,**J9sfUWW'8vX4jݿ=TZhQǏnTVV y$ IҚsn+V:mD"cݺu eUYY)*>NjN53I?\-_|s=(++#FCMtiiig0 s655^|ŗiii<^v\~&55@cPRRvnٲM6ܙk&++k?ȑ#[6o|'|bBBžӧ{{n2+++V@gTTTdΜ9j#ӧO~[YYYwTRRB==""܎;ƧOMMXRR"]fj/^".qXJ 8:PxpΜ9_O6;FS- [{2t%:kV$)>kXc-RP4+?j(u IIIxǏOb;nbb 55̫x޼y NSt̙3sV^ErssZ,&L0SnnŒ8a„D"^z ÇOV)q8Qo[ppuEW)))k^jU 4M/ohk!H977be„ E>=b̈́ {`ӧOK _,;N z뭿qw)99yfS͛7SPxiH$2Y)SSO=uرcޱcG֝wP---رc+Ϗ~{ Lcc#;& RRR>ݷo¢_tI1cƌWk ޞ/jKa5kr>z޽v\/^W|ӹf͚|A޽{hѢN5Ryeƍ̜9s?XE~饗^;vlcBBBmV3~S'On}_+SRRvDE7Vmزe˞F )=|֯_ՙ3g.XHtzΜ9Jlĉ9uW:9m7)ّ뢙o8t1 =X 2^iQ}Y-Ojĝj罬V^hzd?21>0mv18nd?y>4iҹ!55uǯUwĉb$s`ĴѵvWPb`j9UyV˲e|p}eҥ-[vAb4/..~a,B3Bρ[O5k־Yf]H$yc" yJ$i|ʼF̞hmW0Z~ b-ZvM|e"r׮] Fqbqq񤆆q,*TbBR:^&2cW6_vݽ6 _c- XpEkqvmT vTQQqRAn*[^4t571-a V_Tb ۮr+R!MCOx aЉ *pd))){ϟ0%%R&]A@<_{/ZD ⭖Quk0 s577þ}dFQA5A%LV鿠2 ~e&NZ"(rjYl˗u'N\(JI|:AA?}R($%=bfXD")?p@h|ܽrDе _T*>q<--H$/q 7Y-t :N9" Hs3 oNWFT  "jpgPYe2YN۫N:2!!ųY@t$ 7[-.~xO~A@ ӃV'' QH@˜V"w]OX,ʠ*CT'@ ` 0LrjZyA@ z$ 72@ }$ H@% K A D@@ .@ ] @ ws\tEXtSoftwarewww.inkscape.org<IENDB` --AaB03x-- rack-3.1.12/test/multipart/content_type_and_no_disposition000066400000000000000000000001161476365375000241340ustar00rootroot00000000000000--AaB03x content-type: text/plain; charset=US-ASCII contents --AaB03x-- rack-3.1.12/test/multipart/content_type_and_no_filename000066400000000000000000000001731476365375000233530ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="text" content-type: text/plain; charset=US-ASCII contents --AaB03x-- rack-3.1.12/test/multipart/content_type_and_unknown_charset000066400000000000000000000001771476365375000243130ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="text" content-type: text/plain; charset=foo; bar=baz contents --AaB03x-- rack-3.1.12/test/multipart/empty000066400000000000000000000002771476365375000166250ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="submit-name" Larry --AaB03x content-disposition: form-data; name="files"; filename="file1.txt" content-type: text/plain --AaB03x-- rack-3.1.12/test/multipart/end_boundary_first000066400000000000000000000002261476365375000213410ustar00rootroot00000000000000--AaB03x-- --AaB03x Content-Disposition: form-data; name="files"; filename="foo" Content-Type: application/octet-stream contents --AaB03x-- rack-3.1.12/test/multipart/fail_16384_nofile000066400000000000000000000651721476365375000205100ustar00rootroot00000000000000------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="_method" put ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="authenticity_token" XCUgSyYsZ+iHQunq/yCSKFzjeVmsXV/WcphHQ0J+05I= ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[SESE]" BooBar ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[BBBBBBBBB]" 18 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[CCCCCCCCCCCCCCCCCCC]" 0 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[STARTFOO]" 2009-11-04 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[ENDFOO]" 2009-12-01 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[DDDDDDDD]" 0 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[DDDDDDDD]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[EEEEEEEEEE]" 10000 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[FFFFFFFFF]" boskoizcool ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[GGGGGGGGGGG]" 0 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[GGGGGGGGGGG]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[YYYYYYYYYYYYYYY]" 5.00 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[ZZZZZZZZZZZZZ]" mille ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[XXXXXXXXXXXXXXXXXXXXX]" 0 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][9]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][10]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][11]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][12]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][13]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][14]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][15]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][16]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][17]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][18]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][19]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][20]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][21]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][22]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][23]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][0]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][1]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][2]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][3]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][4]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][5]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][6]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][7]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][8]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][9]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][10]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][11]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][12]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][13]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][14]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][15]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][16]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][17]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][18]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][19]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][20]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][21]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][22]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][23]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][0]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][1]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][2]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][3]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][4]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][5]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][6]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][7]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][8]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][9]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][10]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][11]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][12]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][13]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][14]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][15]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][16]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][17]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][18]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][19]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][20]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][21]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][22]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][23]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][0]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][1]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][2]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][3]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][4]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][5]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][6]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][7]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][8]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][9]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][10]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][11]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][12]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][13]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][14]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][15]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][16]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][17]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][18]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][19]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][20]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][21]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][22]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][23]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][0]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][1]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][2]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][3]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][4]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][5]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][6]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][7]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][8]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][9]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][10]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][11]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][12]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][13]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][14]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][15]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][16]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][17]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][18]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][19]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][20]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][21]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][22]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][23]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][0]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][1]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][2]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][3]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][4]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][5]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][6]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][7]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][8]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][9]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][10]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][11]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][12]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][13]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][14]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][15]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][16]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][17]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][18]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][19]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][20]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][21]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][22]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][23]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][0]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][1]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][2]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][3]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][4]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][5]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][6]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][7]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][8]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][9]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][10]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][11]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][12]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][13]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][14]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][15]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][16]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][17]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][18]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][19]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][20]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][21]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][22]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][23]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][0]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][1]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][2]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][3]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][4]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][5]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][6]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][7]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][8]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][ZEZE]" PLAPLAPLAINCINCINC ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][123412341234e]" SITE ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][12345678901]" 56 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_type]" none ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][has_hashashas_has]" 0 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][frefrefre_fre_freee]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][frefrefre_fre_frefre]" forever ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][self_block]" 0 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][COUCOUN]" ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][REGREG]" ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][c1c1]" ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA_TARTARTAR_wizard_rule" ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_rule]" ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[selection_selection]" R ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-1][selection_selection]" 1 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-1][ba_unit_id]" 1015 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-2][selection_selection]" 2 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-2][ba_unit_id]" 1017 ------WebKitFormBoundaryWsY0GnpbI5U7ztzo content-disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[tile_name]" ------WebKitFormBoundaryWsY0GnpbI5U7ztzo-- rack-3.1.12/test/multipart/file1.txt000066400000000000000000000000101476365375000172660ustar00rootroot00000000000000contentsrack-3.1.12/test/multipart/filename_and_modification_param000066400000000000000000000003601476365375000237670ustar00rootroot00000000000000--AaB03x content-type: image/jpeg content-disposition: attachment; name="files"; filename=genome.jpeg; modification-date="Wed, 12 Feb 1997 16:29:51 -0500"; Content-Description: a complete map of the human genome contents --AaB03x-- rack-3.1.12/test/multipart/filename_and_no_name000066400000000000000000000001621476365375000215560ustar00rootroot00000000000000--AaB03x content-disposition: form-data; filename="file1.txt" content-type: text/plain contents --AaB03x-- rack-3.1.12/test/multipart/filename_multi000066400000000000000000000002361476365375000204540ustar00rootroot00000000000000--AaB03x Content-Disposition: form-data; name="files"; filename="foo"; filename*=utf-8''bar Content-Type: application/octet-stream contents --AaB03x-- rack-3.1.12/test/multipart/filename_with_encoded_words000066400000000000000000000003171476365375000231740ustar00rootroot00000000000000--AaB03x content-type: image/jpeg content-disposition: attachment; name="files"; filename*=utf-8''%D1%84%D0%B0%D0%B9%D0%BB Content-Description: a complete map of the human genome contents --AaB03x-- rack-3.1.12/test/multipart/filename_with_escaped_quotes000066400000000000000000000002241476365375000233560ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="files"; filename="escape \"quotes" content-type: application/octet-stream contents --AaB03x-- rack-3.1.12/test/multipart/filename_with_escaped_quotes_and_modification_param000066400000000000000000000003741476365375000301130ustar00rootroot00000000000000--AaB03x content-type: image/jpeg content-disposition: attachment; name="files"; filename="\"human\" genome.jpeg"; modification-date="Wed, 12 Feb 1997 16:29:51 -0500"; Content-Description: a complete map of the human genome contents --AaB03x-- rack-3.1.12/test/multipart/filename_with_null_byte000066400000000000000000000003031476365375000223450ustar00rootroot00000000000000--AaB03x content-type: image/jpeg content-disposition: attachment; name="files"; filename="flowers.exe%00.jpg" Content-Description: a complete map of the human genome contents --AaB03x-- rack-3.1.12/test/multipart/filename_with_percent_escaped_quotes000066400000000000000000000002251476365375000250770ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="files"; filename="escape %22quotes" content-type: application/octet-stream contents --AaB03x-- rack-3.1.12/test/multipart/filename_with_plus000066400000000000000000000002141476365375000213340ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="files"; filename="foo+bar" content-type: application/octet-stream contents --AaB03x-- rack-3.1.12/test/multipart/filename_with_single_quote000066400000000000000000000003021476365375000230450ustar00rootroot00000000000000--AaB03x content-type: image/jpeg content-disposition: attachment; name="files"; filename="bob's flowers.jpg" Content-Description: a complete map of the human genome contents --AaB03x-- rack-3.1.12/test/multipart/filename_with_unescaped_percentages000066400000000000000000000003321476365375000247010ustar00rootroot00000000000000------WebKitFormBoundary2NHc7OhsgU68l3Al content-disposition: form-data; name="document[attachment]"; filename="100% of a photo.jpeg" content-type: image/jpeg contents ------WebKitFormBoundary2NHc7OhsgU68l3Al-- rack-3.1.12/test/multipart/filename_with_unescaped_percentages2000066400000000000000000000003131476365375000247620ustar00rootroot00000000000000------WebKitFormBoundary2NHc7OhsgU68l3Al content-disposition: form-data; name="document[attachment]"; filename="100%a" content-type: image/jpeg contents ------WebKitFormBoundary2NHc7OhsgU68l3Al-- rack-3.1.12/test/multipart/filename_with_unescaped_percentages3000066400000000000000000000003121476365375000247620ustar00rootroot00000000000000------WebKitFormBoundary2NHc7OhsgU68l3Al content-disposition: form-data; name="document[attachment]"; filename="100%" content-type: image/jpeg contents ------WebKitFormBoundary2NHc7OhsgU68l3Al-- rack-3.1.12/test/multipart/filename_with_unescaped_quotes000066400000000000000000000002231476365375000237200ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="files"; filename="escape "quotes" content-type: application/octet-stream contents --AaB03x-- rack-3.1.12/test/multipart/ie000066400000000000000000000002601476365375000160540ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="files"; filename="C:\Documents and Settings\Administrator\Desktop\file1.txt" content-type: text/plain contents --AaB03x-- rack-3.1.12/test/multipart/invalid_character000066400000000000000000000002031476365375000211160ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="files"; filename="invalid.txt" content-type: text/plain contents --AaB03x-- rack-3.1.12/test/multipart/mixed_files000066400000000000000000000006611476365375000177540ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="foo" bar --AaB03x content-disposition: form-data; name="files" content-type: multipart/mixed, boundary=BbC04y --BbC04y content-disposition: attachment; filename="file.txt" content-type: text/plain contents --BbC04y content-disposition: attachment; filename="flowers.jpg" content-type: image/jpeg content-transfer-encoding: binary contents --BbC04y-- --AaB03x-- rack-3.1.12/test/multipart/nested000066400000000000000000000003211476365375000167370ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="foo[submit-name]" Larry --AaB03x content-disposition: form-data; name="foo[files]"; filename="file1.txt" content-type: text/plain contents --AaB03x-- rack-3.1.12/test/multipart/none000066400000000000000000000002341476365375000164170ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="submit-name" Larry --AaB03x content-disposition: form-data; name="files"; filename="" --AaB03x-- rack-3.1.12/test/multipart/preceding_boundary000066400000000000000000000002111476365375000213160ustar00rootroot00000000000000A--AaB03x Content-Disposition: form-data; name="files"; filename="foo" Content-Type: application/octet-stream contents --AaB03x-- rack-3.1.12/test/multipart/quoted000066400000000000000000000004711476365375000167640ustar00rootroot00000000000000--AaB:03x content-disposition: form-data; name="submit-name" Larry --AaB:03x content-disposition: form-data; name="submit-name-with-content" content-type: text/plain Berry --AaB:03x content-disposition: form-data; name="files"; filename="file1.txt" content-type: text/plain contents --AaB:03x-- rack-3.1.12/test/multipart/rack-logo.png000066400000000000000000000635511476365375000201340ustar00rootroot00000000000000PNG  IHDRbKGD pHYsHHFk>fIDATxy|uߓ)fr$mR" iJz @QAA@Et9@T䐴E@( BiZp=6M L&mi&# !陒|G d^>?q@ Dgz A D@@ .@ ] @ @t $ H@% K A D@@ .@ ] @ @t $ H@% KDz NR)H$P @ $ ~p: (..FM~~8iɧBI$k %I q3=xFh[PPplNiz$qry@ 8|*99P(+ a"[T.s$IB3ɗ]. pO H111nlillj&,;~- X,$Hq$ɓ'''_ jz)>1 tQ4d2YN;222( {KK nD!pRZZ:t͋|Qq[T$Q9ȋ D@(z oK#(~FR rSEE0 #6 r/QrFY֡^lbʋ"99&HTDOkK4nh/*xQQ:>`e%Յ1G+м‹IPx*99]Z@zP4L:0XE%KT<]S[pI$>JUrr2%P:1,a' >iK4zE%eZ*eم9W-*XQh!H$HTD[ 1.VPF{2a`4Mi:d&m@ \.7>"IҌ~!_B& H4:EQ@tTiitm0F1 34M+USx B 7T@|DCB ].ac`͋-4M_W/F}" v+,,ThQTzEE%4RAt ;W}E_DߣGr޽{]0HpWowU4|nZh^T ,--΋haF2ܢ=}l}4ɱcp}ՈDDQ)((XY__?#Z0k֬ӦM+W*Ajz5^B*--MTihQ!UWުT*^uzT@-[|/`Pqzꩊ 333/8ތ*/**++QVVQh2\죡?:=- ._ߝqF.[p $ *H&$,]O'}]T233_۲e;"(E ]˗ \OKtF.KA$I{ bh!lƍ'~AjRjG]!.wbe%narZqX)"e V[j,:R CQR___V]]u7D/'$x]dddB+EIJb(7999.hw{ReJV[j+NgVfl6G 3РcYV)ZD@rn\'"Jr%KV[,~`H`f8˲I^==4>}M os5/,ZKXZ慥5!,((H0xш Wk4.:::SF  $ajyx…l$0"0aaxaIdY6j*Y zOT-!MrGq^rY*煥BVR%, K4F㽼hcYV ] 84K,˦A #gDD@Kvvvq:.\, a $ $R5i-`BCD4W{(J/x KP,2$,hIFQϋeYt]48/h4??o֦{@t4 '/, 鄅 z*&Ih2M&S"m60-,e iZFt"L &JKVrC!*((#ruMiiizT*%HgD/&TYX]yZ$Y Efq0LR4ᥥ.+j玁{H4 naRi,[!.$Itw@^4T^44,tsMb\RHOO߫K'A@QzB"D"q$II[0\uXJ[xxEO@^qͭkY*jYΓ$y٫>!)((ŋĆhtgnWbqJ:^Nd2C@QO|;||e_@=[rUXXX,Noa1{IVzI^Dne4'1eA=`qdGSRR̟?(%%R&]ek@ }^@NJ4egg[gDž+WÇ>HpD }Re%8-,ڈJ9jeف=p,Ntz*X͍(&E !@WL&.J%K^U $`P= aYVbXTq͜f֬Y{g͚}\\-^u ms0)==*>>++x7X"_hZz.UbqJ*LOOߣt2F -F@ձ+p%;;2s:|ad/,p/anZ^i愄SNݣt:L&shk@$Pxoy #=p_*s8&i8|@p f|qL&;©SV$$$xE(@Q6mZ +]'_LxH i_p82XT*Wk@ Ht,$I$j8 K^XKKK:wL \nٵkѣGšZ~  iY ;Be$+Ng_0(//OVSS3⣏>z qܩRG[k@ Hʱ{Wyȑ}@ ɭfa~qV pzp83 #}Z0 n!}$!sau@7poo@ 1aY- $O@‘x!У.,76o|ׇ wp@z@  zXkxYR_VVvV\ܭ-b,%%p%%%#].h:eY)[˲"^T *yQH8 @t>% ^VFlQQXʸhk,6\~V"8jh4fXg \1tdii(5j놨X*xTZp"\Dž@ :HvaXI^ -y{VF oeh4MMM=qZ"\.{BQTRZZ:tDCDE wy?RGT)?dž@ A@x+#dLQ.Hfdd|C_Qɧ(*a1/*#xQEQ+|r5EQ 1,@!ᭌ񼕑[BhqN&tG322N2 /*NVT D%T_ N 箃eYaίч K PeX,.WTG322kȨQT2Q9%*v,6EK]] f7@LF儅V2{eLii['t:FsDќtd2YЭ`%*ٍ^GT_Eq\ݻ޽w]SaQAiD%$., \._ʸZͲvX,>[GbccK222Cee?r'PȲ{C˲bQx+*Ӽ%ɕn%ra!}X Fqĉ'9v>nWM[Um p8D6msMwwDT;JgE G ;x ~bgYI$ JUme0^͛W74}p?/0:+k'* GIYC^\ ^lҤI愄V 7i\^H*~W1 Ì`f`0(4 ]*? Z*^ic(@BDoQKK#G8rH}5Msss-~JV[+Mm +""`- ~ĉo6>PvpHD^N(\X3p4'ڔmykiii?֊eY1@ #@ʲb P\^^^ bEDk#I"oҭ,,^4"F] 4MZAmBBwSNݫtjL#ч {V0jA$I$Yt:㭕 A0L'hOQhz˲Q@t MӣrrrXpS~OA$kS̋8h4L2i|A8_d%:n^/:u깄fo10je@Qz Zi8t:_ƪIb ?bH$P᭕@ueY1 p[+$9?NDvx)>>lRmmqo`OOOO?RAB~ ѧ\X ""bcc9&ڵk_t ;w3 3;ث E^A/ԁ|A$ ү`4/D^^ޭF~>~X1qqqg͚o֬Y5vQ4z5@ :M v-Uv@}.+DV,{+i,6-eYAt ,өS/^gP]c:A&uǽV(aUN0 f7;(} D&ZVe ")j:l6xx'>WbqJ:ċw:"ɜA C@Qz ^QBC?;7\997:gƔ 2J :GA.ֈ\gtnDzweS 2UTM!*V(h ɂHǛd2Y)vMzz*b DY );:Q91~|5vzfXuh$ !HgWY]]}z֭' 87TK!m"6M{ +AMcC ݤ'wtF,KwNV[- (JgX-0#XMZ*z~jn]rrr(T-`4Miz _,ǯ H$-:V;EQ'DD8\acz8WXX3􍛧x۶mEDD۸q:$Zb+{} #n_|qݻAP(YSL{NOz1βhKӴd^2bqF9z$##D"i.?O-Be$+0EQ`"p'L&d2y 0@0e١VUgDAqHXX,8 &pRyl|WȰl?pM)G]XXx$@oxw_ܳg"p_} ///)//M !,XG-^gVF+cD 2NK$Xb+mD 6$m$IaBa gf(0 0a,;jƱ,+⻉7siV4=,**[o5!**$F |*j˟Rilláw`5hqa?)iiigOwgP^^.\x"aA83cƌw,XPy a/ ^VF+#:nei4 +#(w=;;~t:pBe  @>V iZ nai+^c418N暽a$I^䅥#\ry_q$:Hd8΁a.3 2aTEiiN*JJJ|6rHؼlCN]]k3˟_OVXR yHYX>V&??.줕q],C,7t]F{DDD@\\nwTSvvvfq\h2$&I`2z P[X~Ͳlb :iǞ8K_RgxX@nmEďiiio222~+ɯ p\?V-q'M}+O,ǭ`[-W I$I^#༰L&S,iӦ;,cй,ˊ-K,pM!/Ix׳hZ+ ~ߓOח]DmΓhUU)))jܶm[.H8hankFRRRBJsllo5kVP(lqu` `&*??hL-..~0܀\AAuֽ#pr>QmB@8 \._UVVeeZ, 2|c!2 /,,$I$IqF< 'H~&gY֓vܖ;"iz$մp M*Vy׳,[).$ygD/#FX;~xL&cТT*+Vl>~g8$`mXc'MI&5V8p9g)5ꫯu:]M0`\p%Xv3]T5cƌ-X=5L(\XhԞ8qbci:VXF ͝;?Oر#nxGïimY4yT뉳L:eEu):\h$ ,Z-KT18 <#ƕ+W>]tv;xv> Dr>|nRRRSG>?ѯ |[֭[7Onm+VlQ*={],/ YYYSC=vV˔H~ܖp2']Zvq-B’H$Xll, rv@+ۙ quSO+|F,5҅Vh`lxoŕ*0==}ĉAFFbp@ӧ$%%uzݒU8t¯=BE[w()++i\Aeر566vUݻ]n]eˎz^JZ*eJ3< ^vp΂~gJ+,@O@v;VXX8h4jyѸS &$$|7uԯz!NW-ɜP]]-q<\[A &ЕvذaCz}}'{@А E"Qq oL\\\[ol6wЗF>!m0÷$hᄈΝvǎC@[nQJJm[8L;zO@a:΂  //||~gZ|sqf4'>0ow\d2I}L:wx̙?kJEGGsg'k: tV\\0eYOk{m,[gwdڎs ޜFDqqqhw׮]+**v\q:h*@zz^_ґ_i.ux+m`JIIVpLjTL ֭[m3f\MtBKKK[-xq oCyyaы/~1%%nYI3PN8@%v1MpIԩSc-G@ZZ \(*;zV $NƚXu]t [hq|ZVX\RNF;hqv(++~ Ç3 38X$c 78η233+rx%e8[+͛]揤ٳg]|D]x7nE_ b1T*}塇^LP߳e'ϴgϞ1۷oz2Mӣ~28%%LIIdWn HWillʺ]ORRRRWWo>>5xyp1a„uap~O=3M8yh4u._ DF/4tҿ@ ٳK6oiyy+}KXr^^ޱ3gV"WPea+6sq5&& h;; _GǸqʋd2o\qL&^yT*|td^D޽{~VVVٚ5k)JomڴIaÆ{iiiִmyyy:NaۍFcT*=qܙd0 b|=_7ܠsȾ=3Smw0v RRRjw_sa @h4Oemg\oOKߵw_⾙5kYf܃Yc}N@pwqeq8?8EQ;wt\#bG"ΝaaHѰ0<1///+(//͛,[l_ 퐀"@ {U㷬%oWbyJU5t:Lv#vkm@ 'y޽ݻw7ï/=IKK|?Ѕ h~QXX8aA8bݺu)))'C=P\Xa + QSߑUpdƔ=/JIId7]Ku+@tyrOH$~{OIIɿBÞ>s-tc_k׮V]yӂ\XңTSu+\4iV^^T3qsaL> 6,//^O\bb+VX__?·dPYA8dݻw_n]ѲeˊC=PMz"$O~||<3r쟖-[VbŊ^1LOΝ;Up b1bDIIJJj={z0pL3.YfB!oIIIٳ?a֭{ 佌BIO Hsa1 #`Y!"~\~llDBC5 mfGf̘ xWq~׿XƎa<#? z补ٳg'&&n ]{6lxn] 9 CbjƲ$I{^[Cʤ[(Jv͚5f̘Tw'脄3fh! #Rl%K裏f.\ms m}=A6X xOzO}AL 0moKgϞjS^ ºkܾ  $3..;Z1 9ŕ85R*׿6tc&eHeLv"%%e߼y 'Nvɔ .A^LNN 333RTo?,s8$%%9^~[zvlqxjjrVKW?: 4?D :g_~=Pk왙3guEE1o޼g- 5}΅ŋFbhL4V d2YN;jdj2!,Z =9ڪm6a\T2 uv>y։8F*6I$p:pň(%(b9(v\+\[.`$~?blq g%9G.]:ܭ9?Z}JlP]]=`0XpS#GX,_X,>R>x6''֓'O m65ǾpႠ8u|ʕ,Y˗!33sY\\b/Pάl֟ .8^kR;n,Ze˖=xb]555ѵ^ziǺtԞ:R ?c^t{ժU111+w䌘cƌF_KLL\P()}111_̚5kEQkjj3F3l6>cJjڹfYHBrCILL\P(v* 駟j<㭪k|g_z'lR({ Ŷ?޽;e6UQQQC=\EPQ(.3.>>scbby,Kx=wqDz>h)S)/]n|/^B_UU%gL&{oժU 4 [l\P|P(|F][[ItIJe>| EB#˷8'111O?[,?z'OܻwovRR#й:vϙ3gX,7mA>==(Jʟϡ ~zX,= b7nܸF$v?Ͽ{겲64yǗ/_d-[l&!`J9sD)LEر֑#G7nvh-..~/xwї^zogϞ}… >*--H$2lv;,\p֭[:thٳ#HZ=:a֭,;r͚5P*EIV]v W*zj._|QFٗ-[VdX"|7i9gΜ'*)RL&Gv A d$ȑA]4N<)gB*pTo?9r… ۵kPүG f:ulʕ×,Y_|7VhpW*wj פ,_ŋ߹k׮555"f3},Z>glUK.uqx˖-`(`GN먮.Zr8eҥxIT*7GEEK., dff.0`@і-[s,bm59bɓ':W555޲'KsXv˖-}k׮{&113gΈ|ބgΜ@.]z'\ti2q#϶n*|;N&O>-歞z,*BP,^-ku8]v >^,`Kv4)nߩS ޤbB?禰@xKCZPP0h4ꋋSư,c8bJ:^M:N&uG-<>;є0h4h4~M?6swO9@^] w @PJwsRyeĉ}?~|qjJKKQ(G׿*Jp}cǎ}m۶L6Nh/X(`׮]0 [o=77w8aHaŋ/]߿ߒHl)))yeفp+9s ] ҶmۦM0$33H$Bp$IZredogfssssM&Sn?7ŋSSSsv#G<l!I ݑoo|G}ǿ(jAN~f͚uJϟ-[͛oqrRI' q<"))q8y9_hYY$s-اOyRR|nycΝ]@%%%V-K.裏6x=""{9JBQTUUE EE`Vp8T|SFﴬL;gΜnRt͟?ˢ9[l!">>ޤwfI||q͛uĉվV)NWT\\|lokwTA$Iҟ~iq|p;>wm'N:o޼'NX aNgOt:Qedd(H.rWFGjR $I^&I<Z  0CM6=hXC%+`4|mK-WHte Z~jaS?ApgoMq\QZT*U 劊^_XPPX^^ވiӦ|ѻ~III &,...d2E^A~ iii{Z˗/~8p;(ׯ_K<״~}X.<@[{l >x<8~}ݺu=%"!sa\.Zd>ݶ#5Nd2qNWT*U x`ή999ptRTt:YVW t eف%R=Ǎm_J͛7wK@Dz2Qcc~f@PW_/\|,OV,+0 $V\iol8^-+W|Pʻ ###A(^'xb%K6.9hl"H'N\W /۷GFu$ʕ+ E.^8D̳n~{/EQŋzH;vlcdd'[d6;P _}wdYV)Hp[sΝ{tʔ)oqVZ5kb0|AW_ɳeFAum]8. ӧWA|(9t r??fff֋D"lݺu&q3\.࿳TVV&2 #dYvHDDD5kv]Oĉ?+xWرo>T$;zp8ݻ9sNhx8~VR}U4: Ans/9(6 v`ǯpbن4=?O4MyX-Njl6po:K0j8ڹs<))jÇr GOю,**J9sfUWW'8vX4jݿ=TZhQǏnTVV y$ IҚsn+V:mD"cݺu eUYY)*>NjN53I?\-_|s=(++#FCMtiiig0 s655^|ŗiii<^v\~&55@cPRRvnٲM6ܙk&++k?ȑ#[6o|'|bBBžӧ{{n2+++V@gTTTdΜ9j#ӧO~[YYYwTRRB==""܎;ƧOMMXRR"]fj/^".qXJ 8:PxpΜ9_O6;FS- [{2t%:kV$)>kXc-RP4+?j(u IIIxǏOb;nbb 55̫x޼y NSt̙3sV^ErssZ,&L0SnnŒ8a„D"^z ÇOV)q8Qo[ppuEW)))k^jU 4M/ohk!H977be„ E>=b̈́ {`ӧOK _,;N z뭿qw)99yfS͛7SPxiH$2Y)SSO=uرcޱcG֝wP---رc+Ϗ~{ Lcc#;& RRR>ݷo¢_tI1cƌWk ޞ/jKa5kr>z޽v\/^W|ӹf͚|A޽{hѢN5Ryeƍ̜9s?XE~饗^;vlcBBBmV3~S'On}_+SRRvDE7Vmزe˞F )=|֯_ՙ3g.XHtzΜ9Jlĉ9uW:9m7)ّ뢙o8t1 =X 2^iQ}Y-Ojĝj罬V^hzd?21>0mv18nd?y>4iҹ!55uǯUwĉb$s`ĴѵvWPb`j9UyV˲e|p}eҥ-[vAb4/..~a,B3Bρ[O5k־Yf]H$yc" yJ$i|ʼF̞hmW0Z~ b-ZvM|e"r׮] Fqbqq񤆆q,*TbBR:^&2cW6_vݽ6 _c- XpEkqvmT vTQQqRAn*[^4t571-a V_Tb ۮr+R!MCOx aЉ *pd))){ϟ0%%R&]A@<_{/ZD ⭖Quk0 s577þ}dFQA5A%LV鿠2 ~e&NZ"(rjYl˗u'N\(JI|:AA?}R($%=bfXD")?p@h|ܽrDе _T*>q<--H$/q 7Y-t :N9" Hs3 oNWFT  "jpgPYe2YN۫N:2!!ųY@t$ 7[-.~xO~A@ ӃV'' QH@˜V"w]OX,ʠ*CT'@ ` 0LrjZyA@ z$ 72@ }$ H@% K A D@@ .@ ] @ ws\tEXtSoftwarewww.inkscape.org<IENDB`rack-3.1.12/test/multipart/robust_field_separation000066400000000000000000000001501476365375000223630ustar00rootroot00000000000000--AaB03x content-disposition: form-data;name="text" content-type: text/plain contents --AaB03x-- rack-3.1.12/test/multipart/semicolon000066400000000000000000000001771476365375000174560ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="files"; filename="fi;le1.txt" content-type: text/plain contents --AaB03x--rack-3.1.12/test/multipart/space case.txt000066400000000000000000000000101476365375000202550ustar00rootroot00000000000000contentsrack-3.1.12/test/multipart/text000066400000000000000000000004631476365375000164500ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="submit-name" Larry --AaB03x content-disposition: form-data; name="submit-name-with-content" content-type: text/plain Berry --AaB03x content-disposition: form-data; name="files"; filename="file1.txt" content-type: text/plain contents --AaB03x--rack-3.1.12/test/multipart/three_files_three_fields000066400000000000000000000015561476365375000224760ustar00rootroot00000000000000--AaB03x content-disposition: form-data; name="reply" yes --AaB03x content-disposition: form-data; name="to" people --AaB03x content-disposition: form-data; name="from" others --AaB03x content-disposition: form-data; name="fileupload1"; filename="file1.jpg" content-type: image/jpeg content-transfer-encoding: base64 /9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg --AaB03x content-disposition: form-data; name="fileupload2"; filename="file2.jpg" content-type: image/jpeg content-transfer-encoding: base64 /9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg --AaB03x content-disposition: form-data; name="fileupload3"; filename="file3.jpg" content-type: image/jpeg content-transfer-encoding: base64 /9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg --AaB03x-- rack-3.1.12/test/multipart/unity3d_wwwform000066400000000000000000000004571476365375000206560ustar00rootroot00000000000000--AaB03x Content-Type: text/plain; charset="utf-8" Content-disposition: form-data; name="user_sid" bbf14f82-d2aa-4c07-9fb8-ca6714a7ea97 --AaB03x Content-Type: image/png; charset=UTF-8 Content-disposition: form-data; name="file"; filename="b67879ed-bfed-4491-a8cc-f99cca769f94.png" --AaB03x rack-3.1.12/test/multipart/webkit000066400000000000000000000014531476365375000167510ustar00rootroot00000000000000------WebKitFormBoundaryWLHCs9qmcJJoyjKR content-disposition: form-data; name="_method" put ------WebKitFormBoundaryWLHCs9qmcJJoyjKR content-disposition: form-data; name="profile[blog]" ------WebKitFormBoundaryWLHCs9qmcJJoyjKR content-disposition: form-data; name="profile[public_email]" ------WebKitFormBoundaryWLHCs9qmcJJoyjKR content-disposition: form-data; name="profile[interests]" ------WebKitFormBoundaryWLHCs9qmcJJoyjKR content-disposition: form-data; name="profile[bio]" hello "quote" ------WebKitFormBoundaryWLHCs9qmcJJoyjKR content-disposition: form-data; name="media"; filename="" Content-Type: application/octet-stream ------WebKitFormBoundaryWLHCs9qmcJJoyjKR content-disposition: form-data; name="commit" Save ------WebKitFormBoundaryWLHCs9qmcJJoyjKR-- rack-3.1.12/test/psych_fix.rb000066400000000000000000000004211476365375000160330ustar00rootroot00000000000000# frozen_string_literal: true # Work correctly with older versions of Psych, having # unsafe_load call load (in older versions, load operates # as unsafe_load in current version). unless YAML.respond_to?(:unsafe_load) def YAML.unsafe_load(body) load(body) end end rack-3.1.12/test/rackup/000077500000000000000000000000001476365375000150025ustar00rootroot00000000000000rack-3.1.12/test/rackup/.gitignore000066400000000000000000000000131476365375000167640ustar00rootroot00000000000000log_output rack-3.1.12/test/rackup/config.ru000066400000000000000000000015321476365375000166200ustar00rootroot00000000000000# frozen_string_literal: true require_relative "../test_request" $stderr = File.open("#{File.dirname(__FILE__)}/log_output", "w") class EnvMiddleware def initialize(app) @app = app end def call(env) # provides a way to test that lint is present if env["PATH_INFO"] == "/broken_lint" return [200, {}, ["Broken Lint"]] # provides a way to kill the process without knowing the pid elsif env["PATH_INFO"] == "/die" exit! end env["test.$DEBUG"] = $DEBUG env["test.$EVAL"] = BUKKIT if defined?(BUKKIT) env["test.$VERBOSE"] = $VERBOSE env["test.$LOAD_PATH"] = $LOAD_PATH env["test.stderr"] = File.expand_path($stderr.path) env["test.Ping"] = defined?(Ping) env["test.pid"] = Process.pid @app.call(env) end end use EnvMiddleware run TestRequest.new rack-3.1.12/test/spec_auth_basic.rb000066400000000000000000000055661476365375000171720ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/auth/basic' require_relative '../lib/rack/mock_request' require_relative '../lib/rack/lint' end describe Rack::Auth::Basic do def realm 'WallysWorld' end def unprotected_app Rack::Lint.new lambda { |env| [ 200, { 'content-type' => 'text/plain' }, ["Hi #{env['REMOTE_USER']}"] ] } end def protected_app app = Rack::Auth::Basic.new(unprotected_app) { |username, password| 'Boss' == username } app.realm = realm app end before do @request = Rack::MockRequest.new(protected_app) end def request_with_basic_auth(username, password, &block) request 'HTTP_AUTHORIZATION' => 'Basic ' + ["#{username}:#{password}"].pack("m*"), &block end def request(headers = {}) yield @request.get('/', headers) end def assert_basic_auth_challenge(response) response.must_be :client_error? response.status.must_equal 401 response.must_include 'www-authenticate' response.headers['www-authenticate'].must_match(/Basic realm="#{Regexp.escape(realm)}"/) response.body.must_be :empty? end it 'challenge correctly when no credentials are specified' do request do |response| assert_basic_auth_challenge response end end it 'rechallenge if incorrect credentials are specified' do request_with_basic_auth 'joe', 'password' do |response| assert_basic_auth_challenge response end end it 'return application output if correct credentials are specified' do request_with_basic_auth 'Boss', 'password' do |response| response.status.must_equal 200 response.body.to_s.must_equal 'Hi Boss' end end it 'return 400 Bad Request if different auth scheme used' do request 'HTTP_AUTHORIZATION' => 'Digest params' do |response| response.must_be :client_error? response.status.must_equal 400 response.wont_include 'www-authenticate' end end it 'return 400 Bad Request for a malformed authorization header' do request 'HTTP_AUTHORIZATION' => '' do |response| response.must_be :client_error? response.status.must_equal 400 response.wont_include 'www-authenticate' end end it 'return 401 Bad Request for a nil authorization header' do request 'HTTP_AUTHORIZATION' => nil do |response| response.must_be :client_error? response.status.must_equal 401 end end it 'return 400 Bad Request for a authorization header with only username' do auth = 'Basic ' + ['foo'].pack("m*") request 'HTTP_AUTHORIZATION' => auth do |response| response.must_be :client_error? response.status.must_equal 400 response.wont_include 'www-authenticate' end end it 'takes realm as optional constructor arg' do app = Rack::Auth::Basic.new(unprotected_app, realm) { true } realm.must_equal app.realm end end rack-3.1.12/test/spec_body_proxy.rb000066400000000000000000000062731476365375000172620ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/body_proxy' end describe Rack::BodyProxy do it 'call each on the wrapped body' do called = false proxy = Rack::BodyProxy.new(['foo']) { } proxy.each do |str| called = true str.must_equal 'foo' end called.must_equal true end it 'call close on the wrapped body' do body = StringIO.new proxy = Rack::BodyProxy.new(body) { } proxy.close body.must_be :closed? end it 'only call close on the wrapped body if it responds to close' do body = [] proxy = Rack::BodyProxy.new(body) { } proxy.close.must_be_nil end it 'call the passed block on close' do called = false proxy = Rack::BodyProxy.new([]) { called = true } called.must_equal false proxy.close called.must_equal true end it 'call the passed block on close even if there is an exception' do object = Object.new def object.close() raise "No!" end called = false begin proxy = Rack::BodyProxy.new(object) { called = true } called.must_equal false proxy.close rescue RuntimeError => e end raise "Expected exception to have been raised" unless e called.must_equal true end it 'allow multiple arguments in respond_to?' do body = [] proxy = Rack::BodyProxy.new(body) { } proxy.respond_to?(:foo, false).must_equal false end it 'allows #method to work with delegated methods' do body = Object.new def body.banana; :pear end proxy = Rack::BodyProxy.new(body) { } proxy.method(:banana).call.must_equal :pear end it 'allows calling delegated methods with keywords' do body = Object.new def body.banana(foo: nil); foo end proxy = Rack::BodyProxy.new(body) { } proxy.banana(foo: 1).must_equal 1 end it 'respond to :to_ary if body does responds to it, and have to_ary call close' do proxy_closed = false proxy = Rack::BodyProxy.new([]) { proxy_closed = true } proxy.respond_to?(:to_ary).must_equal true proxy_closed.must_equal false proxy.to_ary.must_equal [] proxy_closed.must_equal true end it 'not respond to :to_ary if body does not respond to it' do proxy = Rack::BodyProxy.new([].map) { } proxy.respond_to?(:to_ary).must_equal false proc do proxy.to_ary end.must_raise NoMethodError end it 'not respond to :to_str' do proxy = Rack::BodyProxy.new("string body") { } proxy.respond_to?(:to_str).must_equal false proc do proxy.to_str end.must_raise NoMethodError end it 'not respond to :to_path if body does not respond to it' do proxy = Rack::BodyProxy.new("string body") { } proxy.respond_to?(:to_path).must_equal false proc do proxy.to_path end.must_raise NoMethodError end it 'not close more than one time' do count = 0 proxy = Rack::BodyProxy.new([]) { count += 1; raise "Block invoked more than 1 time!" if count > 1 } 2.times { proxy.close } count.must_equal 1 end it 'be closed when the callback is triggered' do closed = false proxy = Rack::BodyProxy.new([]) { closed = proxy.closed? } proxy.close closed.must_equal true end end rack-3.1.12/test/spec_builder.rb000066400000000000000000000221361476365375000165060ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/builder' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' require_relative '../lib/rack/content_length' require_relative '../lib/rack/show_exceptions' require_relative '../lib/rack/auth/basic' end class NothingMiddleware def initialize(app, **) @app = app end def call(env) @@env = env response = @app.call(env) response end def self.env @@env end end describe Rack::Builder do def builder(&block) Rack::Lint.new Rack::Builder.new(&block) end def builder_to_app(&block) Rack::Lint.new Rack::Builder.new(&block).to_app end it "can provide options" do builder = Rack::Builder.new(foo: :bar) builder.options[:foo].must_equal :bar end it "supports run with block" do app = builder_to_app do run {|env| [200, { "content-type" => "text/plain" }, ["OK"]]} end Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'OK' end it "supports mapping" do app = builder_to_app do map '/' do |outer_env| run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['root']] } end map '/sub' do run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['sub']] } end end Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'root' Rack::MockRequest.new(app).get("/sub").body.to_s.must_equal 'sub' end it "supports use when mapping" do app = builder_to_app do map '/sub' do use Rack::ContentLength run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['sub']] } end use Rack::ContentLength run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['root']] } end Rack::MockRequest.new(app).get("/").headers['content-length'].must_equal '4' Rack::MockRequest.new(app).get("/sub").headers['content-length'].must_equal '3' end it "doesn't dupe env even when mapping" do app = builder_to_app do use NothingMiddleware, noop: :noop map '/' do |outer_env| run lambda { |inner_env| inner_env['new_key'] = 'new_value' [200, { "content-type" => "text/plain" }, ['root']] } end end Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'root' NothingMiddleware.env['new_key'].must_equal 'new_value' end it "dupe #to_app when mapping so Rack::Reloader can reload the application on each request" do app = builder do map '/' do |outer_env| run lambda { |env| [200, { "content-type" => "text/plain" }, [object_id.to_s]] } end end builder_app1_id = Rack::MockRequest.new(app).get("/").body.to_s builder_app2_id = Rack::MockRequest.new(app).get("/").body.to_s builder_app2_id.wont_equal builder_app1_id end it "chains apps by default" do app = builder_to_app do use Rack::ShowExceptions run lambda { |env| raise "bzzzt" } end Rack::MockRequest.new(app).get("/").must_be :server_error? Rack::MockRequest.new(app).get("/").must_be :server_error? Rack::MockRequest.new(app).get("/").must_be :server_error? end it "has implicit #to_app" do app = builder do use Rack::ShowExceptions run lambda { |env| raise "bzzzt" } end Rack::MockRequest.new(app).get("/").must_be :server_error? Rack::MockRequest.new(app).get("/").must_be :server_error? Rack::MockRequest.new(app).get("/").must_be :server_error? end it "supports blocks on use" do app = builder do use Rack::ShowExceptions use Rack::Auth::Basic do |username, password| 'secret' == password end run lambda { |env| [200, { "content-type" => "text/plain" }, ['Hi Boss']] } end response = Rack::MockRequest.new(app).get("/") response.must_be :client_error? response.status.must_equal 401 # with auth... response = Rack::MockRequest.new(app).get("/", 'HTTP_AUTHORIZATION' => 'Basic ' + ["joe:secret"].pack("m*")) response.status.must_equal 200 response.body.to_s.must_equal 'Hi Boss' end it "has explicit #to_app" do app = builder do use Rack::ShowExceptions run lambda { |env| raise "bzzzt" } end Rack::MockRequest.new(app).get("/").must_be :server_error? Rack::MockRequest.new(app).get("/").must_be :server_error? Rack::MockRequest.new(app).get("/").must_be :server_error? end it "can mix map and run for endpoints" do app = builder do map '/sub' do run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['sub']] } end run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['root']] } end Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'root' Rack::MockRequest.new(app).get("/sub").body.to_s.must_equal 'sub' end it "accepts middleware-only map blocks" do app = builder do map('/foo') { use Rack::ShowExceptions } run lambda { |env| raise "bzzzt" } end proc { Rack::MockRequest.new(app).get("/") }.must_raise(RuntimeError) Rack::MockRequest.new(app).get("/foo").must_be :server_error? end it "yields the generated app to a block for warmup" do warmed_up_app = nil app = Rack::Builder.new do warmup { |a| warmed_up_app = a } run lambda { |env| [200, {}, []] } end.to_app warmed_up_app.must_equal app end it "initialize apps once" do app = builder do class AppClass def initialize @called = 0 end def call(env) raise "bzzzt" if @called > 0 @called += 1 [200, { 'content-type' => 'text/plain' }, ['OK']] end end use Rack::ShowExceptions run AppClass.new end Rack::MockRequest.new(app).get("/").status.must_equal 200 Rack::MockRequest.new(app).get("/").must_be :server_error? end it "allows use after run" do app = builder do run lambda { |env| raise "bzzzt" } use Rack::ShowExceptions end Rack::MockRequest.new(app).get("/").must_be :server_error? Rack::MockRequest.new(app).get("/").must_be :server_error? Rack::MockRequest.new(app).get("/").must_be :server_error? end it "supports #freeze_app for freezing app and middleware" do app = builder do freeze_app use Rack::ShowExceptions use(Class.new do def initialize(app) @app = app end def call(env) @a = 1 if env['PATH_INFO'] == '/a'; @app.call(env) end end) o = Object.new def o.call(env) @a = 1 if env['PATH_INFO'] == '/b'; [200, {}, []] end run o end Rack::MockRequest.new(app).get("/a").must_be :server_error? Rack::MockRequest.new(app).get("/b").must_be :server_error? Rack::MockRequest.new(app).get("/c").status.must_equal 200 end it 'complains about a missing run' do proc do Rack::Lint.new Rack::Builder.app { use Rack::ShowExceptions } end.must_raise(RuntimeError) end describe "parse_file" do def config_file(name) File.join(File.dirname(__FILE__), 'builder', name) end it "raises if parses commented options" do proc do Rack::Builder.parse_file config_file('options.ru') end.must_raise(RuntimeError). message.must_include('Parsing options from the first comment line is no longer supported') end it "removes __END__ before evaluating app" do app, _ = Rack::Builder.parse_file config_file('end.ru') Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'OK' end it "supports multi-line comments" do app = Rack::Builder.parse_file(config_file('comment.ru')) app.must_be_kind_of(Proc) end it 'requires an_underscore_app not ending in .ru' do $: << File.dirname(__FILE__) app, * = Rack::Builder.parse_file 'builder/an_underscore_app' Rack::MockRequest.new(app).get('/').body.to_s.must_equal 'OK' $:.pop end it "sets __LINE__ correctly" do app, _ = Rack::Builder.parse_file config_file('line.ru') Rack::MockRequest.new(app).get("/").body.to_s.must_equal '3' end it "strips leading unicode byte order mark when present" do enc = Encoding.default_external begin verbose, $VERBOSE = $VERBOSE, nil Encoding.default_external = 'UTF-8' app, _ = Rack::Builder.parse_file config_file('bom.ru') Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'OK' ensure Encoding.default_external = enc $VERBOSE = verbose end end it "respects the frozen_string_literal magic comment" do app, _ = Rack::Builder.parse_file(config_file('frozen.ru')) response = Rack::MockRequest.new(app).get('/') response.body.must_equal 'frozen' body = response.instance_variable_get(:@body) body.must_equal(['frozen']) body[0].frozen?.must_equal true end end describe 'new_from_string' do it "builds a rack app from string" do app, = Rack::Builder.new_from_string "run lambda{|env| [200, {'content-type' => 'text/plane'}, ['OK']] }" Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'OK' end end end rack-3.1.12/test/spec_cascade.rb000066400000000000000000000061251476365375000164430ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/cascade' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' require_relative '../lib/rack/urlmap' require_relative '../lib/rack/files' end describe Rack::Cascade do def cascade(*args) Rack::Lint.new Rack::Cascade.new(*args) end docroot = File.expand_path(File.dirname(__FILE__)) app1 = Rack::Files.new(docroot) app2 = Rack::URLMap.new("/crash" => lambda { |env| raise "boom" }) app3 = Rack::URLMap.new("/foo" => lambda { |env| [200, { "content-type" => "text/plain" }, [""]]}) it "dispatch onward on 404 and 405 by default" do cascade = cascade([app1, app2, app3]) Rack::MockRequest.new(cascade).get("/cgi/test").must_be :ok? Rack::MockRequest.new(cascade).get("/foo").must_be :ok? Rack::MockRequest.new(cascade).get("/toobad").must_be :not_found? Rack::MockRequest.new(cascade).get("/cgi/../..").must_be :client_error? # Put is not allowed by Rack::Files so it'll 405. Rack::MockRequest.new(cascade).put("/foo").must_be :ok? end it "dispatch onward on whatever is passed" do cascade = cascade([app1, app2, app3], [404, 403]) Rack::MockRequest.new(cascade).get("/cgi/../bla").must_be :not_found? end it "include? returns whether app is included" do cascade = Rack::Cascade.new([app1, app2]) cascade.include?(app1).must_equal true cascade.include?(app2).must_equal true cascade.include?(app3).must_equal false end it "return 404 if empty" do Rack::MockRequest.new(cascade([])).get('/').must_be :not_found? end it "uses new response object if empty" do app = Rack::Cascade.new([]) res = app.call('/') s, h, body = res s.must_equal 404 h['content-type'].must_equal 'text/plain' body.must_be_empty res[0] = 200 h['content-type'] = 'text/html' body << "a" res = app.call('/') s, h, body = res s.must_equal 404 h['content-type'].must_equal 'text/plain' body.must_be_empty end it "returns final response if all responses are cascaded" do app = Rack::Cascade.new([]) app << lambda { |env| [405, {}, []] } app.call({})[0].must_equal 405 end it "append new app" do cascade = Rack::Cascade.new([], [404, 403]) Rack::MockRequest.new(cascade).get('/').must_be :not_found? cascade << app2 Rack::MockRequest.new(cascade).get('/cgi/test').must_be :not_found? Rack::MockRequest.new(cascade).get('/cgi/../bla').must_be :not_found? cascade << app1 Rack::MockRequest.new(cascade).get('/cgi/test').must_be :ok? Rack::MockRequest.new(cascade).get('/cgi/../..').must_be :client_error? Rack::MockRequest.new(cascade).get('/foo').must_be :not_found? cascade << app3 Rack::MockRequest.new(cascade).get('/foo').must_be :ok? end it "close the body on cascade" do body = StringIO.new closer = lambda { |env| [404, {}, body] } cascade = Rack::Cascade.new([closer, app3], [404]) Rack::MockRequest.new(cascade).get("/foo").must_be :ok? body.must_be :closed? end end rack-3.1.12/test/spec_common_logger.rb000066400000000000000000000110471476365375000177060ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'logger' separate_testing do require_relative '../lib/rack/common_logger' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::CommonLogger do obj = 'foobar' length = obj.size app = Rack::Lint.new lambda { |env| [200, { "content-type" => "text/html", "content-length" => length.to_s }, [obj]]} app_without_length = Rack::Lint.new lambda { |env| [200, { "content-type" => "text/html" }, []]} app_with_zero_length = Rack::Lint.new lambda { |env| [200, { "content-type" => "text/html", "content-length" => "0" }, []]} app_without_lint = lambda { |env| [200, { "content-type" => "text/html", "content-length" => length.to_s }, [obj]]} it "log to rack.errors by default" do res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/") res.errors.wont_be :empty? res.errors.must_match(/"GET \/ HTTP\/1\.1" 200 #{length} /) end it "log to anything with +write+" do log = StringIO.new Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/") log.string.must_match(/"GET \/ HTTP\/1\.1" 200 #{length} /) end it "work with standard library logger" do logdev = StringIO.new log = Logger.new(logdev) Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/") logdev.string.must_match(/"GET \/ HTTP\/1\.1" 200 #{length} /) end it "log - content length if header is missing" do res = Rack::MockRequest.new(Rack::CommonLogger.new(app_without_length)).get("/") res.errors.wont_be :empty? res.errors.must_match(/"GET \/ HTTP\/1\.1" 200 - /) end it "log - content length if header is zero" do res = Rack::MockRequest.new(Rack::CommonLogger.new(app_with_zero_length)).get("/") res.errors.wont_be :empty? res.errors.must_match(/"GET \/ HTTP\/1\.1" 200 - /) end it "log - records host from X-Forwarded-For header" do res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/", 'HTTP_X_FORWARDED_FOR' => '203.0.113.0') res.errors.wont_be :empty? res.errors.must_match(/203\.0\.113\.0 - /) end it "log - records host from RFC 7239 forwarded for header" do res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/", 'HTTP_FORWARDED' => 'for=203.0.113.0') res.errors.wont_be :empty? res.errors.must_match(/203\.0\.113\.0 - /) end def with_mock_time(t = 0) mc = class << Time; self; end mc.send :alias_method, :old_now, :now mc.send :define_method, :now do at(t) end yield ensure mc.send :undef_method, :now mc.send :alias_method, :now, :old_now end it "log in common log format" do log = StringIO.new with_mock_time do Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/", 'QUERY_STRING' => 'foo=bar') end md = /- - - \[([^\]]+)\] "(\w+) \/\?foo=bar HTTP\/1\.1" (\d{3}) \d+ ([\d\.]+)/.match(log.string) md.wont_equal nil time, method, status, duration = *md.captures time.must_equal Time.at(0).strftime("%d/%b/%Y:%H:%M:%S %z") method.must_equal "GET" status.must_equal "200" (0..1).must_include duration.to_f end it "escapes non printable characters including newline" do logdev = StringIO.new log = Logger.new(logdev) Rack::MockRequest.new(Rack::CommonLogger.new(app_without_lint, log)).request("GET\x1f", "/hello") logdev.string.must_match(/GET\\x1f \/hello HTTP\/1\.1/) Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/", 'REMOTE_USER' => "foo\nbar", "QUERY_STRING" => "bar\nbaz") logdev.string[-1].must_equal "\n" logdev.string.must_include("foo\\xabar") logdev.string.must_include("bar\\xabaz") end it "log path with PATH_INFO" do logdev = StringIO.new log = Logger.new(logdev) Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/hello") logdev.string.must_match(/"GET \/hello HTTP\/1\.1" 200 #{length} /) end it "log path with SCRIPT_NAME" do logdev = StringIO.new log = Logger.new(logdev) Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/path", script_name: "/script") logdev.string.must_match(/"GET \/script\/path HTTP\/1\.1" 200 #{length} /) end it "log path with SERVER_PROTOCOL" do logdev = StringIO.new log = Logger.new(logdev) Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/path", http_version: "HTTP/1.0") logdev.string.must_match(/"GET \/path HTTP\/1\.0" 200 #{length} /) end def length 123 end def self.obj "hello world" end end rack-3.1.12/test/spec_conditional_get.rb000066400000000000000000000113671476365375000202260ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'time' separate_testing do require_relative '../lib/rack/conditional_get' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::ConditionalGet do def conditional_get(app) Rack::Lint.new Rack::ConditionalGet.new(app) end it "set a 304 status and truncate body when if-modified-since hits" do timestamp = Time.now.httpdate app = conditional_get(lambda { |env| [200, { 'last-modified' => timestamp }, ['TEST']] }) response = Rack::MockRequest.new(app). get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp) response.status.must_equal 304 response.body.must_be :empty? end it "set a 304 status and truncate body when if-modified-since hits and is higher than current time" do app = conditional_get(lambda { |env| [200, { 'last-modified' => (Time.now - 3600).httpdate }, ['TEST']] }) response = Rack::MockRequest.new(app). get("/", 'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate) response.status.must_equal 304 response.body.must_be :empty? end it "closes bodies" do body = Object.new def body.each; yield 'TEST' end closed = false body.define_singleton_method(:close){closed = true} app = conditional_get(lambda { |env| [200, { 'last-modified' => (Time.now - 3600).httpdate }, body] }) response = Rack::MockRequest.new(app). get("/", 'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate) response.status.must_equal 304 response.body.must_be :empty? closed.must_equal true end it "set a 304 status and truncate body when if-none-match hits" do app = conditional_get(lambda { |env| [200, { 'etag' => '1234' }, ['TEST']] }) response = Rack::MockRequest.new(app). get("/", 'HTTP_IF_NONE_MATCH' => '1234') response.status.must_equal 304 response.body.must_be :empty? end it "set a 304 status and truncate body when if-none-match hits but if-modified-since is after last-modified" do app = conditional_get(lambda { |env| [200, { 'last-modified' => (Time.now + 3600).httpdate, 'etag' => '1234', 'content-type' => 'text/plain' }, ['TEST']] }) response = Rack::MockRequest.new(app). get("/", 'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate, 'HTTP_IF_NONE_MATCH' => '1234') response.status.must_equal 304 response.body.must_be :empty? end it "not set a 304 status if last-modified is too short" do app = conditional_get(lambda { |env| [200, { 'last-modified' => '1234', 'content-type' => 'text/plain' }, ['TEST']] }) response = Rack::MockRequest.new(app). get("/", 'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate) response.status.must_equal 200 response.body.must_equal 'TEST' end it "not set a 304 status if if-modified-since hits but etag does not" do timestamp = Time.now.httpdate app = conditional_get(lambda { |env| [200, { 'last-modified' => timestamp, 'etag' => '1234', 'content-type' => 'text/plain' }, ['TEST']] }) response = Rack::MockRequest.new(app). get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '4321') response.status.must_equal 200 response.body.must_equal 'TEST' end it "set a 304 status and truncate body when both if-none-match and if-modified-since hits" do timestamp = Time.now.httpdate app = conditional_get(lambda { |env| [200, { 'last-modified' => timestamp, 'etag' => '1234' }, ['TEST']] }) response = Rack::MockRequest.new(app). get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '1234') response.status.must_equal 304 response.body.must_be :empty? end it "not affect non-GET/HEAD requests" do app = conditional_get(lambda { |env| [200, { 'etag' => '1234', 'content-type' => 'text/plain' }, ['TEST']] }) response = Rack::MockRequest.new(app). post("/", 'HTTP_IF_NONE_MATCH' => '1234') response.status.must_equal 200 response.body.must_equal 'TEST' end it "not affect non-200 requests" do app = conditional_get(lambda { |env| [302, { 'etag' => '1234', 'content-type' => 'text/plain' }, ['TEST']] }) response = Rack::MockRequest.new(app). get("/", 'HTTP_IF_NONE_MATCH' => '1234') response.status.must_equal 302 response.body.must_equal 'TEST' end it "not affect requests with malformed HTTP_IF_NONE_MATCH" do bad_timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S %z') app = conditional_get(lambda { |env| [200, { 'last-modified' => (Time.now - 3600).httpdate, 'content-type' => 'text/plain' }, ['TEST']] }) response = Rack::MockRequest.new(app). get("/", 'HTTP_IF_MODIFIED_SINCE' => bad_timestamp) response.status.must_equal 200 response.body.must_equal 'TEST' end end rack-3.1.12/test/spec_config.rb000066400000000000000000000012371476365375000163240ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/config' require_relative '../lib/rack/lint' require_relative '../lib/rack/builder' require_relative '../lib/rack/mock_request' end describe Rack::Config do it "accept a block that modifies the environment" do app = Rack::Builder.new do use Rack::Lint use Rack::Config do |env| env['greeting'] = 'hello' end run lambda { |env| [200, { 'content-type' => 'text/plain' }, [env['greeting'] || '']] } end response = Rack::MockRequest.new(app).get('/') response.body.must_equal 'hello' end end rack-3.1.12/test/spec_content_length.rb000066400000000000000000000056121476365375000200730ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/content_length' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::ContentLength do def content_length(app) Rack::Lint.new Rack::ContentLength.new(app) end def request Rack::MockRequest.env_for end it "set content-length on Array bodies if none is set" do app = lambda { |env| [200, { 'content-type' => 'text/plain' }, ["Hello, World!"]] } response = content_length(app).call(request) response[1]['content-length'].must_equal '13' end it "not set content-length on variable length bodies" do body = lambda { "Hello World!" } def body.each ; yield call ; end app = lambda { |env| [200, { 'content-type' => 'text/plain' }, body] } response = content_length(app).call(request) response[1]['content-length'].must_be_nil end it "not change content-length if it is already set" do app = lambda { |env| [200, { 'content-type' => 'text/plain', 'content-length' => '1' }, "Hello, World!"] } response = content_length(app).call(request) response[1]['content-length'].must_equal '1' end it "not set content-length on 304 responses" do app = lambda { |env| [304, {}, []] } response = content_length(app).call(request) response[1]['content-length'].must_be_nil end it "not set content-length when transfer-encoding is chunked" do app = lambda { |env| [200, { 'content-type' => 'text/plain', 'transfer-encoding' => 'chunked' }, []] } response = content_length(app).call(request) response[1]['content-length'].must_be_nil end # Using "Connection: close" for this is fairly contended. It might be useful # to have some other way to signal this. # # should "not force a content-length when Connection:close" do # app = lambda { |env| [200, {'Connection' => 'close'}, []] } # response = content_length(app).call({}) # response[1]['content-length'].must_be_nil # end it "close bodies that need to be closed" do body = Struct.new(:body) do attr_reader :closed def each; body.each {|b| yield b}; close; end def close; @closed = true; end def to_ary; enum_for.to_a; end end.new(%w[one two three]) app = lambda { |env| [200, { 'content-type' => 'text/plain' }, body] } content_length(app).call(request) body.closed.must_equal true end it "support single-execute bodies" do body = Struct.new(:body) do def each yield body.shift until body.empty? end def to_ary; enum_for.to_a; end end.new(%w[one two three]) app = lambda { |env| [200, { 'content-type' => 'text/plain' }, body] } response = content_length(app).call(request) expected = %w[one two three] response[1]['content-length'].must_equal expected.join.size.to_s response[2].to_enum.to_a.must_equal expected end end rack-3.1.12/test/spec_content_type.rb000066400000000000000000000025431476365375000175730ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/content_type' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::ContentType do def content_type(app, *args) Rack::Lint.new Rack::ContentType.new(app, *args) end def request Rack::MockRequest.env_for end it "set content-type to default text/html if none is set" do app = lambda { |env| [200, {}, "Hello, World!"] } headers = content_type(app).call(request)[1] headers['content-type'].must_equal 'text/html' end it "set content-type to chosen default if none is set" do app = lambda { |env| [200, {}, "Hello, World!"] } headers = content_type(app, 'application/octet-stream').call(request)[1] headers['content-type'].must_equal 'application/octet-stream' end it "not change content-type if it is already set" do app = lambda { |env| [200, { 'content-type' => 'foo/bar' }, "Hello, World!"] } headers = content_type(app).call(request)[1] headers['content-type'].must_equal 'foo/bar' end [100, 204, 304].each do |code| it "not set content-type on #{code} responses" do app = lambda { |env| [code, {}, []] } response = content_type(app, "text/html").call(request) response[1]['content-type'].must_be_nil end end end rack-3.1.12/test/spec_deflater.rb000066400000000000000000000365651476365375000166610ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'time' # for Time#httpdate require 'zlib' separate_testing do require_relative '../lib/rack/deflater' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::Deflater do def build_response(status, body, accept_encoding, options = {}) body = [body] if body.respond_to? :to_str app = lambda do |env| res = [status, options['response_headers'] || {}, body] res[1]['content-type'] = 'text/plain' unless res[0] == 304 res end request = Rack::MockRequest.env_for('', (options['request_headers'] || {}).merge('HTTP_ACCEPT_ENCODING' => accept_encoding)) deflater = Rack::Lint.new Rack::Deflater.new(app, options['deflater_options'] || {}) deflater.call(request) end ## # Constructs response object and verifies if it yields right results # # [expected_status] expected response status, e.g. 200, 304 # [expected_body] expected response body # [accept_encoding] what Accept-Encoding header to send and expect, e.g. # 'deflate' - accepts and expects deflate encoding in response # { 'gzip' => nil } - accepts gzip but expects no encoding in response # [options] hash of request options, i.e. # 'app_status' - what status dummy app should return (may be changed by deflater at some point) # 'app_body' - what body dummy app should return (may be changed by deflater at some point) # 'request_headers' - extra request headers to be sent # 'response_headers' - extra response headers to be returned # 'deflater_options' - options passed to deflater middleware # [block] useful for doing some extra verification def verify(expected_status, expected_body, accept_encoding, options = {}, &block) accept_encoding, expected_encoding = if accept_encoding.kind_of?(Hash) [accept_encoding.keys.first, accept_encoding.values.first] else [accept_encoding, accept_encoding.dup] end start = Time.now.to_i # build response status, headers, body = build_response( options['app_status'] || expected_status, options['app_body'] || expected_body, accept_encoding, options ) # verify status status.must_equal expected_status # verify body unless options['skip_body_verify'] body_text = ''.dup body.each { |part| body_text << part } deflated_body = case expected_encoding when 'deflate' inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) inflater.inflate(body_text) << inflater.finish when 'gzip' io = StringIO.new(body_text) gz = Zlib::GzipReader.new(io) mtime = gz.mtime.to_i if last_mod = headers['last-modified'] Time.httpdate(last_mod).to_i.must_equal mtime else mtime.must_be(:<=, Time.now.to_i) mtime.must_be(:>=, start.to_i - 1) end tmp = gz.read gz.close tmp else body_text end deflated_body.must_equal expected_body end # yield full response verification yield(status, headers, body) if block_given? body.close if body.respond_to?(:close) end # automatic gzip detection (streamable) def auto_inflater Zlib::Inflate.new(32 + Zlib::MAX_WBITS) end def deflate_or_gzip { 'deflate, gzip' => 'gzip' } end it 'be able to deflate bodies that respond to each' do app_body = Object.new class << app_body; def each; yield('foo'); yield('bar'); end; end verify(200, 'foobar', deflate_or_gzip, { 'app_body' => app_body }) do |status, headers, body| headers.must_equal({ 'content-encoding' => 'gzip', 'vary' => 'Accept-Encoding', 'content-type' => 'text/plain' }) end end it 'should not update vary response header if it includes * or accept-encoding' do verify(200, 'foobar', deflate_or_gzip, 'response_headers' => { 'vary' => 'Accept-Encoding' } ) do |status, headers, body| headers['vary'].must_equal 'Accept-Encoding' end verify(200, 'foobar', deflate_or_gzip, 'response_headers' => { 'vary' => '*' } ) do |status, headers, body| headers['vary'].must_equal '*' end verify(200, 'foobar', deflate_or_gzip, 'response_headers' => { 'vary' => 'Do-Not-Accept-Encoding' } ) do |status, headers, body| headers['vary'].must_equal 'Do-Not-Accept-Encoding,Accept-Encoding' end end it 'be able to deflate bodies that respond to each and contain empty chunks' do app_body = Object.new class << app_body; def each; yield('foo'); yield(''); yield('bar'); end; end verify(200, 'foobar', deflate_or_gzip, { 'app_body' => app_body }) do |status, headers, body| headers.must_equal({ 'content-encoding' => 'gzip', 'vary' => 'Accept-Encoding', 'content-type' => 'text/plain' }) end end it 'flush deflated chunks to the client as they become ready' do app_body = Object.new class << app_body; def each; yield('foo'); yield('bar'); end; end verify(200, app_body, deflate_or_gzip, { 'skip_body_verify' => true }) do |status, headers, body| headers.must_equal({ 'content-encoding' => 'gzip', 'vary' => 'Accept-Encoding', 'content-type' => 'text/plain' }) buf = [] inflater = auto_inflater body.each { |part| buf << inflater.inflate(part) } buf << inflater.finish buf.delete_if { |part| part.empty? }.join.must_equal 'foobar' end end it 'does not raise when a client aborts reading' do app_body = Object.new class << app_body; def each; yield('foo'); yield('bar'); end; end opts = { 'skip_body_verify' => true } verify(200, app_body, 'gzip', opts) do |status, headers, body| headers.must_equal({ 'content-encoding' => 'gzip', 'vary' => 'Accept-Encoding', 'content-type' => 'text/plain' }) buf = [] inflater = auto_inflater FakeDisconnect = Class.new(RuntimeError) assert_raises(FakeDisconnect, "not Zlib::DataError not raised") do body.each do |part| tmp = inflater.inflate(part) buf << tmp if tmp.bytesize > 0 raise FakeDisconnect end end inflater.finish buf.must_equal(%w(foo)) end end # TODO: This is really just a special case of the above... it 'be able to deflate String bodies' do verify(200, 'Hello world!', deflate_or_gzip) do |status, headers, body| headers.must_equal({ 'content-encoding' => 'gzip', 'vary' => 'Accept-Encoding', 'content-type' => 'text/plain' }) end end it 'be able to gzip bodies that respond to each' do app_body = Object.new class << app_body; def each; yield('foo'); yield('bar'); end; end verify(200, 'foobar', 'gzip', { 'app_body' => app_body }) do |status, headers, body| headers.must_equal({ 'content-encoding' => 'gzip', 'vary' => 'Accept-Encoding', 'content-type' => 'text/plain' }) end end it 'be able to gzip files' do verify(200, File.binread(__FILE__), 'gzip', { 'app_body' => File.open(__FILE__)}) do |status, headers, body| headers.must_equal({ 'content-encoding' => 'gzip', 'vary' => 'Accept-Encoding', 'content-type' => 'text/plain' }) end end it 'flush gzipped chunks to the client as they become ready' do app_body = Object.new class << app_body; def each; yield('foo'); yield('bar'); end; end verify(200, app_body, 'gzip', { 'skip_body_verify' => true }) do |status, headers, body| headers.must_equal({ 'content-encoding' => 'gzip', 'vary' => 'Accept-Encoding', 'content-type' => 'text/plain' }) buf = [] inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32) body.each { |part| buf << inflater.inflate(part) } buf << inflater.finish buf.delete_if { |part| part.empty? }.join.must_equal 'foobar' end end it 'be able to fallback to no deflation' do verify(200, 'Hello world!', 'superzip') do |status, headers, body| headers.must_equal({ 'vary' => 'Accept-Encoding', 'content-type' => 'text/plain' }) end end it 'be able to skip when there is no response entity body' do verify(304, '', { 'gzip' => nil }, { 'app_body' => [] }) do |status, headers, body| headers.must_equal({}) end end it 'handle the lack of an acceptable encoding' do app_body = 'Hello world!' not_found_body1 = 'An acceptable encoding for the requested resource / could not be found.' not_found_body2 = 'An acceptable encoding for the requested resource /foo/bar could not be found.' options1 = { 'app_status' => 200, 'app_body' => app_body, 'request_headers' => { 'PATH_INFO' => '/' } } options2 = { 'app_status' => 200, 'app_body' => app_body, 'request_headers' => { 'PATH_INFO' => '/foo/bar' } } app_body3 = [app_body] closed = false app_body3.define_singleton_method(:close){closed = true} options3 = { 'app_status' => 200, 'app_body' => app_body3, 'request_headers' => { 'PATH_INFO' => '/' } } verify(406, not_found_body1, 'identity;q=0', options1) do |status, headers, body| headers.must_equal({ 'content-type' => 'text/plain', 'content-length' => not_found_body1.length.to_s }) end verify(406, not_found_body2, 'identity;q=0', options2) do |status, headers, body| headers.must_equal({ 'content-type' => 'text/plain', 'content-length' => not_found_body2.length.to_s }) end verify(406, not_found_body1, 'identity;q=0', options3) do |status, headers, body| headers.must_equal({ 'content-type' => 'text/plain', 'content-length' => not_found_body1.length.to_s }) end closed.must_equal true end it 'handle gzip response with last-modified header' do last_modified = Time.now.httpdate options = { 'response_headers' => { 'content-type' => 'text/plain', 'last-modified' => last_modified } } verify(200, 'Hello World!', 'gzip', options) do |status, headers, body| headers.must_equal({ 'content-encoding' => 'gzip', 'vary' => 'Accept-Encoding', 'last-modified' => last_modified, 'content-type' => 'text/plain' }) end end it 'do nothing when no-transform cache-control directive present' do options = { 'response_headers' => { 'content-type' => 'text/plain', 'cache-control' => 'no-transform' } } verify(200, 'Hello World!', { 'gzip' => nil }, options) do |status, headers, body| headers.wont_include 'content-encoding' end end it 'do nothing when content-encoding already present' do options = { 'response_headers' => { 'content-type' => 'text/plain', 'content-encoding' => 'gzip' } } verify(200, 'Hello World!', { 'gzip' => nil }, options) end it 'deflate when content-encoding is identity' do options = { 'response_headers' => { 'content-type' => 'text/plain', 'content-encoding' => 'identity' } } verify(200, 'Hello World!', deflate_or_gzip, options) end it "deflate if content-type matches :include" do options = { 'response_headers' => { 'content-type' => 'text/plain' }, 'deflater_options' => { include: %w(text/plain) } } verify(200, 'Hello World!', 'gzip', options) end it "deflate if content-type is included it :include" do options = { 'response_headers' => { 'content-type' => 'text/plain; charset=us-ascii' }, 'deflater_options' => { include: %w(text/plain) } } verify(200, 'Hello World!', 'gzip', options) end it "not deflate if content-type is not set but given in :include" do options = { 'deflater_options' => { include: %w(text/plain) } } verify(304, 'Hello World!', { 'gzip' => nil }, options) end it "not deflate if content-type do not match :include" do options = { 'response_headers' => { 'content-type' => 'text/plain' }, 'deflater_options' => { include: %w(text/json) } } verify(200, 'Hello World!', { 'gzip' => nil }, options) end it "not deflate if content-length is 0" do options = { 'response_headers' => { 'content-length' => '0' }, } verify(200, '', { 'gzip' => nil }, options) end it "deflate response if :if lambda evaluates to true" do options = { 'deflater_options' => { if: lambda { |env, status, headers, body| true } } } verify(200, 'Hello World!', deflate_or_gzip, options) end it "not deflate if :if lambda evaluates to false" do options = { 'deflater_options' => { if: lambda { |env, status, headers, body| false } } } verify(200, 'Hello World!', { 'gzip' => nil }, options) end it "check for content-length via :if" do response = 'Hello World!' response_len = response.length options = { 'response_headers' => { 'content-length' => response_len.to_s }, 'deflater_options' => { if: lambda { |env, status, headers, body| headers['content-length'].to_i >= response_len } } } verify(200, response, 'gzip', options) end it 'will honor sync: false to avoid unnecessary flushing' do app_body = Object.new class << app_body def each (0..20).each { |i| yield "hello\n" } end end options = { 'deflater_options' => { sync: false }, 'app_body' => app_body, 'skip_body_verify' => true, } verify(200, app_body, deflate_or_gzip, options) do |status, headers, body| headers.must_equal({ 'content-encoding' => 'gzip', 'vary' => 'Accept-Encoding', 'content-type' => 'text/plain' }) buf = ''.dup raw_bytes = 0 inflater = auto_inflater body.each do |part| raw_bytes += part.bytesize buf << inflater.inflate(part) end buf << inflater.finish expect = "hello\n" * 21 buf.must_equal expect raw_bytes.must_be(:<, expect.bytesize) end end it 'will honor sync: false to avoid unnecessary flushing when deflating files' do content = File.binread(__FILE__) options = { 'deflater_options' => { sync: false }, 'app_body' => File.open(__FILE__), 'skip_body_verify' => true, } verify(200, content, deflate_or_gzip, options) do |status, headers, body| headers.must_equal({ 'content-encoding' => 'gzip', 'vary' => 'Accept-Encoding', 'content-type' => 'text/plain' }) buf = ''.dup raw_bytes = 0 inflater = auto_inflater body.each do |part| raw_bytes += part.bytesize buf << inflater.inflate(part) end buf << inflater.finish buf.must_equal content raw_bytes.must_be(:<, content.bytesize) end end it 'does not close the response body prematurely' do app_body = Class.new do attr_reader :closed; def each; yield('foo'); yield('bar'); end; def close; @closed = true; end; end.new verify(200, 'foobar', deflate_or_gzip, { 'app_body' => app_body }) do |status, headers, body| assert_nil app_body.closed end end end rack-3.1.12/test/spec_directory.rb000066400000000000000000000145651476365375000170730ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'tempfile' require 'fileutils' separate_testing do require_relative '../lib/rack/directory' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' require_relative '../lib/rack/utils' require_relative '../lib/rack/builder' end describe Rack::Directory do DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT FILE_CATCH = proc{|env| [200, { 'content-type' => 'text/plain', "content-length" => "7" }, ['passed!']] } attr_reader :app def setup @app = Rack::Lint.new(Rack::Directory.new(DOCROOT, FILE_CATCH)) end it 'serves directories with + in the name' do Dir.mktmpdir do |dir| plus_dir = "foo+bar" full_dir = File.join(dir, plus_dir) FileUtils.mkdir full_dir FileUtils.touch File.join(full_dir, "omg.txt") app = Rack::Directory.new(dir, FILE_CATCH) env = Rack::MockRequest.env_for("/#{plus_dir}/") status, _, body = app.call env assert_equal 200, status str = ''.dup body.each { |x| str << x } assert_match "foo+bar", str end end it "serve root directory index" do res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/") res.must_be :ok? assert_includes(res.body, '') assert_includes(res.body, "href='cgi") end it "serve directory indices" do res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/cgi/") res.must_be :ok? assert_includes(res.body, '') assert_includes(res.body, "rackup_stub.rb") end it "return 404 for pipes" do begin File.mkfifo('test/cgi/fifo') res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/cgi/fifo") res.status.must_equal 404 ensure File.delete('test/cgi/fifo') end end it "serve directory indices with bad symlinks" do begin File.symlink('foo', 'test/cgi/foo') res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/cgi/") res.must_be :ok? assert_match(res, //) ensure File.delete('test/cgi/foo') end end it "return 404 for unreadable directories" do begin File.write('test/cgi/unreadable', '') File.chmod(0, 'test/cgi/unreadable') res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/cgi/unreadable") res.status.must_equal 404 ensure File.delete('test/cgi/unreadable') end end it "pass to app if file found" do res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/cgi/test") res.must_be :ok? assert_match(res, /passed!/) end it "serve uri with URL encoded filenames" do res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/%63%67%69/") # "/cgi/test" res.must_be :ok? assert_match(res, //) res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/cgi/%74%65%73%74") # "/cgi/test" res.must_be :ok? assert_match(res, /passed!/) end it "serve uri with URL encoded null byte (%00) in filenames" do res = Rack::MockRequest.new(Rack::Lint.new(app)) .get("/cgi/test%00") res.must_be :bad_request? end it "allow directory traversal inside root directory" do res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/cgi/../rackup") res.must_be :ok? res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/cgi/%2E%2E/rackup") res.must_be :ok? end it "not allow directory traversal" do res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/cgi/../../lib") res.must_be :forbidden? res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/cgi/%2E%2E/%2E%2E/lib") res.must_be :forbidden? end it "not allow dir globs" do Dir.mktmpdir do |dir| weirds = "uploads/.?/.?" full_dir = File.join(dir, weirds) FileUtils.mkdir_p full_dir FileUtils.touch File.join(dir, "secret.txt") app = Rack::Directory.new(File.join(dir, "uploads")) res = Rack::MockRequest.new(app).get("/.%3F") refute_match "secret.txt", res.body end end it "404 if it can't find the file" do res = Rack::MockRequest.new(Rack::Lint.new(app)). get("/cgi/blubb") res.must_be :not_found? end it "uri escape path parts" do # #265, properly escape file names mr = Rack::MockRequest.new(Rack::Lint.new(app)) res = mr.get("/cgi/test%2bdirectory") res.must_be :ok? res.body.must_match(Regexp.new(Rack::Utils.escape_html( "/cgi/test\\+directory/test\\+file"))) res = mr.get("/cgi/test%2bdirectory/test%2bfile") res.must_be :ok? end it "correctly escape script name with spaces" do Dir.mktmpdir do |dir| space_dir = "foo bar" full_dir = File.join(dir, space_dir) FileUtils.mkdir full_dir FileUtils.touch File.join(full_dir, "omg omg.txt") app = Rack::Directory.new(dir, FILE_CATCH) env = Rack::MockRequest.env_for(Rack::Utils.escape_path("/#{space_dir}/")) status, _, body = app.call env assert_equal 200, status str = ''.dup body.each { |x| str << x } assert_match Rack::Utils.escape_html("/foo%20bar/omg%20omg.txt"), str end end it "correctly escape script name with '" do Dir.mktmpdir do |dir| quote_dir = "foo'bar" full_dir = File.join(dir, quote_dir) FileUtils.mkdir full_dir FileUtils.touch File.join(full_dir, "omg'omg.txt") app = Rack::Directory.new(dir, FILE_CATCH) env = Rack::MockRequest.env_for(Rack::Utils.escape("/#{quote_dir}/")) status, _, body = app.call env assert_equal 200, status str = ''.dup body.each { |x| str << x } assert_match Rack::Utils.escape_html("/foo'bar/omg'omg.txt"), str end end it "correctly escape script name" do _app = app app2 = Rack::Builder.new do map '/script-path' do run _app end end mr = Rack::MockRequest.new(Rack::Lint.new(app2)) res = mr.get("/script-path/cgi/test%2bdirectory") res.must_be :ok? res.body.must_match(Regexp.new(Rack::Utils.escape_html( "/script-path/cgi/test\\+directory/test\\+file"))) res = mr.get("/script-path/cgi/test+directory/test+file") res.must_be :ok? end it "return error when file not found for head request" do res = Rack::MockRequest.new(Rack::Lint.new(app)). head("/cgi/missing") res.must_be :not_found? res.body.must_be :empty? end end rack-3.1.12/test/spec_etag.rb000066400000000000000000000102301476365375000157700ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'time' separate_testing do require_relative '../lib/rack/etag' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::ETag do def etag(app, *args) Rack::Lint.new Rack::ETag.new(app, *args) end def request Rack::MockRequest.env_for end def sendfile_body File.new(File::NULL) end it "set etag if none is set if status is 200" do app = lambda { |env| [200, { 'content-type' => 'text/plain' }, ["Hello, World!"]] } response = etag(app).call(request) response[1]['etag'].must_equal "W/\"dffd6021bb2bd5b0af676290809ec3a5\"" end it "set etag if none is set if status is 201" do app = lambda { |env| [201, { 'content-type' => 'text/plain' }, ["Hello, World!"]] } response = etag(app).call(request) response[1]['etag'].must_equal "W/\"dffd6021bb2bd5b0af676290809ec3a5\"" end it "set cache-control to 'max-age=0, private, must-revalidate' (default) if none is set" do app = lambda { |env| [201, { 'content-type' => 'text/plain' }, ["Hello, World!"]] } response = etag(app).call(request) response[1]['cache-control'].must_equal 'max-age=0, private, must-revalidate' end it "set cache-control to chosen one if none is set" do app = lambda { |env| [201, { 'content-type' => 'text/plain' }, ["Hello, World!"]] } response = etag(app, nil, 'public').call(request) response[1]['cache-control'].must_equal 'public' end it "set a given cache-control even if digest could not be calculated" do app = lambda { |env| [200, { 'content-type' => 'text/plain' }, []] } response = etag(app, 'no-cache').call(request) response[1]['cache-control'].must_equal 'no-cache' end it "not set cache-control if it is already set" do app = lambda { |env| [201, { 'content-type' => 'text/plain', 'cache-control' => 'public' }, ["Hello, World!"]] } response = etag(app).call(request) response[1]['cache-control'].must_equal 'public' end it "not set cache-control if directive isn't present" do app = lambda { |env| [200, { 'content-type' => 'text/plain' }, ["Hello, World!"]] } response = etag(app, nil, nil).call(request) response[1]['cache-control'].must_be_nil end it "not change etag if it is already set" do app = lambda { |env| [200, { 'content-type' => 'text/plain', 'etag' => '"abc"' }, ["Hello, World!"]] } response = etag(app).call(request) response[1]['etag'].must_equal "\"abc\"" end it "not set etag if body is empty" do app = lambda { |env| [200, { 'content-type' => 'text/plain', 'last-modified' => Time.now.httpdate }, []] } response = etag(app).call(request) response[1]['etag'].must_be_nil end it "set handle empty body parts" do app = lambda { |env| [200, { 'content-type' => 'text/plain' }, ["Hello", "", ", World!"]] } response = etag(app).call(request) response[1]['etag'].must_equal "W/\"dffd6021bb2bd5b0af676290809ec3a5\"" end it "not set etag if last-modified is set" do app = lambda { |env| [200, { 'content-type' => 'text/plain', 'last-modified' => Time.now.httpdate }, ["Hello, World!"]] } response = etag(app).call(request) response[1]['etag'].must_be_nil end it "not set etag if a sendfile_body is given" do app = lambda { |env| [200, { 'content-type' => 'text/plain' }, sendfile_body] } response = etag(app).call(request) response[1]['etag'].must_be_nil end it "not set etag if a status is not 200 or 201" do app = lambda { |env| [401, { 'content-type' => 'text/plain' }, ['Access denied.']] } response = etag(app).call(request) response[1]['etag'].must_be_nil end it "set etag even if no-cache is given" do app = lambda { |env| [200, { 'content-type' => 'text/plain', 'cache-control' => 'no-cache, must-revalidate' }, ['Hello, World!']] } response = etag(app).call(request) response[1]['etag'].must_equal "W/\"dffd6021bb2bd5b0af676290809ec3a5\"" end it "close the original body" do body = StringIO.new app = lambda { |env| [200, {}, body] } response = etag(app).call(request) body.wont_be :closed? response[2].close body.must_be :closed? end end rack-3.1.12/test/spec_events.rb000066400000000000000000000071221476365375000163620ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/events' end module Rack class TestEvents < Minitest::Test class EventMiddleware attr_reader :events def initialize(events) @events = events end def on_start(req, res) events << [self, __method__] end def on_commit(req, res) events << [self, __method__] end def on_send(req, res) events << [self, __method__] end def on_finish(req, res) events << [self, __method__] end def on_error(req, res, e) events << [self, __method__] end end def test_events_fire events = [] ret = [200, {}, []] app = lambda { |env| events << [app, :call]; ret } se = EventMiddleware.new events e = Events.new app, [se] triple = e.call({}) response_body = [] triple[2].each { |x| response_body << x } triple[2].close triple[2] = response_body assert_equal ret, triple assert_equal [[se, :on_start], [app, :call], [se, :on_commit], [se, :on_send], [se, :on_finish], ], events end def test_send_and_finish_are_not_run_until_body_is_sent events = [] ret = [200, {}, []] app = lambda { |env| events << [app, :call]; ret } se = EventMiddleware.new events e = Events.new app, [se] e.call({}) assert_equal [[se, :on_start], [app, :call], [se, :on_commit], ], events end def test_send_is_called_on_each events = [] ret = [200, {}, []] app = lambda { |env| events << [app, :call]; ret } se = EventMiddleware.new events e = Events.new app, [se] triple = e.call({}) triple[2].each { |x| } assert_equal [[se, :on_start], [app, :call], [se, :on_commit], [se, :on_send], ], events end def test_finish_is_called_on_close events = [] ret = [200, {}, []] app = lambda { |env| events << [app, :call]; ret } se = EventMiddleware.new events e = Events.new app, [se] triple = e.call({}) triple[2].each { |x| } triple[2].close assert_equal [[se, :on_start], [app, :call], [se, :on_commit], [se, :on_send], [se, :on_finish], ], events end def test_finish_is_called_in_reverse_order events = [] ret = [200, {}, []] app = lambda { |env| events << [app, :call]; ret } se1 = EventMiddleware.new events se2 = EventMiddleware.new events se3 = EventMiddleware.new events e = Events.new app, [se1, se2, se3] triple = e.call({}) triple[2].each { |x| } triple[2].close groups = events.group_by { |x| x.last } assert_equal groups[:on_start].map(&:first), groups[:on_finish].map(&:first).reverse assert_equal groups[:on_commit].map(&:first), groups[:on_finish].map(&:first) assert_equal groups[:on_send].map(&:first), groups[:on_finish].map(&:first) end def test_finish_is_called_if_there_is_an_exception events = [] app = lambda { |env| raise } se = EventMiddleware.new events e = Events.new app, [se] assert_raises(RuntimeError) do e.call({}) end assert_equal [[se, :on_start], [se, :on_error], [se, :on_finish], ], events end end end rack-3.1.12/test/spec_files.rb000066400000000000000000000225411476365375000161620ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/files' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::Files do DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT def files(*args) Rack::Lint.new Rack::Files.new(*args) end it "can be used without root" do # https://github.com/rack/rack/issues/1464 app = Rack::Files.new(nil) request = Rack::Request.new( Rack::MockRequest.env_for("/cgi/test") ) file_path = File.expand_path("cgi/test", __dir__) assert_equal 200, app.serving(request, file_path)[0] end it 'serves files with + in the file name' do Dir.mktmpdir do |dir| File.write File.join(dir, "you+me.txt"), "hello world" app = files(dir) env = Rack::MockRequest.env_for("/you+me.txt") status, _, body = app.call env assert_equal 200, status str = ''.dup body.each { |x| str << x } assert_match "hello world", str end end it "serve files" do res = Rack::MockRequest.new(files(DOCROOT)).get("/cgi/test") res.must_be :ok? assert_match(res, /ruby/) end it "does not serve directories" do res = Rack::MockRequest.new(files(DOCROOT)).get("/cgi/assets") res.status.must_equal 404 end it "set last-modified header" do res = Rack::MockRequest.new(files(DOCROOT)).get("/cgi/test") path = File.join(DOCROOT, "/cgi/test") res.must_be :ok? res["last-modified"].must_equal File.mtime(path).httpdate end it "return 304 if file isn't modified since last serve" do path = File.join(DOCROOT, "/cgi/test") res = Rack::MockRequest.new(files(DOCROOT)). get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => File.mtime(path).httpdate) res.status.must_equal 304 res.body.must_be :empty? end it "return the file if it's modified since last serve" do path = File.join(DOCROOT, "/cgi/test") res = Rack::MockRequest.new(files(DOCROOT)). get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => (File.mtime(path) - 100).httpdate) res.must_be :ok? end it "serve files with URL encoded filenames" do res = Rack::MockRequest.new(files(DOCROOT)).get("/cgi/%74%65%73%74") # "/cgi/test" res.must_be :ok? # res.must_match(/ruby/) # nope # (/ruby/).must_match res # This is weird, but an oddity of minitest # assert_match(/ruby/, res) # nope assert_match(res, /ruby/) end it "serve uri with URL encoded null byte (%00) in filenames" do res = Rack::MockRequest.new(files(DOCROOT)).get("/cgi/test%00") res.must_be :bad_request? end it "allow safe directory traversal" do req = Rack::MockRequest.new(files(DOCROOT)) res = req.get('/cgi/../cgi/test') res.must_be :successful? res = req.get('.') res.must_be :not_found? res = req.get("test/..") res.must_be :not_found? end it "not allow unsafe directory traversal" do req = Rack::MockRequest.new(files(DOCROOT)) res = req.get("/../README.rdoc") res.must_be :client_error? res = req.get("../test/spec_file.rb") res.must_be :client_error? res = req.get("../README.rdoc") res.must_be :client_error? res.must_be :not_found? end it "allow files with .. in their name" do req = Rack::MockRequest.new(files(DOCROOT)) res = req.get("/cgi/..test") res.must_be :not_found? res = req.get("/cgi/test..") res.must_be :not_found? res = req.get("/cgi../test..") res.must_be :not_found? end it "not allow unsafe directory traversal with encoded periods" do res = Rack::MockRequest.new(files(DOCROOT)).get("/%2E%2E/README") res.must_be :client_error? res.must_be :not_found? end it "allow safe directory traversal with encoded periods" do res = Rack::MockRequest.new(files(DOCROOT)).get("/cgi/%2E%2E/cgi/test") res.must_be :successful? end it "404 if it can't find the file" do res = Rack::MockRequest.new(files(DOCROOT)).get("/cgi/blubb") res.must_be :not_found? end it "detect SystemCallErrors" do res = Rack::MockRequest.new(files(DOCROOT)).get("/cgi") res.must_be :not_found? end it "return bodies that respond to #to_path" do env = Rack::MockRequest.env_for("/cgi/test") status, _, body = Rack::Files.new(DOCROOT).call(env) path = File.join(DOCROOT, "/cgi/test") status.must_equal 200 body.must_respond_to :to_path body.to_path.must_equal path end it "return bodies that do not respond to #to_path if a byte range is requested" do env = Rack::MockRequest.env_for("/cgi/test") env["HTTP_RANGE"] = "bytes=22-33" status, _, body = Rack::Files.new(DOCROOT).call(env) status.must_equal 206 body.wont_respond_to :to_path end it "return correct byte range in body" do env = Rack::MockRequest.env_for("/cgi/test") env["HTTP_RANGE"] = "bytes=22-33" res = Rack::MockResponse.new(*files(DOCROOT).call(env)) res.status.must_equal 206 res["content-length"].must_equal "12" res["content-range"].must_equal "bytes 22-33/209" res.body.must_equal "IS FILE! ***" end it "handle case where file is truncated during request" do env = Rack::MockRequest.env_for("/cgi/test") env["HTTP_RANGE"] = "bytes=0-3300" files = Class.new(Rack::Files) do def filesize(_); 10000 end end.new(DOCROOT) res = Rack::MockResponse.new(*files.call(env)) res.status.must_equal 206 res.length.must_equal 209 res.finish res["content-length"].must_equal "209" res["content-range"].must_equal "bytes 0-3300/10000" end it "return correct multiple byte ranges in body" do env = Rack::MockRequest.env_for("/cgi/test") env["HTTP_RANGE"] = "bytes=22-33, 60-80" res = Rack::MockResponse.new(*files(DOCROOT).call(env)) res.status.must_equal 206 res["content-length"].must_equal "191" res["content-type"].must_equal "multipart/byteranges; boundary=AaB03x" expected_body = <<-EOF \r --AaB03x\r content-type: text/plain\r content-range: bytes 22-33/209\r \r IS FILE! ***\r --AaB03x\r content-type: text/plain\r content-range: bytes 60-80/209\r \r , tests will break!!!\r --AaB03x--\r EOF res.body.must_equal expected_body end it "return error for unsatisfiable byte range" do env = Rack::MockRequest.env_for("/cgi/test") env["HTTP_RANGE"] = "bytes=1234-5678" res = Rack::MockResponse.new(*files(DOCROOT).call(env)) res.status.must_equal 416 res["content-range"].must_equal "bytes */209" end it "ignores range when file is 0 bytes" do env = Rack::MockRequest.env_for("/cgi/assets/images/favicon.ico") env["HTTP_RANGE"] = "bytes=0-1234" res = Rack::MockResponse.new(*files(DOCROOT).call(env)) res.status.must_equal 200 res["content-range"].must_be_nil end it "support custom http headers" do env = Rack::MockRequest.env_for("/cgi/test") status, heads, _ = files(DOCROOT, 'cache-control' => 'public, max-age=38', 'access-control-allow-origin' => '*').call(env) status.must_equal 200 heads['cache-control'].must_equal 'public, max-age=38' heads['access-control-allow-origin'].must_equal '*' end it "support not add custom http headers if none are supplied" do env = Rack::MockRequest.env_for("/cgi/test") status, heads, _ = files(DOCROOT).call(env) status.must_equal 200 heads['cache-control'].must_be_nil heads['access-control-allow-origin'].must_be_nil end it "only support GET, HEAD, and OPTIONS requests" do req = Rack::MockRequest.new(files(DOCROOT)) forbidden = %w[post put patch delete] forbidden.each do |method| res = req.send(method, "/cgi/test") res.must_be :client_error? res.must_be :method_not_allowed? res.headers['allow'].split(/, */).sort.must_equal %w(GET HEAD OPTIONS) end allowed = %w[get head options] allowed.each do |method| res = req.send(method, "/cgi/test") res.must_be :successful? end end it "set Allow correctly for OPTIONS requests" do req = Rack::MockRequest.new(files(DOCROOT)) res = req.options('/cgi/test') res.must_be :successful? res.headers['allow'].wont_equal nil res.headers['allow'].split(/, */).sort.must_equal %w(GET HEAD OPTIONS) end it "set content-length correctly for HEAD requests" do req = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT))) res = req.head "/cgi/test" res.must_be :successful? res['content-length'].must_equal "209" end it "default to a mime type of text/plain" do req = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT))) res = req.get "/cgi/test" res.must_be :successful? res['content-type'].must_equal "text/plain" end it "allow the default mime type to be set" do req = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT, nil, 'application/octet-stream'))) res = req.get "/cgi/test" res.must_be :successful? res['content-type'].must_equal "application/octet-stream" end it "not set content-type if the mime type is not set" do req = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT, nil, nil))) res = req.get "/cgi/test" res.must_be :successful? res['content-type'].must_be_nil end it "return error when file not found for head request" do res = Rack::MockRequest.new(files(DOCROOT)).head("/cgi/missing") res.must_be :not_found? res.body.must_be :empty? end end rack-3.1.12/test/spec_head.rb000066400000000000000000000027151476365375000157620ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/head' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::Head do def test_response(headers = {}) body = StringIO.new "foo" app = lambda do |env| [200, { "content-type" => "test/plain", "content-length" => "3" }, body] end request = Rack::MockRequest.env_for("/", headers) response = Rack::Lint.new(Rack::Head.new(app)).call(request) return response, body end it "pass GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do %w[GET POST PUT DELETE OPTIONS TRACE].each do |type| resp, _ = test_response("REQUEST_METHOD" => type) resp[0].must_equal 200 resp[1].must_equal "content-type" => "test/plain", "content-length" => "3" resp[2].to_enum.to_a.must_equal ["foo"] end end it "remove body from HEAD requests" do resp, _ = test_response("REQUEST_METHOD" => "HEAD") resp[0].must_equal 200 resp[1].must_equal "content-type" => "test/plain", "content-length" => "3" resp[2].to_enum.to_a.must_equal [] end it "close the body when it is removed" do resp, body = test_response("REQUEST_METHOD" => "HEAD") resp[0].must_equal 200 resp[1].must_equal "content-type" => "test/plain", "content-length" => "3" resp[2].to_enum.to_a.must_equal [] body.wont_be :closed? resp[2].close body.must_be :closed? end end rack-3.1.12/test/spec_headers.rb000066400000000000000000000353061476365375000164760ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/headers' end class RackHeadersTest < Minitest::Spec before do @h = Rack::Headers.new @fh = Rack::Headers['AB'=>'1', 'cd'=>'2', '3'=>'4'] end def test_public_interface headers_methods = Rack::Headers.public_instance_methods.sort hash_methods = Hash.public_instance_methods.sort assert_empty(headers_methods - hash_methods) assert_empty(hash_methods - headers_methods) end def test_class_aref assert_equal Hash[], Rack::Headers[] assert_equal Hash['a'=>'2'], Rack::Headers['A'=>'2'] assert_equal Hash['a'=>'2', 'b'=>'4'], Rack::Headers['A'=>'2', 'B'=>'4'] assert_equal Hash['a','2','b','4'], Rack::Headers['A','2','B','4'] assert_raises(ArgumentError){Rack::Headers['A']} assert_raises(ArgumentError){Rack::Headers['A',2,'B']} end def test_default_values h, ch = Hash.new, Rack::Headers.new assert_equal h, ch h, ch = Hash.new('1'), Rack::Headers.new('1') assert_equal h, ch assert_equal h['3'], ch['3'] h['a'], ch['A'] = ['2', '2'] assert_equal h['a'], ch['a'] h, ch = Hash.new{|h,k| k*2}, Rack::Headers.new{|h,k| k*2} assert_equal h['3'], ch['3'] h['c'], ch['C'] = ['2', '2'] assert_equal h['c'], ch['c'] assert_raises(ArgumentError){Rack::Headers.new('1'){|hash,k| key}} assert_nil @fh.default assert_nil @fh.default_proc assert_nil @fh['55'] assert_equal '3', Rack::Headers.new('3').default assert_nil Rack::Headers.new('3').default_proc assert_equal '3', Rack::Headers.new('3')['1'] @fh.default = '4' assert_equal '4', @fh.default assert_nil @fh.default_proc assert_equal '4', @fh['55'] h = Rack::Headers.new('5') assert_equal '5', h.default assert_nil h.default_proc assert_equal '5', h['55'] h = Rack::Headers.new{|hash, key| '1234'} assert_nil h.default refute_equal nil, h.default_proc assert_equal '1234', h['55'] h = Rack::Headers.new{|hash, key| hash[key] = '1234'; nil} assert_nil h.default refute_equal nil, h.default_proc assert_nil h['Ac'] assert_equal '1234', h['aC'] end def test_store_and_retrieve assert_nil @h['a'] @h['A'] = '2' assert_equal '2', @h['a'] assert_equal '2', @h['A'] @h['a'] = '3' assert_equal '3', @h['a'] assert_equal '3', @h['A'] @h['AB'] = '5' assert_equal '5', @h['ab'] assert_equal '5', @h['AB'] assert_equal '5', @h['aB'] assert_equal '5', @h['Ab'] @h.store('C', '8') assert_equal '8', @h['c'] assert_equal '8', @h['C'] end def test_clear assert_equal 3, @fh.length @fh.clear assert_equal Hash[], @fh assert_equal 0, @fh.length end def test_delete assert_equal 3, @fh.length assert_equal '1', @fh.delete('aB') assert_equal 2, @fh.length assert_nil @fh.delete('Ab') assert_equal 2, @fh.length end def test_delete_if_and_reject assert_equal 3, @fh.length hash = @fh.reject{|key, value| key == 'ab' || key == 'cd'} assert_equal 1, hash.length assert_equal Hash['3'=>'4'], hash assert_equal 3, @fh.length hash = @fh.delete_if{|key, value| key == 'ab' || key == 'cd'} assert_equal 1, hash.length assert_equal Hash['3'=>'4'], hash assert_equal 1, @fh.length assert_equal Hash['3'=>'4'], @fh assert_nil @fh.reject!{|key, value| key == 'ab' || key == 'cd'} hash = @fh.reject!{|key, value| key == '3'} assert_equal 0, hash.length assert_equal Hash[], hash assert_equal 0, @fh.length assert_equal Hash[], @fh end def test_dup_and_clone def @h.foo; 1; end h2 = @h.dup h3 = @h.clone h2['A'] = '2' h3['B'] = '3' assert_equal Rack::Headers[], @h assert_raises NoMethodError do h2.foo end assert_equal 1, h3.foo assert_equal '2', h2['a'] assert_equal '3', h3['b'] end def test_each i = 0 @h.each{i+=1} assert_equal 0, i items = [['ab','1'], ['cd','2'], ['3','4']] @fh.each do |k,v| assert items.include?([k,v]) items -= [[k,v]] end assert_equal [], items end def test_each_key i = 0 @h.each{i+=1} assert_equal 0, i keys = ['ab', 'cd', '3'] @fh.each_key do |k| assert keys.include?(k) assert k.frozen? keys -= [k] end assert_equal [], keys end def test_each_value i = 0 @h.each{i+=1} assert_equal 0, i values = ['1', '2', '4'] @fh.each_value do |v| assert values.include?(v) values -= [v] end assert_equal [], values end def test_empty assert @h.empty? assert !@fh.empty? end def test_fetch assert_raises(ArgumentError){@h.fetch(1,2,3)} assert_raises(ArgumentError){@h.fetch(1,2,3){4}} assert_raises(IndexError){@h.fetch(1)} @h.default = '33' assert_raises(IndexError){@h.fetch(1)} @h['1'] = '8' assert_equal '8', @h.fetch('1') assert_equal '3', @h.fetch(2, '3') assert_equal '222', @h.fetch('2'){|k| k*3} assert_equal '1', @fh.fetch('Ab') assert_equal '2', @fh.fetch('cD', '3') assert_equal '4', @fh.fetch("3", 3) assert_equal '4', @fh.fetch("3"){|k| k*3} assert_raises(IndexError){Rack::Headers.new{34}.fetch(1)} end def test_has_key %i'include? has_key? key? member?'.each do |meth| assert !@h.send(meth,1) assert @fh.send(meth,'Ab') assert @fh.send(meth,'cD') assert @fh.send(meth,'3') assert @fh.send(meth,'ab') assert @fh.send(meth,'CD') assert @fh.send(meth,'3') assert !@fh.send(meth,1) end end def test_has_value %i'value? has_value?'.each do |meth| assert !@h.send(meth,'1') assert @fh.send(meth,'1') assert @fh.send(meth,'2') assert @fh.send(meth,'4') assert !@fh.send(meth,'3') end end def test_inspect %i'inspect to_s'.each do |meth| assert_equal '{}', @h.send(meth) assert_equal '{"ab"=>"1", "cd"=>"2", "3"=>"4"}', @fh.send(meth) end end def test_invert assert_kind_of(Rack::Headers, @h.invert) assert_equal({}, @h.invert) assert_equal({"1"=>"ab", "2"=>"cd", "4"=>"3"}, @fh.invert) assert_equal({'cd'=>'ab'}, Rack::Headers['AB'=>'CD'].invert) assert_equal({'cd'=>'xy'}, Rack::Headers['AB'=>'Cd', 'xY'=>'cD'].invert) end def test_keys assert_equal [], @h.keys assert_equal %w'ab cd 3', @fh.keys end def test_length %i'length size'.each do |meth| assert_equal 0, @h.send(meth) assert_equal 3, @fh.send(meth) end end def test_merge_and_update assert_equal @h, @h.merge({}) assert_equal @fh, @fh.merge({}) assert_equal Rack::Headers['ab'=>'55'], @h.merge({'ab'=>'55'}) assert_equal Rack::Headers[], @h assert_equal Rack::Headers['ab'=>'55'], @h.update({'ab'=>'55'}) assert_equal Rack::Headers['ab'=>'55'], @h assert_equal Rack::Headers['ab'=>'55', 'cd'=>'2', '3'=>'4'], @fh.merge({'ab'=>'55'}) assert_equal Rack::Headers['ab'=>'1', 'cd'=>'2', '3'=>'4'], @fh assert_equal Rack::Headers['ab'=>'55', 'cd'=>'2', '3'=>'4'], @fh.merge!({'ab'=>'55'}) assert_equal Rack::Headers['ab'=>'55', 'cd'=>'2', '3'=>'4'], @fh assert_equal Rack::Headers['ab'=>'abss55', 'cd'=>'2', '3'=>'4'], @fh.merge({'ab'=>'ss'}){|k,ov,nv| [k,nv,ov].join} assert_equal Rack::Headers['ab'=>'55', 'cd'=>'2', '3'=>'4'], @fh assert_equal Rack::Headers['ab'=>'abss55', 'cd'=>'2', '3'=>'4'], @fh.update({'ab'=>'ss'}){|k,ov,nv| [k,nv,ov].join} assert_equal Rack::Headers['ab'=>'abss55', 'cd'=>'2', '3'=>'4'], @fh assert_equal Rack::Headers['ab'=>'abssabss55', 'cd'=>'2', '3'=>'4'], @fh.merge!({'ab'=>'ss'}){|k,ov,nv| [k,nv,ov].join} assert_equal Rack::Headers['ab'=>'abssabss55', 'cd'=>'2', '3'=>'4'], @fh end def test_replace h = @h.dup fh = @fh.dup h1 = fh.replace(@h) assert_equal @h, h1 assert_same fh, h1 h2 = h.replace(@fh) assert_equal @fh, h2 assert_same h, h2 assert_equal @h, fh.replace({}) assert_equal @fh, h.replace('AB'=>'1', 'cd'=>'2', '3'=>'4') end def test_select assert_equal({}, @h.select{true}) assert_equal({}, @h.select{false}) assert_equal({'3' => '4', "ab" => '1', 'cd' => '2'}, @fh.select{true}) assert_equal({}, @fh.select{false}) assert_equal({'cd' => '2'}, @fh.select{|k,v| k.start_with?('c')}) assert_equal({'3' => '4'}, @fh.select{|k,v| v == '4'}) end def test_shift assert_nil @h.shift array = @fh.to_a i = 3 while true assert i >= 0 kv = @fh.shift if kv.nil? assert_equal [], array break else i -= 1 assert array.include?(kv) array -= [kv] end end assert_equal [], array assert_equal 0, i end def test_sort assert_equal [], @h.sort assert_equal [], @h.sort{|a,b| a.to_s<=>b.to_s} assert_equal [['ab', '1'], ['cd', '4'], ['ef', '2']], Rack::Headers['CD','4','AB','1','EF','2'].sort assert_equal [['3', '4'], ['ab', '1'], ['cd', '2']], @fh.sort{|(ak,av),(bk,bv)| ak.to_s<=>bk.to_s} end def test_to_a assert_equal [], @h.to_a assert_equal [['ab', '1'], ['cd', '2'], ['3', '4']], @fh.to_a end def test_to_hash assert_equal Hash[], @h.to_hash assert_equal Hash['3','4','ab','1','cd','2'], @fh.to_hash end def test_values assert_equal [], @h.values assert_equal ['f', 'c'], Rack::Headers['aB','f','1','c'].values end def test_values_at assert_equal [], @h.values_at() assert_equal [nil], @h.values_at(1) assert_equal [nil, nil], @h.values_at(1, 1) assert_equal [], @fh.values_at() assert_equal ['1'], @fh.values_at('AB') assert_equal ['2', '1'], @fh.values_at('CD', 'Ab') assert_equal ['2', nil, '1'], @fh.values_at('CD', 32, 'aB') assert_equal ['4', '2', nil, '1'], @fh.values_at('3', 'CD', 32, 'ab') end def test_assoc assert_nil @h.assoc(1) assert_equal ['ab', '1'], @fh.assoc('Ab') assert_equal ['cd', '2'], @fh.assoc('CD') assert_nil @fh.assoc('4') assert_equal ['3', '4'], @fh.assoc('3') end def test_default_proc= @h.default_proc = proc{|h, k| k * 2} assert_equal 'aa', @h['A'] @h['Ab'] = '2' assert_equal '2', @h['aB'] end def test_flatten assert_equal [], @h.flatten assert_equal ['ab', '1', 'cd', '2', '3', '4'], @fh.flatten @fh['X'] = '56' assert_equal ['ab', '1', 'cd', '2', '3', '4', 'x', '56'], @fh.flatten assert_equal ['ab', '1', 'cd', '2', '3', '4', 'x', '56'], @fh.flatten(2) end def test_keep_if assert_equal @h, @h.keep_if{|k, v| true} assert_equal @fh, @fh.keep_if{|k, v| true} assert_equal @h, @fh.dup.keep_if{|k, v| false} assert_equal Rack::Headers["AB"=>'1'], @fh.keep_if{|k, v| k == "ab"} end def test_key assert_nil @h.key('1') assert_nil @fh.key(1) assert_equal 'ab', @fh.key('1') assert_equal 'cd', @fh.key('2') assert_nil @fh.key('3') assert_equal '3', @fh.key('4') end def test_rassoc assert_nil @h.rassoc('1') assert_equal ['ab', '1'], @fh.rassoc('1') assert_equal ['cd', '2'], @fh.rassoc('2') assert_nil @fh.rassoc('3') assert_equal ['3', '4'], @fh.rassoc('4') end def test_select! assert_nil @h.select!{|k, v| true} assert_nil @fh.select!{|k, v| true} assert_equal @h, @fh.dup.select!{|k, v| false} assert_equal Rack::Headers["AB"=>'1'], @fh.select!{|k, v| k == "ab"} end def test_compare_by_identity assert_raises(TypeError){@fh.compare_by_identity} end def test_compare_by_identity? assert_equal(false, @fh.compare_by_identity?) end def test_to_h assert_equal Hash[], @h.to_h assert_equal Hash['3','4','ab','1','cd','2'], @fh.to_h end def test_dig assert_equal('1', @fh.dig('AB')) assert_equal('2', @fh.dig('Cd')) assert_equal('4', @fh.dig('3')) assert_nil(@fh.dig('4')) assert_raises(TypeError){@fh.dig('AB', 1)} assert_raises(TypeError){@fh.dig('cd', 2)} assert_raises(TypeError){@fh.dig('3', 3)} assert_nil(@fh.dig('4', 5)) end def test_fetch_values assert_equal(['1'], @fh.fetch_values('AB')) assert_equal(['1', '2', '4'], @fh.fetch_values('AB', 'Cd', '3')) assert_raises(KeyError){@fh.fetch_values('AB', 'cD', '4')} end def test_to_proc pr = @fh.to_proc assert_equal('1', pr['AB']) assert_equal('2', pr['cD']) assert_equal('4', pr['3']) assert_nil(pr['4']) end def test_compact assert_equal(false, @fh.compact.equal?(@fh)) assert_equal(@fh, @fh.compact) assert_equal(Rack::Headers['Ab'=>1], Rack::Headers['aB'=>1, 'cd'=>nil].compact) end def test_compact! fh = @fh.dup assert_nil(@fh.compact!) assert_equal(fh, @fh) h = Rack::Headers['Ab'=>1, 'cd'=>nil] assert_equal(Rack::Headers['aB'=>1], h.compact!) assert_equal(Rack::Headers['AB'=>1], h) end def test_transform_values fh = @fh.transform_values{|v| v.to_s*2} assert_equal('1', @fh['aB']) assert_equal(Rack::Headers['AB'=>'11', 'cD'=>'22', '3'=>'44'], fh) assert_equal('11', fh['Ab']) end def test_transform_values! @fh.transform_values!{|v| v.to_s*2} assert_equal('11', @fh['AB']) assert_equal(Rack::Headers['Ab'=>'11', 'CD'=>'22', '3'=>'44'], @fh) assert_equal('11', @fh['aB']) end if RUBY_VERSION >= '2.5' def test_slice assert_equal(Rack::Headers['Ab'=>'1', 'cD'=>'2', '3'=>'4'], @fh.slice('aB', 'Cd', '3')) assert_equal(Rack::Headers['AB'=>'1', 'CD'=>'2'], @fh.slice('Ab', 'CD')) assert_equal(Rack::Headers[], @fh.slice('ad')) assert_equal('1', @fh.slice('AB', 'cd')['Ab']) end def test_transform_keys map = {'ab'=>'Xy', 'cd'=>'dC', '3'=>'5'} dh = @fh.dup fh = @fh.transform_keys{|k| map[k]} assert_equal(dh, @fh) assert_equal('1', fh['xY']) assert_equal('2', fh['Dc']) assert_equal('4', fh['5']) end def test_transform_keys! map = {'ab'=>'Xy', 'cd'=>'dC', '3'=>'5'} dh = @fh.dup @fh.transform_keys!{|k| map[k]} assert_equal(false, dh == @fh) assert_equal('1', @fh['xY']) assert_equal('2', @fh['DC']) assert_equal('4', @fh['5']) end end if RUBY_VERSION >= '2.6' def test_filter! assert_nil @h.filter!{|k, v| true} assert_nil @fh.filter!{|k, v| true} assert_equal @h, @fh.dup.filter!{|k, v| false} assert_equal Rack::Headers["AB"=>'1'], @fh.filter!{|k, v| k == "ab"} end end if RUBY_VERSION >= '2.7' def test_deconstruct_keys assert_equal(@fh.to_hash, @fh.deconstruct_keys([])) assert_equal(Rack::Headers, @fh.deconstruct_keys([]).class) end end if RUBY_VERSION >= '3.0' def test_except @fh = Rack::Headers['AB'=>'1', 'Cd'=>'2', '3'=>'4'] assert_equal(@fh, @fh.except) assert_equal(Rack::Headers['cD'=>'2', '3'=>'4'], @fh.except('AB', 5)) assert_equal(Rack::Headers['AB'=>'1'], @fh.except('cD', '3')) end end end rack-3.1.12/test/spec_lint.rb000066400000000000000000001054111476365375000160240ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'tempfile' separate_testing do require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::Lint do valid_app = lambda do |env| [200, { "content-type" => "test/plain", "content-length" => "3" }, ["foo"]] end def env(options = {}) unless options.key?(:input) options[:input] = String.new end Rack::MockRequest.env_for("/", options) end it "pass valid request" do Rack::Lint.new(valid_app).call(env({})).first.must_equal 200 end it "notice fatal errors" do lambda { Rack::Lint.new(nil).call }.must_raise(Rack::Lint::LintError). message.must_match(/No env given/) end it "notice environment errors" do lambda { Rack::Lint.new(nil).call 5 }.must_raise(Rack::Lint::LintError). message.must_match(/not a Hash/) lambda { Rack::Lint.new(nil).call({}.freeze) }.must_raise(Rack::Lint::LintError). message.must_match(/env should not be frozen, but is/) lambda { e = env e.delete("REQUEST_METHOD") Rack::Lint.new(nil).call(e) }.must_raise(Rack::Lint::LintError). message.must_match(/missing required key REQUEST_METHOD/) lambda { e = env e.delete("SERVER_NAME") Rack::Lint.new(nil).call(e) }.must_raise(Rack::Lint::LintError). message.must_match(/missing required key SERVER_NAME/) lambda { e = env e.delete("SERVER_PROTOCOL") Rack::Lint.new(nil).call(e) }.must_raise(Rack::Lint::LintError). message.must_match(/missing required key SERVER_PROTOCOL/) lambda { e = env e["SERVER_PROTOCOL"] = 'Foo' Rack::Lint.new(nil).call(e) }.must_raise(Rack::Lint::LintError). message.must_match(/env\[SERVER_PROTOCOL\] does not match HTTP/) lambda { Rack::Lint.new(nil).call(env("HTTP_CONTENT_TYPE" => "text/plain")) }.must_raise(Rack::Lint::LintError). message.must_match(/contains HTTP_CONTENT_TYPE/) lambda { Rack::Lint.new(nil).call(env("HTTP_CONTENT_LENGTH" => "42")) }.must_raise(Rack::Lint::LintError). message.must_match(/contains HTTP_CONTENT_LENGTH/) lambda { Rack::Lint.new(nil).call(env("FOO" => Object.new)) }.must_raise(Rack::Lint::LintError). message.must_match(/non-string value/) lambda { Rack::Lint.new(nil).call(env("rack.url_scheme" => "gopher")) }.must_raise(Rack::Lint::LintError). message.must_match(/url_scheme unknown/) lambda { Rack::Lint.new(nil).call(env("rack.session" => [])) }.must_raise(Rack::Lint::LintError). message.must_equal "session [] must respond to store and []=" Rack::Lint.new(valid_app).call(env("rack.session" => {}))[0].must_equal 200 lambda { Rack::Lint.new(nil).call(env("rack.session" => {}.freeze)) }.must_raise(Rack::Lint::LintError). message.must_equal "session {} must respond to to_hash and return unfrozen Hash instance" obj = {} obj.singleton_class.send(:undef_method, :to_hash) lambda { Rack::Lint.new(nil).call(env("rack.session" => obj)) }.must_raise(Rack::Lint::LintError). message.must_equal "session {} must respond to to_hash and return unfrozen Hash instance" obj.singleton_class.send(:undef_method, :clear) lambda { Rack::Lint.new(nil).call(env("rack.session" => obj)) }.must_raise(Rack::Lint::LintError). message.must_equal "session {} must respond to clear" obj.singleton_class.send(:undef_method, :delete) lambda { Rack::Lint.new(nil).call(env("rack.session" => obj)) }.must_raise(Rack::Lint::LintError). message.must_equal "session {} must respond to delete" obj.singleton_class.send(:undef_method, :fetch) lambda { Rack::Lint.new(nil).call(env("rack.session" => obj)) }.must_raise(Rack::Lint::LintError). message.must_equal "session {} must respond to fetch and []" obj = Object.new def obj.inspect; '[]' end lambda { Rack::Lint.new(nil).call(env("rack.logger" => obj)) }.must_raise(Rack::Lint::LintError). message.must_equal "logger [] must respond to info" def obj.info(*) end lambda { Rack::Lint.new(nil).call(env("rack.logger" => obj)) }.must_raise(Rack::Lint::LintError). message.must_equal "logger [] must respond to debug" def obj.debug(*) end lambda { Rack::Lint.new(nil).call(env("rack.logger" => obj)) }.must_raise(Rack::Lint::LintError). message.must_equal "logger [] must respond to warn" def obj.warn(*) end lambda { Rack::Lint.new(nil).call(env("rack.logger" => obj)) }.must_raise(Rack::Lint::LintError). message.must_equal "logger [] must respond to error" def obj.error(*) end lambda { Rack::Lint.new(nil).call(env("rack.logger" => obj)) }.must_raise(Rack::Lint::LintError). message.must_equal "logger [] must respond to fatal" def obj.fatal(*) end Rack::Lint.new(valid_app).call(env("rack.logger" => obj))[0].must_equal 200 lambda { Rack::Lint.new(nil).call(env("rack.multipart.buffer_size" => 0)) }.must_raise(Rack::Lint::LintError). message.must_equal "rack.multipart.buffer_size must be an Integer > 0 if specified" Rack::Lint.new(valid_app).call(env("rack.multipart.buffer_size" => 1))[0].must_equal 200 lambda { Rack::Lint.new(nil).call(env("rack.multipart.tempfile_factory" => Tempfile)) }.must_raise(Rack::Lint::LintError). message.must_equal "rack.multipart.tempfile_factory must respond to #call" lambda { Rack::Lint.new(lambda { |env| env['rack.multipart.tempfile_factory'].call("testfile", "text/plain") }).call(env("rack.multipart.tempfile_factory" => lambda { |filename, content_type| Object.new })) }.must_raise(Rack::Lint::LintError). message.must_equal "rack.multipart.tempfile_factory return value must respond to #<<" lambda { Rack::Lint.new(lambda { |env| env['rack.multipart.tempfile_factory'].call("testfile", "text/plain") [] }).call(env("rack.multipart.tempfile_factory" => lambda { |filename, content_type| String.new })) }.must_raise(Rack::Lint::LintError). message.must_equal "response array has 0 elements instead of 3" lambda { Rack::Lint.new(nil).call(env("SERVER_PORT" => "howdy")) }.must_raise(Rack::Lint::LintError). message.must_equal 'env[SERVER_PORT] is not an Integer' lambda { Rack::Lint.new(nil).call(env("SERVER_NAME" => "\u1234")) }.must_raise(Rack::Lint::LintError). message.must_equal "\u1234 must be a valid authority" lambda { Rack::Lint.new(nil).call(env("HTTP_HOST" => "\u1234")) }.must_raise(Rack::Lint::LintError). message.must_equal "\u1234 must be a valid authority" lambda { Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?")) }.must_raise(Rack::Lint::LintError). message.must_match(/REQUEST_METHOD/) lambda { Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "OOPS?\b!")) }.must_raise(Rack::Lint::LintError). message.must_match(/OOPS\?\\/) lambda { Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy")) }.must_raise(Rack::Lint::LintError). message.must_match(/must start with/) lambda { Rack::Lint.new(nil).call(env("CONTENT_LENGTH" => "xcii")) }.must_raise(Rack::Lint::LintError). message.must_match(/Invalid CONTENT_LENGTH/) lambda { Rack::Lint.new(nil).call(env("QUERY_STRING" => nil)) }.must_raise(Rack::Lint::LintError). message.must_include('env variable QUERY_STRING has non-string value nil') lambda { Rack::Lint.new(nil).call(env("QUERY_STRING" => "\u1234")) }.must_raise(Rack::Lint::LintError). message.must_include('env variable QUERY_STRING has value containing non-ASCII characters and has non-ASCII-8BIT encoding') Rack::Lint.new(lambda { |env| [200, {}, []] }).call(env("QUERY_STRING" => "\u1234".b)).first.must_equal 200 lambda { e = env e.delete("PATH_INFO") e.delete("SCRIPT_NAME") Rack::Lint.new(nil).call(e) }.must_raise(Rack::Lint::LintError). message.must_match(/One of .* must be set/) lambda { Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/")) }.must_raise(Rack::Lint::LintError). message.must_match(/cannot be .* make it ''/) lambda { Rack::Lint.new(nil).call(env("rack.response_finished" => "not a callable")) }.must_raise(Rack::Lint::LintError). message.must_match(/rack.response_finished must be an array of callable objects/) lambda { Rack::Lint.new(nil).call(env("rack.response_finished" => [-> (env) {}, "not a callable"])) }.must_raise(Rack::Lint::LintError). message.must_match(/rack.response_finished values must respond to call/) end it "notice input errors" do lambda { Rack::Lint.new(nil).call(env("rack.input" => "")) }.must_raise(Rack::Lint::LintError). message.must_match(/does not respond to #gets/) lambda { input = Object.new def input.binmode? false end Rack::Lint.new(nil).call(env("rack.input" => input)) }.must_raise(Rack::Lint::LintError). message.must_match(/is not opened in binary mode/) lambda { input = Object.new def input.external_encoding result = Object.new def result.name "US-ASCII" end result end Rack::Lint.new(nil).call(env("rack.input" => input)) }.must_raise(Rack::Lint::LintError). message.must_match(/does not have ASCII-8BIT as its external encoding/) end it "notice error errors" do lambda { io = StringIO.new io.binmode Rack::Lint.new(nil).call(env("rack.errors" => "", "rack.input" => io)) }.must_raise(Rack::Lint::LintError). message.must_match(/does not respond to #puts/) end it "notice response errors" do lambda { Rack::Lint.new(lambda { |env| "" }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_include('response is not an Array, but String') lambda { Rack::Lint.new(lambda { |env| [nil, nil, nil, nil] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_include('response array has 4 elements instead of 3') end it "accepts empty PATH_INFO" do Rack::Lint.new(valid_app).call(env("PATH_INFO" => "")).first.must_equal 200 end it "notices request-target asterisk form errors" do # A non-empty PATH_INFO starting with something other than / has # implications for Rack::Request#path and methods downstream from # it. Note that RFC3875 does not actually anticipate dealing with # `OPTIONS *`; that should be considered a bug in the spec. Rack::Lint.new(valid_app).call(env("REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => '*')). first.must_equal 200 lambda do Rack::Lint.new(nil).call(env("PATH_INFO" => "*")) end.must_raise(Rack::Lint::LintError). message.must_match(/Only OPTIONS requests may have PATH_INFO set to '\*'/) end it "notices request-target authority form errors" do Rack::Lint.new(valid_app).call(env("REQUEST_METHOD" => "CONNECT", "PATH_INFO" => "example.com:80")). first.must_equal 200 lambda do Rack::Lint.new(nil).call(env("PATH_INFO" => "example.com:80")) end.must_raise(Rack::Lint::LintError). message.must_match(/Only CONNECT requests may have PATH_INFO set to an authority/) lambda do Rack::Lint.new(nil).call(env("PATH_INFO" => "/:80")).first.must_equal 200 end end it "notices request-target absolute-form errors" do Rack::Lint.new(valid_app).call(env("REQUEST_METHOD" => "GET", "PATH_INFO" => "http://foo/bar")). first.must_equal 200 lambda do Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "CONNECT", "PATH_INFO" => "http://foo/bar")) end.must_raise(Rack::Lint::LintError). message.must_match(/CONNECT and OPTIONS requests must not have PATH_INFO set to a URI/) lambda do Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "http://foo/bar")) end.must_raise(Rack::Lint::LintError). message.must_match(/CONNECT and OPTIONS requests must not have PATH_INFO set to a URI/) end it "notices request-target origin-form errors" do Rack::Lint.new(valid_app).call(env("REQUEST_METHOD" => "GET", "PATH_INFO" => "/foo/bar")). first.must_equal 200 lambda do Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "GET", "PATH_INFO" => "../etc/passwd")) end.must_raise(Rack::Lint::LintError). message.must_match(/PATH_INFO must start with a '\/'/) lambda do Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "GET", "PATH_INFO" => "/foo/bar#qux")) end.must_raise(Rack::Lint::LintError). message.must_match(/PATH_INFO.*must not include a fragment/) lambda do Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "GET", "PATH_INFO" => "/foo/bar?baz#qux")) end.must_raise(Rack::Lint::LintError). message.must_match(/PATH_INFO.*must not include a fragment/) end it "notice status errors" do lambda { Rack::Lint.new(lambda { |env| ["cc", {}, ""] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/must be an Integer >=100/) lambda { Rack::Lint.new(lambda { |env| [42, {}, ""] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/must be an Integer >=100/) lambda { Rack::Lint.new(lambda { |env| ["200", {}, ""] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/must be an Integer >=100/) end it "notice header errors" do obj = Object.new def obj.each; end lambda { io = StringIO.new('a') io.binmode Rack::Lint.new(lambda { |env| env['rack.input'].each{ |x| } [200, obj, []] }).call(env({ "rack.input" => io })) }.must_raise(Rack::Lint::LintError). message.must_equal "headers object should be a hash, but isn't (got Object as headers)" lambda { Rack::Lint.new(lambda { |env| [200, {}.freeze, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_equal "headers object should not be frozen, but is" lambda { Rack::Lint.new(lambda { |env| [200, { true => false }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_equal "header key must be a string, was TrueClass" lambda { Rack::Lint.new(lambda { |env| [200, { "status" => "404" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/must not contain status/) # From RFC 7230: # Most HTTP header field values are defined using common syntax # components (token, quoted-string, and comment) separated by # whitespace or specific delimiting characters. Delimiters are chosen # from the set of US-ASCII visual characters not allowed in a token # (DQUOTE and "(),/:;<=>?@[\]{}"). Rack also doesn't allow uppercase # ASCII (A-Z) in header keys. # # token = 1*tchar # # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" # / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" # / DIGIT / ALPHA # ; any VCHAR, except delimiters invalid_headers = 0.upto(31).map(&:chr) + %W<( ) , / : ; < = > ? @ [ \\ ] { } \x7F> invalid_headers.each do |invalid_header| lambda { Rack::Lint.new(lambda { |env| [200, { invalid_header => "text/plain" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError, "on invalid header: #{invalid_header}"). message.must_equal("invalid header name: #{invalid_header}") end ('A'..'Z').each do |invalid_header| lambda { Rack::Lint.new(lambda { |env| [200, { invalid_header => "text/plain" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError, "on invalid header: #{invalid_header}"). message.must_equal("uppercase character in header name: #{invalid_header}") end valid_headers = 0.upto(127).map(&:chr) - invalid_headers - ('A'..'Z').to_a valid_headers.each do |valid_header| Rack::Lint.new(lambda { |env| [200, { valid_header => "text/plain" }, []] }).call(env({})).first.must_equal 200 end lambda { Rack::Lint.new(lambda { |env| [200, { "foo" => Object.new }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_equal "a header value must be a String or Array of Strings, but the value of 'foo' is a Object" lambda { Rack::Lint.new(lambda { |env| [200, { "foo-bar" => "text\000plain" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/invalid header/) lambda { Rack::Lint.new(lambda { |env| [200, [%w(content-type text/plain), %w(content-length 0)], []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_equal "headers object should be a hash, but isn't (got Array as headers)" end it "notice rack.early_hints errors" do def self.env(arg={}) super(arg.merge("rack.early_hints" => proc{})) end def self.app(value) app = Rack::Lint.new(lambda { |env| env['rack.early_hints'].call(value) [200, {}, []] }) lambda { app.call(env) } end app(Object.new).must_raise(Rack::Lint::LintError). message.must_equal "headers object should be a hash, but isn't (got Object as headers)" app({}.freeze).must_raise(Rack::Lint::LintError). message.must_equal "headers object should not be frozen, but is" app(true => false).must_raise(Rack::Lint::LintError). message.must_equal "header key must be a string, was TrueClass" app("status" => "404").must_raise(Rack::Lint::LintError). message.must_match(/must not contain status/) invalid_headers = 0.upto(31).map(&:chr) + %W<( ) , / : ; < = > ? @ [ \\ ] { } \x7F> invalid_headers.each do |invalid_header| app(invalid_header => "text/plain"). must_raise(Rack::Lint::LintError, "on invalid header: #{invalid_header}"). message.must_equal("invalid header name: #{invalid_header}") end ('A'..'Z').each do |invalid_header| app(invalid_header => "text/plain"). must_raise(Rack::Lint::LintError, "on invalid header: #{invalid_header}"). message.must_equal("uppercase character in header name: #{invalid_header}") end valid_headers = 0.upto(127).map(&:chr) - invalid_headers - ('A'..'Z').to_a valid_headers.each do |valid_header| app(valid_header => "text/plain").call.first.must_equal 200 end app("foo" => Object.new).must_raise(Rack::Lint::LintError). message.must_equal "a header value must be a String or Array of Strings, but the value of 'foo' is a Object" app("foo-bar" => "text\000plain").must_raise(Rack::Lint::LintError). message.must_match(/invalid header/) app([%w(content-type text/plain), %w(content-length 0)]).must_raise(Rack::Lint::LintError). message.must_equal "headers object should be a hash, but isn't (got Array as headers)" end it "notice content-type errors" do # lambda { # Rack::Lint.new(lambda { |env| # [200, {"content-length" => "0"}, []] # }).call(env({})) # }.must_raise(Rack::Lint::LintError). # message.must_match(/No content-type/) [100, 101, 204, 304].each do |status| lambda { Rack::Lint.new(lambda { |env| [status, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/content-type header found/) end end it "notice content-length errors" do [100, 101, 204, 304].each do |status| lambda { Rack::Lint.new(lambda { |env| [status, { "content-length" => "0" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/content-length header found/) end lambda { Rack::Lint.new(lambda { |env| [200, { "content-type" => "text/plain", "content-length" => "1" }, []] }).call(env({}))[2].each { } }.must_raise(Rack::Lint::LintError). message.must_match(/content-length header was 1, but should be 0/) end it "responds to to_path" do body = Object.new def body.each; end def body.to_path; __FILE__ end app = lambda { |env| [200, {}, body] } body = Rack::Lint.new(app).call(env({}))[2] body.must_respond_to(:to_path) body.to_path.must_equal __FILE__ end it "notice body errors" do lambda { body = Rack::Lint.new(lambda { |env| [200, { "content-type" => "text/plain", "content-length" => "3" }, [1, 2, 3]] }).call(env({}))[2] body.each { |part| } }.must_raise(Rack::Lint::LintError). message.must_match(/yielded non-string/) lambda { body = Rack::Lint.new(lambda { |env| [200, { "content-type" => "text/plain", "content-length" => "3" }, Object.new] }).call(env({}))[2] body.respond_to?(:to_ary).must_equal false body.each { |part| } }.must_raise(Rack::Lint::LintError). message.must_equal 'Enumerable Body must respond to each' lambda { body = Rack::Lint.new(lambda { |env| [200, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({}))[2] body.each { |part| } body.each { |part| } }.must_raise(Rack::Lint::LintError). message.must_equal 'Response body must only be invoked once (each)' # Lint before and after the Rack middleware being tested. def stacked_lint(app) Rack::Lint.new(lambda do |env| Rack::Lint.new(app).call(env).tap {|response| response[2] = yield response[2]} end) end yielder_app = lambda do |_| input = Object.new def input.each; 10.times {yield 'foo'}; end [200, {"content-type" => "text/plain", "content-length" => "30"}, input] end lambda { body = stacked_lint(yielder_app) {|body| new_body = Struct.new(:body) do def each(&block) body.each { |part| yield part.upcase } body.close end end new_body.new(body) }.call(env({}))[2] body.each {|part| part.must_equal 'FOO'} body.close }.call lambda { body = stacked_lint(yielder_app) { |body| body.enum_for.to_a }.call(env({}))[2] body.each {} body.close }.must_raise(Rack::Lint::LintError). message.must_match(/Middleware must not call #each directly/) lambda { body = stacked_lint(yielder_app) { |body| new_body = Struct.new(:body) do def each(&block) body.enum_for.each_slice(2) { |parts| yield parts.join } end end new_body.new(body) }.call(env({}))[2] body.each {} body.close }.must_raise(Rack::Lint::LintError). message.must_match(/New body must yield at least once per iteration of old body/) lambda { body = stacked_lint(yielder_app) { |body| Struct.new(:body) do def each; body.each {|part| yield part} end end.new(body) }.call(env({}))[2] body.each {} body.close }.must_raise(Rack::Lint::LintError). message.must_match(/Body has not been closed/) static_app = lambda do |_| input = ['foo'] * 10 [200, {"content-type" => "text/plain", "content-length" => "30"}, input] end lambda { body = stacked_lint(static_app) { |body| body.to_ary}.call(env({}))[2] body.each {} body.close }.call array_mismatch = lambda do |_| input = Object.new def input.to_ary; ['bar'] * 10; end def input.each; 10.times {yield 'foo'}; end [200, {"content-type" => "text/plain", "content-length" => "30"}, input] end lambda { body = stacked_lint(array_mismatch) { |body| body.to_ary}.call(env({}))[2] body.each {} body.close }.must_raise(Rack::Lint::LintError). message.must_match(/#to_ary not identical to contents produced by calling #each/) lambda { body = Rack::Lint.new(lambda { |env| to_path = Object.new def to_path.each; end def to_path.to_path; 'non-existent' end [200, { "content-type" => "text/plain", "content-length" => "0" }, to_path] }).call(env({}))[2] body.each { |part| } }.must_raise(Rack::Lint::LintError). message.must_equal 'The file identified by body.to_path does not exist' lambda { body = Rack::Lint.new(lambda { |env| [200, { "content-type" => "text/plain", "content-length" => "0" }, Object.new] }).call(env({}))[2] body.call(nil) }.must_raise(Rack::Lint::LintError). message.must_equal 'Streaming Body must respond to call' lambda { body = Rack::Lint.new(lambda { |env| [200, { "content-type" => "text/plain", "content-length" => "0" }, proc{}] }).call(env({}))[2] body.call(StringIO.new) body.call(nil) }.must_raise(Rack::Lint::LintError). message.must_equal 'Response body must only be invoked once (call)' lambda { body = Rack::Lint.new(lambda { |env| [200, { "content-type" => "text/plain", "content-length" => "0" }, proc{}] }).call(env({}))[2] body.call(nil) }.must_raise(Rack::Lint::LintError). message.must_equal 'Stream must respond to read' end it "notice input handling errors" do lambda { Rack::Lint.new(lambda { |env| env["rack.input"].gets("\r\n") [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/gets called with arguments/) lambda { Rack::Lint.new(lambda { |env| env["rack.input"].gets env["rack.input"].read(1, 2, 3) [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/read called with too many arguments/) lambda { Rack::Lint.new(lambda { |env| env["rack.input"].read("foo") [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/read called with non-integer and non-nil length/) lambda { Rack::Lint.new(lambda { |env| env["rack.input"].read(-1) [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/read called with a negative length/) lambda { Rack::Lint.new(lambda { |env| env["rack.input"].read(nil, nil) [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/read called with non-String buffer/) lambda { Rack::Lint.new(lambda { |env| env["rack.input"].read(nil, 1) [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/read called with non-String buffer/) weirdio = Object.new class << weirdio def gets 42 end def read 23 end def each yield 23 yield 42 end end eof_weirdio = Object.new class << eof_weirdio def gets nil end def read(*args) nil end def each end end lambda { Rack::Lint.new(lambda { |env| env["rack.input"].gets [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env("rack.input" => weirdio)) }.must_raise(Rack::Lint::LintError). message.must_match(/gets didn't return a String/) lambda { Rack::Lint.new(lambda { |env| env["rack.input"].each(1) { |x| } [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env("rack.input" => weirdio)) }.must_raise(Rack::Lint::LintError). message.must_match(/rack.input#each called with arguments/) lambda { Rack::Lint.new(lambda { |env| env["rack.input"].each { |x| } [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env("rack.input" => weirdio)) }.must_raise(Rack::Lint::LintError). message.must_match(/each didn't yield a String/) lambda { Rack::Lint.new(lambda { |env| env["rack.input"].read [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env("rack.input" => weirdio)) }.must_raise(Rack::Lint::LintError). message.must_match(/read didn't return nil or a String/) lambda { Rack::Lint.new(lambda { |env| env["rack.input"].read [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env("rack.input" => eof_weirdio)) }.must_raise(Rack::Lint::LintError). message.must_match(/read\(nil\) returned nil on EOF/) end it "can call close" do app = lambda do |env| env["rack.input"].close [201, {"content-type" => "text/plain", "content-length" => "0"}, []] end response = Rack::Lint.new(app).call(env({})) response.first.must_equal 201 end it "notice error handling errors" do lambda { Rack::Lint.new(lambda { |env| env["rack.errors"].write(42) [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/write not called with a String/) lambda { Rack::Lint.new(lambda { |env| env["rack.errors"].close [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({})) }.must_raise(Rack::Lint::LintError). message.must_match(/close must not be called/) end it "notice HEAD errors" do Rack::Lint.new(lambda { |env| [200, { "content-type" => "test/plain", "content-length" => "3" }, []] }).call(env({ "REQUEST_METHOD" => "HEAD" })).first.must_equal 200 lambda { Rack::Lint.new(lambda { |env| [200, { "content-type" => "test/plain", "content-length" => "3" }, ["foo"]] }).call(env({ "REQUEST_METHOD" => "HEAD" }))[2].each { } }.must_raise(Rack::Lint::LintError). message.must_match(/body was given for HEAD/) end def assert_lint(*args) hello_str = "hello world".dup hello_str.force_encoding(Encoding::ASCII_8BIT) Rack::Lint.new(lambda { |env| env["rack.input"].send(:read, *args) [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({ "rack.input" => StringIO.new(hello_str) })). first.must_equal 201 end it "pass valid read calls" do assert_lint assert_lint 0 assert_lint 1 assert_lint nil assert_lint nil, ''.dup assert_lint 1, ''.dup end it "notices when request env doesn't have a valid rack.hijack callback" do lambda { Rack::Lint.new(lambda { |env| env['rack.hijack'].call [201, { "content-type" => "text/plain", "content-length" => "0" }, []] }).call(env({ 'rack.hijack' => Object.new })) }.must_raise(Rack::Lint::LintError). message.must_match(/rack.hijack must respond to call/) end it "notices when the response headers don't have a valid rack.hijack callback" do lambda { Rack::Lint.new(lambda { |env| [201, { "content-type" => "text/plain", "content-length" => "0", 'rack.hijack' => Object.new }, []] }).call(env({ 'rack.hijack?' => true })) }.must_raise(Rack::Lint::LintError). message.must_equal 'rack.hijack header must respond to #call' end it "pass valid rack.response_finished" do callable_object = Class.new do def call(env, status, headers, error) end end.new Rack::Lint.new(lambda { |env| [200, {}, ["foo"]] }).call(env({ "rack.response_finished" => [-> (env) {}, lambda { |env| }, callable_object], "content-length" => "3" })).first.must_equal 200 end it "notices when the response protocol is specified in the response but not in the request" do app = Rack::Lint.new(lambda{|env| [101, {'rack.protocol' => 'websocket'}, ["foo"]] }) lambda do app.call(env()) end .must_raise(Rack::Lint::LintError) .message.must_match(/rack.protocol header is "websocket", but rack.protocol was not set in request/) end it "notices when the response protocol is specified in the response but not in the request" do app = Rack::Lint.new(lambda{|env| [101, {'rack.protocol' => 'websocket'}, ["foo"]] }) lambda do app.call(env('rack.protocol' => ['smtp'])) end .must_raise(Rack::Lint::LintError) .message.must_match(/rack.protocol header is "websocket", but should be one of \["smtp"\] from the request!/) end it "pass valid rack.protocol" do app = Rack::Lint.new(lambda{|env| [101, {'rack.protocol' => 'websocket'}, ["foo"]] }) response = app.call(env({'rack.protocol' => ['websocket']})) response.first.must_equal 101 end end rack-3.1.12/test/spec_lock.rb000066400000000000000000000114401476365375000160040ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/lock' require_relative '../lib/rack/mock_request' require_relative '../lib/rack/lint' end class Lock attr_reader :synchronized def initialize @synchronized = false end def lock @synchronized = true end def unlock @synchronized = false end end module LockHelpers def lock_app(app, lock = Lock.new) app = if lock Rack::Lock.new app, lock else Rack::Lock.new app end Rack::Lint.new app end end describe Rack::Lock do include LockHelpers describe 'Proxy' do include LockHelpers it 'delegate each' do env = Rack::MockRequest.env_for("/") response = Class.new { attr_accessor :close_called def initialize; @close_called = false; end def each; %w{ hi mom }.each { |x| yield x }; end }.new app = lock_app(lambda { |inner_env| [200, { "content-type" => "text/plain" }, response] }) response = app.call(env)[2] list = [] response.each { |x| list << x } list.must_equal %w{ hi mom } end it 'delegate to_path' do lock = Lock.new env = Rack::MockRequest.env_for("/") res = ['Hello World'] def res.to_path ; "/tmp/hello.txt" ; end app = Rack::Lock.new(lambda { |inner_env| [200, { "content-type" => "text/plain" }, res] }, lock) body = app.call(env)[2] body.must_respond_to :to_path body.to_path.must_equal "/tmp/hello.txt" end it 'not delegate to_path if body does not implement it' do env = Rack::MockRequest.env_for("/") res = ['Hello World'] app = lock_app(lambda { |inner_env| [200, { "content-type" => "text/plain" }, res] }) body = app.call(env)[2] body.wont_respond_to :to_path end end it 'call super on close' do env = Rack::MockRequest.env_for("/") response = Class.new do attr_accessor :close_called def initialize; @close_called = false; end def close; @close_called = true; end end.new app = lock_app(lambda { |inner_env| [200, { "content-type" => "text/plain" }, response] }) app.call(env) response.close_called.must_equal false response.close response.close_called.must_equal true end it "not unlock until body is closed" do lock = Lock.new env = Rack::MockRequest.env_for("/") response = Object.new app = lock_app(lambda { |inner_env| [200, { "content-type" => "text/plain" }, response] }, lock) lock.synchronized.must_equal false response = app.call(env)[2] lock.synchronized.must_equal true response.close lock.synchronized.must_equal false end it "return value from app" do env = Rack::MockRequest.env_for("/") body = [200, { "content-type" => "text/plain" }, %w{ hi mom }] app = lock_app(lambda { |inner_env| body }) res = app.call(env) res[0].must_equal body[0] res[1].must_equal body[1] res[2].to_enum.to_a.must_equal ["hi", "mom"] end it "call synchronize on lock" do lock = Lock.new env = Rack::MockRequest.env_for("/") app = lock_app(lambda { |inner_env| [200, { "content-type" => "text/plain" }, %w{ a b c }] }, lock) lock.synchronized.must_equal false app.call(env) lock.synchronized.must_equal true end it "unlock if the app raises" do lock = Lock.new env = Rack::MockRequest.env_for("/") app = lock_app(lambda { raise Exception }, lock) lambda { app.call(env) }.must_raise Exception lock.synchronized.must_equal false end it "unlock if the app throws" do lock = Lock.new env = Rack::MockRequest.env_for("/") app = lock_app(lambda {|_| throw :bacon }, lock) lambda { app.call(env) }.must_throw :bacon lock.synchronized.must_equal false end it 'not unlock if an error is raised before the mutex is locked' do lock = Class.new do def initialize() @unlocked = false end def unlocked?() @unlocked end def lock() raise Exception end def unlock() @unlocked = true end end.new env = Rack::MockRequest.env_for("/") app = lock_app(proc { [200, { "content-type" => "text/plain" }, []] }, lock) lambda { app.call(env) }.must_raise Exception lock.unlocked?.must_equal false end it "unlock if an exception occurs before returning" do lock = Lock.new env = Rack::MockRequest.env_for("/") app = lock_app(proc { [].freeze }, lock) lambda { app.call(env) }.must_raise Exception lock.synchronized.must_equal false end it "not replace the environment" do env = Rack::MockRequest.env_for("/") app = lock_app(lambda { |inner_env| [200, { "content-type" => "text/plain" }, [inner_env.object_id.to_s]] }) _, _, body = app.call(env) body.to_enum.to_a.must_equal [env.object_id.to_s] end end rack-3.1.12/test/spec_logger.rb000066400000000000000000000013451476365375000163360ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/logger' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::Logger do app = lambda { |env| log = env['rack.logger'] log.debug("Created logger") log.info("Program started") log.warn("Nothing to do!") [200, { 'content-type' => 'text/plain' }, ["Hello, World!"]] } it "conform to Rack::Lint" do errors = StringIO.new a = Rack::Lint.new(Rack::Logger.new(app)) Rack::MockRequest.new(a).get('/', 'rack.errors' => errors) errors.string.must_match(/INFO -- : Program started/) errors.string.must_match(/WARN -- : Nothing to do/) end end rack-3.1.12/test/spec_media_type.rb000066400000000000000000000037061476365375000172020ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/media_type' end describe Rack::MediaType do before { @empty_hash = {} } describe 'when content_type nil' do before { @content_type = nil } it '#type is nil' do Rack::MediaType.type(@content_type).must_be_nil end it '#params is empty' do Rack::MediaType.params(@content_type).must_equal @empty_hash end end describe 'when content_type contains only media_type' do before { @content_type = 'application/text' } it '#type is application/text' do Rack::MediaType.type(@content_type).must_equal 'application/text' end it '#params is empty' do Rack::MediaType.params(@content_type).must_equal @empty_hash end end describe 'when content_type contains media_type and params' do before { @content_type = 'application/text;CHARSET="utf-8"' } it '#type is application/text' do Rack::MediaType.type(@content_type).must_equal 'application/text' end it '#params has key "charset" with value "utf-8"' do Rack::MediaType.params(@content_type)['charset'].must_equal 'utf-8' end end describe 'when content_type contains media_type and incomplete params' do before { @content_type = 'application/text;CHARSET' } it '#type is application/text' do Rack::MediaType.type(@content_type).must_equal 'application/text' end it '#params has key "charset" with value ""' do Rack::MediaType.params(@content_type)['charset'].must_equal '' end end describe 'when content_type contains media_type and empty params' do before { @content_type = 'application/text;CHARSET=' } it '#type is application/text' do Rack::MediaType.type(@content_type).must_equal 'application/text' end it '#params has key "charset" with value of empty string' do Rack::MediaType.params(@content_type)['charset'].must_equal '' end end end rack-3.1.12/test/spec_method_override.rb000066400000000000000000000077601476365375000202450ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/method_override' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::MethodOverride do def app Rack::Lint.new(Rack::MethodOverride.new(lambda {|e| [200, { "content-type" => "text/plain" }, []] })) end it "not affect GET requests" do env = Rack::MockRequest.env_for("/?_method=delete", method: "GET") app.call env env["REQUEST_METHOD"].must_equal "GET" end it "sets rack.errors for invalid UTF8 _method values" do errors = StringIO.new env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=\xBF".b, Rack::RACK_ERRORS => errors) app.call env errors.rewind errors.read.must_equal "Invalid string for method\n" env["REQUEST_METHOD"].must_equal "POST" end it "modify REQUEST_METHOD for POST requests when _method parameter is set" do env = Rack::MockRequest.env_for("/", method: "POST", input: "_method=put") app.call env env["REQUEST_METHOD"].must_equal "PUT" end it "modify REQUEST_METHOD for POST requests when X-HTTP-Method-Override is set" do env = Rack::MockRequest.env_for("/", :method => "POST", "HTTP_X_HTTP_METHOD_OVERRIDE" => "PATCH" ) app.call env env["REQUEST_METHOD"].must_equal "PATCH" end it "not modify REQUEST_METHOD if the method is unknown" do env = Rack::MockRequest.env_for("/", method: "POST", input: "_method=foo") app.call env env["REQUEST_METHOD"].must_equal "POST" end it "not modify REQUEST_METHOD when _method is nil" do env = Rack::MockRequest.env_for("/", method: "POST", input: "foo=bar") app.call env env["REQUEST_METHOD"].must_equal "POST" end it "store the original REQUEST_METHOD prior to overriding" do env = Rack::MockRequest.env_for("/", method: "POST", input: "_method=options") app.call env env["rack.methodoverride.original_method"].must_equal "POST" end it "not modify REQUEST_METHOD when given invalid multipart form data" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size.to_s, :method => "POST", :input => input) app.call env env["REQUEST_METHOD"].must_equal "POST" end it "writes error to RACK_ERRORS when given invalid multipart form data" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size.to_s, Rack::RACK_ERRORS => StringIO.new, :method => "POST", :input => input) Rack::MethodOverride.new(proc { [200, { "content-type" => "text/plain" }, []] }).call env env[Rack::RACK_ERRORS].rewind env[Rack::RACK_ERRORS].read.must_include 'Bad request content body' end it "not modify REQUEST_METHOD for POST requests when the params are unparseable because too deep" do env = Rack::MockRequest.env_for("/", method: "POST", input: ("[a]" * 36) + "=1") app.call env env["REQUEST_METHOD"].must_equal "POST" end it "not modify REQUEST_METHOD for POST requests when the params are unparseable" do env = Rack::MockRequest.env_for("/", method: "POST", input: "(%bad-params%)") app.call env env["REQUEST_METHOD"].must_equal "POST" end it "not set form input when the content type is JSON" do env = Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "application/json", method: "POST", input: '{"_method":"options"}') app.call env env["REQUEST_METHOD"].must_equal "POST" env["rack.request.form_input"].must_be_nil end end rack-3.1.12/test/spec_mime.rb000066400000000000000000000035651476365375000160140ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/mime' end describe Rack::Mime do it "should return the fallback mime-type for files with no extension" do fallback = 'image/jpg' Rack::Mime.mime_type(File.extname('no_ext'), fallback).must_equal fallback end it "should always return 'application/octet-stream' for unknown file extensions" do unknown_ext = File.extname('unknown_ext.abcdefg') Rack::Mime.mime_type(unknown_ext).must_equal 'application/octet-stream' end it "should return the mime-type for a given extension" do # sanity check. it would be infeasible test every single mime-type. Rack::Mime.mime_type(File.extname('image.jpg')).must_equal 'image/jpeg' end it "should support null fallbacks" do Rack::Mime.mime_type('.nothing', nil).must_be_nil end it "should match exact mimes" do Rack::Mime.match?('text/html', 'text/html').must_equal true Rack::Mime.match?('text/html', 'text/meme').must_equal false Rack::Mime.match?('text', 'text').must_equal true Rack::Mime.match?('text', 'binary').must_equal false end it "should match class wildcard mimes" do Rack::Mime.match?('text/html', 'text/*').must_equal true Rack::Mime.match?('text/plain', 'text/*').must_equal true Rack::Mime.match?('application/json', 'text/*').must_equal false Rack::Mime.match?('text/html', 'text').must_equal true end it "should match full wildcards" do Rack::Mime.match?('text/html', '*').must_equal true Rack::Mime.match?('text/plain', '*').must_equal true Rack::Mime.match?('text/html', '*/*').must_equal true Rack::Mime.match?('text/plain', '*/*').must_equal true end it "should match type wildcard mimes" do Rack::Mime.match?('text/html', '*/html').must_equal true Rack::Mime.match?('text/plain', '*/plain').must_equal true end end rack-3.1.12/test/spec_mock_request.rb000066400000000000000000000234741476365375000175670ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'yaml' require_relative 'psych_fix' separate_testing do require_relative '../lib/rack/mock_request' require_relative '../lib/rack/lint' require_relative '../lib/rack/request' require_relative '../lib/rack/body_proxy' end app = Rack::Lint.new(lambda { |env| req = Rack::Request.new(env) if input = env["rack.input"] env["mock.postdata"] = input.read end if req.GET["error"] env["rack.errors"].puts req.GET["error"] env["rack.errors"].flush end body = req.head? ? "" : env.to_yaml response = Rack::Response.new( body, req.GET["status"] || 200, "content-type" => "text/yaml" ) response.set_cookie("session_test", { value: "session_test", domain: "test.com", path: "/" }) response.set_cookie("secure_test", { value: "secure_test", domain: "test.com", path: "/", secure: true }) response.set_cookie("persistent_test", { value: "persistent_test", max_age: 15552000, path: "/" }) response.set_cookie("persistent_with_expires_test", { value: "persistent_with_expires_test", expires: Time.httpdate("Thu, 31 Oct 2021 07:28:00 GMT"), path: "/" }) response.set_cookie("expires_and_max-age_test", { value: "expires_and_max-age_test", expires: Time.now + 15552000 * 2, max_age: 15552000, path: "/" }) response.finish }) describe Rack::MockRequest do it "return a MockResponse" do res = Rack::MockRequest.new(app).get("") res.must_be_kind_of Rack::MockResponse end it "be able to only return the environment" do env = Rack::MockRequest.env_for("") env.must_be_kind_of Hash end it "should handle a non-GET request with :input String and :params" do env = Rack::MockRequest.env_for("/", method: :post, input: "", params: {}) env["PATH_INFO"].must_equal "/" env.must_be_kind_of Hash env['rack.input'].read.must_equal '' end it "should convert :input IO object to binary encoding" do f = File.open(__FILE__, :encoding=>'UTF-8') env = Rack::MockRequest.env_for("/", method: :post, input: f) f.external_encoding.must_equal Encoding::BINARY env['rack.input'].read.must_equal File.binread(__FILE__) ensure f&.close end it "return an environment with a path" do env = Rack::MockRequest.env_for("http://www.example.com/parse?location[]=1&location[]=2&age_group[]=2") env["QUERY_STRING"].must_equal "location[]=1&location[]=2&age_group[]=2" env["PATH_INFO"].must_equal "/parse" env.must_be_kind_of Hash end it "provide sensible defaults" do res = Rack::MockRequest.new(app).request env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "GET" env["SERVER_NAME"].must_equal "example.org" env["SERVER_PORT"].must_equal "80" env["SERVER_PROTOCOL"].must_equal "HTTP/1.1" env["QUERY_STRING"].must_equal "" env["PATH_INFO"].must_equal "/" env["SCRIPT_NAME"].must_equal "" env["rack.url_scheme"].must_equal "http" env["mock.postdata"].must_be_nil end it "allow GET/POST/PUT/DELETE/HEAD" do res = Rack::MockRequest.new(app).get("", input: "foo") env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "GET" res = Rack::MockRequest.new(app).post("", input: "foo") env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "POST" res = Rack::MockRequest.new(app).put("", input: "foo") env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "PUT" res = Rack::MockRequest.new(app).patch("", input: "foo") env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "PATCH" res = Rack::MockRequest.new(app).delete("", input: "foo") env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "DELETE" Rack::MockRequest.env_for("/", method: "HEAD")["REQUEST_METHOD"] .must_equal "HEAD" Rack::MockRequest.env_for("/", method: "OPTIONS")["REQUEST_METHOD"] .must_equal "OPTIONS" end it "set content length" do env = Rack::MockRequest.env_for("/", input: "foo") env["CONTENT_LENGTH"].must_equal "3" env = Rack::MockRequest.env_for("/", input: StringIO.new("foo")) env["CONTENT_LENGTH"].must_equal "3" env = Rack::MockRequest.env_for("/", input: Tempfile.new("name").tap { |t| t << "foo" }) env["CONTENT_LENGTH"].must_equal "3" env = Rack::MockRequest.env_for("/", input: IO.pipe.first) env["CONTENT_LENGTH"].must_be_nil end it "allow posting" do res = Rack::MockRequest.new(app).get("", input: "foo") env = YAML.unsafe_load(res.body) env["mock.postdata"].must_equal "foo" res = Rack::MockRequest.new(app).post("", input: StringIO.new("foo".b)) env = YAML.unsafe_load(res.body) env["mock.postdata"].must_equal "foo" end it "use all parts of an URL" do res = Rack::MockRequest.new(app). get("https://bla.example.org:9292/meh/foo?bar") res.must_be_kind_of Rack::MockResponse env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "GET" env["SERVER_NAME"].must_equal "bla.example.org" env["SERVER_PORT"].must_equal "9292" env["QUERY_STRING"].must_equal "bar" env["PATH_INFO"].must_equal "/meh/foo" env["rack.url_scheme"].must_equal "https" end it "set SSL port and HTTP flag on when using https" do res = Rack::MockRequest.new(app). get("https://example.org/foo") res.must_be_kind_of Rack::MockResponse env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "GET" env["SERVER_NAME"].must_equal "example.org" env["SERVER_PORT"].must_equal "443" env["QUERY_STRING"].must_equal "" env["PATH_INFO"].must_equal "/foo" env["rack.url_scheme"].must_equal "https" env["HTTPS"].must_equal "on" end it "prepend slash to uri path" do res = Rack::MockRequest.new(app). get("foo") res.must_be_kind_of Rack::MockResponse env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "GET" env["SERVER_NAME"].must_equal "example.org" env["SERVER_PORT"].must_equal "80" env["QUERY_STRING"].must_equal "" env["PATH_INFO"].must_equal "/foo" env["rack.url_scheme"].must_equal "http" end it "properly convert method name to an uppercase string" do res = Rack::MockRequest.new(app).request(:get) env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "GET" end it "accept :script_name option to set SCRIPT_NAME" do res = Rack::MockRequest.new(app).get("/", script_name: '/foo') env = YAML.unsafe_load(res.body) env["SCRIPT_NAME"].must_equal "/foo" end it "accept :http_version option to set SERVER_PROTOCOL" do res = Rack::MockRequest.new(app).get("/", http_version: 'HTTP/1.0') env = YAML.unsafe_load(res.body) env["SERVER_PROTOCOL"].must_equal "HTTP/1.0" end it "accept params and build query string for GET requests" do res = Rack::MockRequest.new(app).get("/foo?baz=2", params: { foo: { bar: "1" } }) env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "GET" env["QUERY_STRING"].must_include "baz=2" env["QUERY_STRING"].must_include "foo%5Bbar%5D=1" env["PATH_INFO"].must_equal "/foo" env["mock.postdata"].must_be_nil end it "accept raw input in params for GET requests" do res = Rack::MockRequest.new(app).get("/foo?baz=2", params: "foo%5Bbar%5D=1") env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "GET" env["QUERY_STRING"].must_include "baz=2" env["QUERY_STRING"].must_include "foo%5Bbar%5D=1" env["PATH_INFO"].must_equal "/foo" env["mock.postdata"].must_be_nil end it "accept params and build url encoded params for POST requests" do res = Rack::MockRequest.new(app).post("/foo", params: { foo: { bar: "1" } }) env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "POST" env["QUERY_STRING"].must_equal "" env["PATH_INFO"].must_equal "/foo" env["CONTENT_TYPE"].must_equal "application/x-www-form-urlencoded" env["mock.postdata"].must_equal "foo%5Bbar%5D=1" end it "accept raw input in params for POST requests" do res = Rack::MockRequest.new(app).post("/foo", params: "foo%5Bbar%5D=1") env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "POST" env["QUERY_STRING"].must_equal "" env["PATH_INFO"].must_equal "/foo" env["CONTENT_TYPE"].must_equal "application/x-www-form-urlencoded" env["mock.postdata"].must_equal "foo%5Bbar%5D=1" end it "accept params and build multipart encoded params for POST requests" do files = Rack::Multipart::UploadedFile.new(File.join(File.dirname(__FILE__), "multipart", "file1.txt")) res = Rack::MockRequest.new(app).post("/foo", params: { "submit-name" => "Larry", "files" => files }) env = YAML.unsafe_load(res.body) env["REQUEST_METHOD"].must_equal "POST" env["QUERY_STRING"].must_equal "" env["PATH_INFO"].must_equal "/foo" env["CONTENT_TYPE"].must_equal "multipart/form-data; boundary=AaB03x" # The gsub accounts for differences in YAMLs affect on the data. env["mock.postdata"].gsub("\r", "").length.must_equal 206 end it "behave valid according to the Rack spec" do url = "https://bla.example.org:9292/meh/foo?bar" Rack::MockRequest.new(app).get(url, lint: true). must_be_kind_of Rack::MockResponse end it "call close on the original body object" do called = false body = Rack::BodyProxy.new(['hi']) { called = true } capp = proc { |e| [200, { 'content-type' => 'text/plain' }, body] } called.must_equal false Rack::MockRequest.new(capp).get('/', lint: true) called.must_equal true end it "defaults encoding to ASCII 8BIT" do req = Rack::MockRequest.env_for("/foo") keys = [ Rack::REQUEST_METHOD, Rack::SERVER_NAME, Rack::SERVER_PORT, Rack::QUERY_STRING, Rack::PATH_INFO, Rack::HTTPS, Rack::RACK_URL_SCHEME ] keys.each do |k| assert_equal Encoding::ASCII_8BIT, req[k].encoding end end end rack-3.1.12/test/spec_mock_response.rb000066400000000000000000000237711476365375000177350ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'yaml' require_relative 'psych_fix' separate_testing do require_relative '../lib/rack/mock_request' require_relative '../lib/rack/mock_response' require_relative '../lib/rack/lint' require_relative '../lib/rack/request' end app = Rack::Lint.new(lambda { |env| req = Rack::Request.new(env) if input = env["rack.input"] env["mock.postdata"] = input.read end if req.GET["error"] env["rack.errors"].puts req.GET["error"] env["rack.errors"].flush end body = req.head? ? "" : env.to_yaml response = Rack::Response.new( body, req.GET["status"] || 200, "content-type" => "text/yaml" ) response.set_cookie("session_test", { value: "session_test", domain: "test.com", path: "/" }) response.set_cookie("secure_test", { value: "secure_test", domain: "test.com", path: "/", secure: true }) response.set_cookie("persistent_test", { value: "persistent_test", max_age: 15552000, path: "/" }) response.set_cookie("persistent_with_expires_test", { value: "persistent_with_expires_test", expires: Time.httpdate("Thu, 31 Oct 2021 07:28:00 GMT"), path: "/" }) response.set_cookie("expires_and_max-age_test", { value: "expires_and_max-age_test", expires: Time.now + 15552000 * 2, max_age: 15552000, path: "/" }) response.finish }) describe Rack::MockResponse do it 'has standard constructor' do headers = { "header" => "value" } body = ["body"] response = Rack::MockResponse[200, headers, body] response.status.must_equal 200 response.headers.must_equal headers response.body.must_equal body.join end it "provides access to the HTTP status" do res = Rack::MockRequest.new(app).get("") res.must_be :successful? res.must_be :ok? res = Rack::MockRequest.new(app).get("/?status=404") res.wont_be :successful? res.must_be :client_error? res.must_be :not_found? res = Rack::MockRequest.new(app).get("/?status=501") res.wont_be :successful? res.must_be :server_error? res = Rack::MockRequest.new(app).get("/?status=307") res.must_be :redirect? res = Rack::MockRequest.new(app).get("/?status=201", lint: true) res.must_be :empty? end it "provides access to the HTTP headers" do res = Rack::MockRequest.new(app).get("") res.must_include "content-type" res.headers["content-type"].must_equal "text/yaml" res.original_headers["content-type"].must_equal "text/yaml" res["content-type"].must_equal "text/yaml" res.content_type.must_equal "text/yaml" res.content_length.wont_equal 0 res.location.must_be_nil end it "provides access to session cookies" do res = Rack::MockRequest.new(app).get("") session_cookie = res.cookie("session_test") session_cookie.value[0].must_equal "session_test" session_cookie.domain.must_equal "test.com" session_cookie.path.must_equal "/" session_cookie.secure.must_equal false session_cookie.expires.must_be_nil end it "provides access to persistent cookies set with max-age" do res = Rack::MockRequest.new(app).get("") persistent_cookie = res.cookie("persistent_test") persistent_cookie.value[0].must_equal "persistent_test" persistent_cookie.domain.must_be_nil persistent_cookie.path.must_equal "/" persistent_cookie.secure.must_equal false persistent_cookie.expires.wont_be_nil persistent_cookie.expires.must_be :<, (Time.now + 15552000) end it "provides access to persistent cookies set with expires" do res = Rack::MockRequest.new(app).get("") persistent_cookie = res.cookie("persistent_with_expires_test") persistent_cookie.value[0].must_equal "persistent_with_expires_test" persistent_cookie.domain.must_be_nil persistent_cookie.path.must_equal "/" persistent_cookie.secure.must_equal false persistent_cookie.expires.wont_be_nil persistent_cookie.expires.must_equal Time.httpdate("Thu, 31 Oct 2021 07:28:00 GMT") end it "parses cookies giving max-age precedence over expires" do res = Rack::MockRequest.new(app).get("") persistent_cookie = res.cookie("expires_and_max-age_test") persistent_cookie.value[0].must_equal "expires_and_max-age_test" persistent_cookie.expires.wont_be_nil persistent_cookie.expires.must_be :<, (Time.now + 15552000) end it "provides access to secure cookies" do res = Rack::MockRequest.new(app).get("") secure_cookie = res.cookie("secure_test") secure_cookie.value[0].must_equal "secure_test" secure_cookie.domain.must_equal "test.com" secure_cookie.path.must_equal "/" secure_cookie.secure.must_equal true secure_cookie.expires.must_be_nil end it "parses cookie headers with equals sign at the end" do res = Rack::MockRequest.new(->(env) { [200, { "Set-Cookie" => "__cf_bm=_somebase64encodedstringwithequalsatthened=; array=awesome" }, [""]] }).get("") cookie = res.cookie("__cf_bm") cookie.value[0].must_equal "_somebase64encodedstringwithequalsatthened=" end it "returns nil if a non existent cookie is requested" do res = Rack::MockRequest.new(app).get("") res.cookie("i_dont_exist").must_be_nil end it "handles an empty cookie" do res = Rack::MockRequest.new(->(env) { [200, { "Set-Cookie" => "" }, [""]] }).get("") res.cookie("i_dont_exist").must_be_nil end it "parses multiple set-cookie headers provided as hash with array value" do cookie_headers = { "set-cookie" => ["array=awesome", "multiple=times"]} res = Rack::MockRequest.new(->(env) { [200, cookie_headers, [""]] }).get("") array_cookie = res.cookie("array") array_cookie.value[0].must_equal "awesome" second_cookie = res.cookie("multiple") second_cookie.value[0].must_equal "times" end it "provides access to the HTTP body" do res = Rack::MockRequest.new(app).get("") res.body.must_match(/rack/) assert_match(res, /rack/) res.match('rack')[0].must_equal 'rack' res.match('banana').must_be_nil end it "provides access to the Rack errors" do res = Rack::MockRequest.new(app).get("/?error=foo", lint: true) res.must_be :ok? res.errors.wont_be :empty? res.errors.must_include "foo" end it "allows calling body.close afterwards" do # this is exactly what rack-test does body = StringIO.new("hi") res = Rack::MockResponse.new(200, {}, body) body.close if body.respond_to?(:close) res.body.must_equal 'hi' end it "ignores plain strings passed as errors" do Rack::MockResponse.new(200, {}, [], 'e').errors.must_be_nil end it "optionally makes Rack errors fatal" do lambda { Rack::MockRequest.new(app).get("/?error=foo", fatal: true) }.must_raise Rack::MockRequest::FatalWarning lambda { Rack::MockRequest.new(lambda { |env| env['rack.errors'].write(env['rack.errors'].string) }).get("/", fatal: true) }.must_raise(Rack::MockRequest::FatalWarning).message.must_equal '' end class ChunkedBody # :nodoc: 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 term end # Close the response body if the response body supports it. def close @body.close if @body.respond_to?(:close) end end it "does not calculate content length for streaming body" do body = ChunkedBody.new(["a" * 96]) res = Rack::MockResponse.new(200, { "transfer-encoding" => "chunked" }, body).to_a headers = res[1] refute headers.key?("content-length") end end describe Rack::MockResponse, 'headers' do before do @res = Rack::MockRequest.new(app).get('') @res.set_header 'FOO', '1' end it 'has_header?' do lambda { @res.has_header? nil }.must_raise ArgumentError @res.has_header?('FOO').must_equal true @res.has_header?('Foo').must_equal true end it 'get_header' do lambda { @res.get_header nil }.must_raise ArgumentError @res.get_header('FOO').must_equal '1' @res.get_header('Foo').must_equal '1' end it 'set_header' do lambda { @res.set_header nil, '1' }.must_raise ArgumentError @res.set_header('FOO', '2').must_equal '2' @res.get_header('FOO').must_equal '2' @res.set_header('Foo', '3').must_equal '3' @res.get_header('Foo').must_equal '3' @res.get_header('FOO').must_equal '3' @res.set_header('FOO', nil).must_be_nil @res.get_header('FOO').must_be_nil @res.has_header?('FOO').must_equal true end it 'add_header' do lambda { @res.add_header nil, '1' }.must_raise ArgumentError # Sets header on first addition @res.add_header('FOO', '1').must_equal ['1', '1'] @res.get_header('FOO').must_equal ['1', '1'] # Ignores nil additions @res.add_header('FOO', nil).must_equal ['1', '1'] @res.get_header('FOO').must_equal ['1', '1'] # Converts additions to strings @res.add_header('FOO', 2).must_equal ['1', '1', '2'] @res.get_header('FOO').must_equal ['1', '1', '2'] # Respects underlying case-sensitivity @res.add_header('Foo', 'yep').must_equal ['1', '1', '2', 'yep'] @res.get_header('Foo').must_equal ['1', '1', '2', 'yep'] @res.get_header('FOO').must_equal ['1', '1', '2', 'yep'] end it 'delete_header' do lambda { @res.delete_header nil }.must_raise ArgumentError @res.delete_header('FOO').must_equal '1' @res.has_header?('FOO').must_equal false @res.has_header?('Foo').must_equal false @res.delete_header('Foo').must_be_nil end it 'does not add extra headers' do # Force the body to be "enumerable" only: enumerable_app = lambda { |env| [200, {}, [""].to_enum] } response = Rack::MockRequest.new(enumerable_app).get('/') response.status.must_equal 200 # This fails in Rack < 3.1 as it incorrectly adds a content-length header: response.headers.must_equal({}) response.body.must_equal "" end end rack-3.1.12/test/spec_multipart.rb000066400000000000000000001155411476365375000171040ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'timeout' separate_testing do require_relative '../lib/rack/multipart' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' require_relative '../lib/rack/query_parser' require_relative '../lib/rack/utils' require_relative '../lib/rack/request' end describe Rack::Multipart do def multipart_fixture(name, boundary = "AaB03x") file = multipart_file(name) data = File.open(file, 'rb') { |io| io.read } type = %(multipart/form-data; boundary=#{boundary}) length = data.bytesize { "CONTENT_TYPE" => type, "CONTENT_LENGTH" => length.to_s, :input => StringIO.new(data) } end def multipart_file(name) File.join(File.dirname(__FILE__), "multipart", name.to_s) end it "returns nil if the content type is not multipart" do env = Rack::MockRequest.env_for("/", "CONTENT_TYPE" => 'application/x-www-form-urlencoded', :input => "") Rack::Multipart.parse_multipart(env).must_be_nil end it "raises an exception if boundary is too long" do env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename, "A"*71)) lambda { Rack::Multipart.parse_multipart(env) }.must_raise Rack::Multipart::BoundaryTooLongError end it "raises a bad request exception if no body is given but content type indicates a multipart body" do env = Rack::MockRequest.env_for("/", "CONTENT_TYPE" => 'multipart/form-data; boundary=BurgerBurger', :input => nil) lambda { Rack::Multipart.parse_multipart(env) }.must_raise Rack::Multipart::MissingInputError end it "parses multipart content when content type is present but disposition is not" do env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_disposition)) params = Rack::Multipart.parse_multipart(env) params["text/plain; charset=US-ASCII"].must_equal ["contents"] end it "parses multipart content when content type is present but disposition is not when using IO" do read, write = IO.pipe env = multipart_fixture(:content_type_and_no_disposition) write.write(env[:input].read) write.close env[:input] = read env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_disposition)) params = Rack::Multipart.parse_multipart(env) params["text/plain; charset=US-ASCII"].must_equal ["contents"] end it "parses multipart content when content type present but filename is not" do env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename)) params = Rack::Multipart.parse_multipart(env) params["text"].must_equal "contents" end it "raises for invalid data preceding the boundary" do env = Rack::MockRequest.env_for '/', multipart_fixture(:preceding_boundary) lambda { Rack::Multipart.parse_multipart(env) }.must_raise Rack::Multipart::EmptyContentError end it "ignores initial end boundaries" do env = Rack::MockRequest.env_for '/', multipart_fixture(:end_boundary_first) params = Rack::Multipart.parse_multipart(env) params["files"][:filename].must_equal "foo" end it "parses multipart content with different filename and filename*" do env = Rack::MockRequest.env_for '/', multipart_fixture(:filename_multi) params = Rack::Multipart.parse_multipart(env) params["files"][:filename].must_equal "bar" end it "sets US_ASCII encoding based on charset" do env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename)) params = Rack::Multipart.parse_multipart(env) params["text"].encoding.must_equal Encoding::US_ASCII # I'm not 100% sure if making the param name encoding match the # content-type charset is the right thing to do. We should revisit this. params.keys.each do |key| key.encoding.must_equal Encoding::US_ASCII end end it "sets BINARY encoding for invalid charsets" do env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_unknown_charset)) params = Rack::Multipart.parse_multipart(env) params["text"].encoding.must_equal Encoding::BINARY # I'm not 100% sure if making the param name encoding match the # content-type charset is the right thing to do. We should revisit this. params.keys.each do |key| key.encoding.must_equal Encoding::BINARY end end it "sets BINARY encoding on things without content type" do env = Rack::MockRequest.env_for("/", multipart_fixture(:none)) params = Rack::Multipart.parse_multipart(env) params["submit-name"].encoding.must_equal Encoding::UTF_8 end it "sets UTF8 encoding on names of things without a content type" do env = Rack::MockRequest.env_for("/", multipart_fixture(:none)) params = Rack::Multipart.parse_multipart(env) params.keys.each do |key| key.encoding.must_equal Encoding::UTF_8 end end it "sets default text to UTF8" do env = Rack::MockRequest.env_for("/", multipart_fixture(:text)) params = Rack::Multipart.parse_multipart(env) params['submit-name'].encoding.must_equal Encoding::UTF_8 params['submit-name-with-content'].encoding.must_equal Encoding::UTF_8 params.keys.each do |key| key.encoding.must_equal Encoding::UTF_8 end end it "handles quoted encodings" do # See #905 env = Rack::MockRequest.env_for("/", multipart_fixture(:unity3d_wwwform)) params = Rack::Multipart.parse_multipart(env) params['user_sid'].encoding.must_equal Encoding::UTF_8 end it "parses multipart form webkit style" do env = Rack::MockRequest.env_for '/', multipart_fixture(:webkit) env['CONTENT_TYPE'] = "multipart/form-data; boundary=----WebKitFormBoundaryWLHCs9qmcJJoyjKR" params = Rack::Multipart.parse_multipart(env) params['profile']['bio'].must_include 'hello' params['profile'].keys.must_include 'public_email' end it "rejects insanely long boundaries" do # using a pipe since a tempfile can use up too much space rd, wr = IO.pipe # we only call rewind once at start, so make sure it succeeds # and doesn't hit ESPIPE def rd.rewind; end wr.sync = true # write to a pipe in a background thread, this will write a lot # unless Rack (properly) shuts down the read end thr = Thread.new do begin wr.write("--AaB03x") # make the initial boundary a few gigs long longer = "0123456789" * 1024 * 1024 (1024 * 1024).times do while wr.write_nonblock(longer, exception: false) == :wait_writable Thread.pass end end wr.write("\r\n") wr.write('content-disposition: form-data; name="a"; filename="a.txt"') wr.write("\r\n") wr.write("content-type: text/plain\r\n") wr.write("\r\na") wr.write("--AaB03x--\r\n") wr.close rescue => err # this is EPIPE if Rack shuts us down err end end fixture = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => (1024 * 1024 * 8).to_s, :input => rd, } env = Rack::MockRequest.env_for '/', fixture lambda { Rack::Multipart.parse_multipart(env) }.must_raise Rack::Multipart::EmptyContentError rd.close err = thr.value err.must_be_instance_of Errno::EPIPE wr.close end # see https://github.com/rack/rack/pull/1309 it "parses strange multipart pdf" do boundary = '---------------------------932620571087722842402766118' data = StringIO.new data.write("--#{boundary}") data.write("\r\n") data.write('content-disposition: form-data; name="a"; filename="a.pdf"') data.write("\r\n") data.write("content-type:application/pdf\r\n") data.write("\r\n") data.write("-" * (1024 * 1024)) data.write("\r\n") data.write("--#{boundary}--\r\n") data.rewind fixture = { "CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}", "CONTENT_LENGTH" => data.length.to_s, :input => data, } env = Rack::MockRequest.env_for '/', fixture Timeout::timeout(10) { Rack::Multipart.parse_multipart(env) } end content_disposition_parse = lambda do |params| boundary = '---------------------------932620571087722842402766118' data = StringIO.new data.write("--#{boundary}") data.write("\r\n") data.write("Content-Disposition: form-data;#{params}") data.write("\r\n") data.write("content-type:application/pdf\r\n") data.write("\r\n") data.write("--#{boundary}--\r\n") data.rewind fixture = { "CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}", "CONTENT_LENGTH" => data.length.to_s, :input => data, } env = Rack::MockRequest.env_for '/', fixture Rack::Multipart.parse_multipart(env) end # see https://github.com/rack/rack/issues/2076 it "parses content-disposition with modification date before the name parameter" do x = content_disposition_parse.call(' filename="sample.sql"; modification-date="Wed, 26 Apr 2023 11:01:34 GMT"; size=24; name="file"') x.keys.must_equal ["file"] x["file"][:filename].must_equal "sample.sql" x["file"][:name].must_equal "file" end it "parses content-disposition with colon in parameter value before the name parameter" do x = content_disposition_parse.call(' filename="sam:ple.sql"; name="file"') x.keys.must_equal ["file"] x["file"][:filename].must_equal "sam:ple.sql" x["file"][:name].must_equal "file" end it "parses content-disposition with name= in parameter value before the name parameter" do x = content_disposition_parse.call('filename="name=bar"; name="file"') x.keys.must_equal ["file"] x["file"][:filename].must_equal "name=bar" x["file"][:name].must_equal "file" end it "parses content-disposition with unquoted parameter values" do x = content_disposition_parse.call('filename=sam:ple.sql; name=file') x.keys.must_equal ["file"] x["file"][:filename].must_equal "sam:ple.sql" x["file"][:name].must_equal "file" end it "parses content-disposition with backslash escaped parameter values" do x = content_disposition_parse.call('filename="foo\"bar"; name=file') x.keys.must_equal ["file"] x["file"][:filename].must_equal "foo\"bar" x["file"][:name].must_equal "file" end it "parses content-disposition with IE full paths in filename" do x = content_disposition_parse.call('filename="c:\foo\bar"; name=file;') x.keys.must_equal ["file"] x["file"][:filename].must_equal "bar" x["file"][:name].must_equal "file" end it "parses content-disposition with escaped parameter values in name" do x = content_disposition_parse.call('filename="bar"; name="file\\\\-\\xfoo"') x.keys.must_equal ["file\\-xfoo"] x["file\\-xfoo"][:filename].must_equal "bar" x["file\\-xfoo"][:name].must_equal "file\\-xfoo" end it "parses content-disposition with escaped parameter values in name" do x = content_disposition_parse.call('filename="bar"; name="file\\\\-\\xfoo"') x.keys.must_equal ["file\\-xfoo"] x["file\\-xfoo"][:filename].must_equal "bar" x["file\\-xfoo"][:name].must_equal "file\\-xfoo" end it "parses up to 16 content-disposition params" do x = content_disposition_parse.call("#{14.times.map{|x| "a#{x}=b;"}.join} filename=\"bar\"; name=\"file\"") x.keys.must_equal ["file"] x["file"][:filename].must_equal "bar" x["file"][:name].must_equal "file" end it "stops parsing content-disposition after 16 params" do x = content_disposition_parse.call("#{15.times.map{|x| "a#{x}=b;"}.join} filename=\"bar\"; name=\"file\"") x.keys.must_equal ["bar"] x["bar"][:filename].must_equal "bar" x["bar"][:name].must_equal "bar" end it "allows content-disposition values up to 1536 bytes" do x = content_disposition_parse.call("a=#{'a'*1480}; filename=\"bar\"; name=\"file\"") x.keys.must_equal ["file"] x["file"][:filename].must_equal "bar" x["file"][:name].must_equal "file" end it "ignores content-disposition values over to 1536 bytes" do x = content_disposition_parse.call("a=#{'a'*1510}; filename=\"bar\"; name=\"file\"") x.must_equal "text/plain"=>[""] end it 'raises an EOF error on content-length mismatch' do env = Rack::MockRequest.env_for("/", multipart_fixture(:empty)) env['rack.input'] = StringIO.new assert_raises(EOFError) do Rack::Multipart.parse_multipart(env) end end it "parses multipart upload with text file" do env = Rack::MockRequest.env_for("/", multipart_fixture(:text)) params = Rack::Multipart.parse_multipart(env) params["submit-name"].must_equal "Larry" params["submit-name-with-content"].must_equal "Berry" params["files"][:type].must_equal "text/plain" params["files"][:filename].must_equal "file1.txt" params["files"][:head].must_equal "content-disposition: form-data; " + "name=\"files\"; filename=\"file1.txt\"\r\n" + "content-type: text/plain\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "contents" end it "accepts the params hash class to use for multipart parsing" do c = Class.new(Rack::QueryParser::Params) do def initialize(*) super(){|h, k| h[k.to_s] if k.is_a?(Symbol)} end end query_parser = Rack::QueryParser.new c, 100 env = Rack::MockRequest.env_for("/", multipart_fixture(:text)) params = Rack::Multipart.parse_multipart(env, query_parser) params[:files][:type].must_equal "text/plain" end it "preserves extension in the created tempfile" do env = Rack::MockRequest.env_for("/", multipart_fixture(:text)) params = Rack::Multipart.parse_multipart(env) File.extname(params["files"][:tempfile].path).must_equal ".txt" end it "parses multipart upload with text file with a no name field" do env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_no_name)) params = Rack::Multipart.parse_multipart(env) params["file1.txt"][:type].must_equal "text/plain" params["file1.txt"][:filename].must_equal "file1.txt" params["file1.txt"][:head].must_equal "content-disposition: form-data; " + "filename=\"file1.txt\"\r\n" + "content-type: text/plain\r\n" params["file1.txt"][:name].must_equal "file1.txt" params["file1.txt"][:tempfile].read.must_equal "contents" end it "parses multipart upload file using custom tempfile class" do env = Rack::MockRequest.env_for("/", multipart_fixture(:text)) my_tempfile = "".dup env['rack.multipart.tempfile_factory'] = lambda { |filename, content_type| my_tempfile } params = Rack::Multipart.parse_multipart(env) params["files"][:tempfile].object_id.must_equal my_tempfile.object_id my_tempfile.must_equal "contents" end it "parses multipart upload with nested parameters" do env = Rack::MockRequest.env_for("/", multipart_fixture(:nested)) params = Rack::Multipart.parse_multipart(env) params["foo"]["submit-name"].must_equal "Larry" params["foo"]["files"][:type].must_equal "text/plain" params["foo"]["files"][:filename].must_equal "file1.txt" params["foo"]["files"][:head].must_equal "content-disposition: form-data; " + "name=\"foo[files]\"; filename=\"file1.txt\"\r\n" + "content-type: text/plain\r\n" params["foo"]["files"][:name].must_equal "foo[files]" params["foo"]["files"][:tempfile].read.must_equal "contents" end it "parses multipart upload with binary file" do env = Rack::MockRequest.env_for("/", multipart_fixture(:binary)) params = Rack::Multipart.parse_multipart(env) params["submit-name"].must_equal "Larry" params["files"][:type].must_equal "image/png" params["files"][:filename].must_equal "rack-logo.png" params["files"][:head].must_equal "content-disposition: form-data; " + "name=\"files\"; filename=\"rack-logo.png\"\r\n" + "content-type: image/png\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.length.must_equal 26473 end it "parses multipart upload with an empty file" do env = Rack::MockRequest.env_for("/", multipart_fixture(:empty)) params = Rack::Multipart.parse_multipart(env) params["submit-name"].must_equal "Larry" params["files"][:type].must_equal "text/plain" params["files"][:filename].must_equal "file1.txt" params["files"][:head].must_equal "content-disposition: form-data; " + "name=\"files\"; filename=\"file1.txt\"\r\n" + "content-type: text/plain\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "" end it "parses multipart upload with a filename containing semicolons" do env = Rack::MockRequest.env_for("/", multipart_fixture(:semicolon)) params = Rack::Multipart.parse_multipart(env) params["files"][:type].must_equal "text/plain" params["files"][:filename].must_equal "fi;le1.txt" params["files"][:head].must_equal "content-disposition: form-data; " + "name=\"files\"; filename=\"fi;le1.txt\"\r\n" + "content-type: text/plain\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "contents" end it "parses multipart upload with quoted boundary" do env = Rack::MockRequest.env_for("/", multipart_fixture(:quoted, %("AaB:03x"))) params = Rack::Multipart.parse_multipart(env) params["submit-name"].must_equal "Larry" params["submit-name-with-content"].must_equal "Berry" params["files"][:type].must_equal "text/plain" params["files"][:filename].must_equal "file1.txt" params["files"][:head].must_equal "content-disposition: form-data; " + "name=\"files\"; filename=\"file1.txt\"\r\n" + "content-type: text/plain\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "contents" end it "parses multipart upload with a filename containing invalid characters" do env = Rack::MockRequest.env_for("/", multipart_fixture(:invalid_character)) params = Rack::Multipart.parse_multipart(env) params["files"][:type].must_equal "text/plain" params["files"][:filename].must_match(/invalid/) head = "content-disposition: form-data; " + "name=\"files\"; filename=\"invalid\xC3.txt\"\r\n" + "content-type: text/plain\r\n" head = head.force_encoding(Encoding::ASCII_8BIT) params["files"][:head].must_equal head params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "contents" end it "parses multipart form with an encoded word filename" do env = Rack::MockRequest.env_for '/', multipart_fixture(:filename_with_encoded_words) params = Rack::Multipart.parse_multipart(env) params["files"][:filename].must_equal "файл" end it "parses multipart form with a single quote in the filename" do env = Rack::MockRequest.env_for '/', multipart_fixture(:filename_with_single_quote) params = Rack::Multipart.parse_multipart(env) params["files"][:filename].must_equal "bob's flowers.jpg" end it "parses multipart form with a null byte in the filename" do env = Rack::MockRequest.env_for '/', multipart_fixture(:filename_with_null_byte) params = Rack::Multipart.parse_multipart(env) params["files"][:filename].must_equal "flowers.exe\u0000.jpg" end it "is robust separating content-disposition fields" do env = Rack::MockRequest.env_for("/", multipart_fixture(:robust_field_separation)) params = Rack::Multipart.parse_multipart(env) params["text"].must_equal "contents" end it "does not include file params if no file was selected" do env = Rack::MockRequest.env_for("/", multipart_fixture(:none)) params = Rack::Multipart.parse_multipart(env) params["submit-name"].must_equal "Larry" params["files"].must_be_nil params.keys.wont_include "files" end it "parses multipart/mixed" do env = Rack::MockRequest.env_for("/", multipart_fixture(:mixed_files)) params = Rack::Multipart.parse_multipart(env) params["foo"].must_equal "bar" params["files"].must_be_instance_of String params["files"].size.must_equal 252 end it "parses IE multipart upload and cleans up the filename" do env = Rack::MockRequest.env_for("/", multipart_fixture(:ie)) params = Rack::Multipart.parse_multipart(env) params["files"][:type].must_equal "text/plain" params["files"][:filename].must_equal "file1.txt" params["files"][:head].must_equal "content-disposition: form-data; " + "name=\"files\"; " + 'filename="C:\Documents and Settings\Administrator\Desktop\file1.txt"' + "\r\ncontent-type: text/plain\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "contents" end it "parses filename and modification param" do env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_modification_param)) params = Rack::Multipart.parse_multipart(env) params["files"][:type].must_equal "image/jpeg" params["files"][:filename].must_equal "genome.jpeg" params["files"][:head].must_equal "content-type: image/jpeg\r\n" + "content-disposition: attachment; " + "name=\"files\"; " + "filename=genome.jpeg; " + "modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" + "Content-Description: a complete map of the human genome\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "contents" end it "parses filename with escaped quotes" do env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes)) params = Rack::Multipart.parse_multipart(env) params["files"][:type].must_equal "application/octet-stream" params["files"][:filename].must_equal "escape \"quotes" params["files"][:head].must_equal "content-disposition: form-data; " + "name=\"files\"; " + "filename=\"escape \\\"quotes\"\r\n" + "content-type: application/octet-stream\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "contents" end it "parses filename with plus character" do env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_plus)) params = Rack::Multipart.parse_multipart(env) params["files"][:type].must_equal "application/octet-stream" params["files"][:filename].must_equal "foo+bar" params["files"][:head].must_equal "content-disposition: form-data; " + "name=\"files\"; " + "filename=\"foo+bar\"\r\n" + "content-type: application/octet-stream\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "contents" end it "parses filename with percent escaped quotes" do env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_percent_escaped_quotes)) params = Rack::Multipart.parse_multipart(env) params["files"][:type].must_equal "application/octet-stream" params["files"][:filename].must_equal "escape \"quotes" params["files"][:head].must_equal "content-disposition: form-data; " + "name=\"files\"; " + "filename=\"escape %22quotes\"\r\n" + "content-type: application/octet-stream\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "contents" end it "parses filename with escaped quotes and modification param" do env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes_and_modification_param)) params = Rack::Multipart.parse_multipart(env) params["files"][:type].must_equal "image/jpeg" params["files"][:filename].must_equal "\"human\" genome.jpeg" params["files"][:head].must_equal "content-type: image/jpeg\r\n" + "content-disposition: attachment; " + "name=\"files\"; " + "filename=\"\\\"human\\\" genome.jpeg\"; " + "modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" + "Content-Description: a complete map of the human genome\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "contents" end it "parses filename with unescaped percentage characters" do env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages, "----WebKitFormBoundary2NHc7OhsgU68l3Al")) params = Rack::Multipart.parse_multipart(env) files = params["document"]["attachment"] files[:type].must_equal "image/jpeg" files[:filename].must_equal "100% of a photo.jpeg" files[:head].must_equal <<-MULTIPART content-disposition: form-data; name="document[attachment]"; filename="100% of a photo.jpeg"\r content-type: image/jpeg\r MULTIPART files[:name].must_equal "document[attachment]" files[:tempfile].read.must_equal "contents" end it "parses filename with unescaped percentage characters that look like partial hex escapes" do env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages2, "----WebKitFormBoundary2NHc7OhsgU68l3Al")) params = Rack::Multipart.parse_multipart(env) files = params["document"]["attachment"] files[:type].must_equal "image/jpeg" files[:filename].must_equal "100%a" files[:head].must_equal <<-MULTIPART content-disposition: form-data; name="document[attachment]"; filename="100%a"\r content-type: image/jpeg\r MULTIPART files[:name].must_equal "document[attachment]" files[:tempfile].read.must_equal "contents" end it "parses filename with unescaped percentage characters that look like partial hex escapes" do env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages3, "----WebKitFormBoundary2NHc7OhsgU68l3Al")) params = Rack::Multipart.parse_multipart(env) files = params["document"]["attachment"] files[:type].must_equal "image/jpeg" files[:filename].must_equal "100%" files[:head].must_equal <<-MULTIPART content-disposition: form-data; name="document[attachment]"; filename="100%"\r content-type: image/jpeg\r MULTIPART files[:name].must_equal "document[attachment]" files[:tempfile].read.must_equal "contents" end it "raises a RuntimeError for invalid file path" do proc{Rack::Multipart::UploadedFile.new('non-existant')}.must_raise RuntimeError end it "supports uploading files in binary mode" do Rack::Multipart::UploadedFile.new(multipart_file("file1.txt")).wont_be :binmode? Rack::Multipart::UploadedFile.new(multipart_file("file1.txt"), binary: true).must_be :binmode? end it "builds multipart body" do files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt")) data = Rack::Multipart.build_multipart("submit-name" => "Larry", "files" => files) options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params["submit-name"].must_equal "Larry" params["files"][:filename].must_equal "file1.txt" params["files"][:tempfile].read.must_equal "contents" end it "builds multipart filename with space" do files = Rack::Multipart::UploadedFile.new(multipart_file("space case.txt")) data = Rack::Multipart.build_multipart("submit-name" => "Larry", "files" => files) options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params["submit-name"].must_equal "Larry" params["files"][:filename].must_equal "space case.txt" params["files"][:tempfile].read.must_equal "contents" end it "builds nested multipart body using array" do files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt")) data = Rack::Multipart.build_multipart("people" => [{ "submit-name" => "Larry", "files" => files }]) options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params["people"][0]["submit-name"].must_equal "Larry" params["people"][0]["files"][:filename].must_equal "file1.txt" params["people"][0]["files"][:tempfile].read.must_equal "contents" end it "builds nested multipart body using hash" do files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt")) data = Rack::Multipart.build_multipart("people" => { "foo" => { "submit-name" => "Larry", "files" => files } }) options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params["people"]["foo"]["submit-name"].must_equal "Larry" params["people"]["foo"]["files"][:filename].must_equal "file1.txt" params["people"]["foo"]["files"][:tempfile].read.must_equal "contents" end it "builds multipart body from StringIO" do files = Rack::Multipart::UploadedFile.new(io: StringIO.new('foo'), filename: 'bar.txt') data = Rack::Multipart.build_multipart("submit-name" => "Larry", "files" => files) options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params["submit-name"].must_equal "Larry" params["files"][:filename].must_equal "bar.txt" params["files"][:tempfile].read.must_equal "foo" end it "can parse fields that end at the end of the buffer" do input = File.read(multipart_file("bad_robots")) req = Rack::Request.new Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "multipart/form-data, boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon", "CONTENT_LENGTH" => input.size, :input => input) req.POST['file.path'].must_equal "/var/tmp/uploads/4/0001728414" req.POST['addresses'].wont_equal nil end it "builds complete params with the chunk size of 16384 slicing exactly on boundary" do begin previous_limit = Rack::Utils.multipart_part_limit Rack::Utils.multipart_part_limit = 256 data = File.open(multipart_file("fail_16384_nofile"), 'rb') { |f| f.read }.gsub(/\n/, "\r\n") options = { "CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params.wont_equal nil params.keys.must_include "AAAAAAAAAAAAAAAAAAA" params["AAAAAAAAAAAAAAAAAAA"].keys.must_include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER" params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.must_include "new" params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.must_include "-2" params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.must_include "ba_unit_id" params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].must_equal "1017" ensure Rack::Utils.multipart_part_limit = previous_limit end end it "does not reach a multi-part limit" do begin previous_limit = Rack::Utils.multipart_part_limit Rack::Utils.multipart_part_limit = 4 env = Rack::MockRequest.env_for '/', multipart_fixture(:three_files_three_fields) params = Rack::Multipart.parse_multipart(env) params['reply'].must_equal 'yes' params['to'].must_equal 'people' params['from'].must_equal 'others' ensure Rack::Utils.multipart_part_limit = previous_limit end end it "treats a multipart limit of 0 as no limit" do begin previous_limit = Rack::Utils.multipart_part_limit Rack::Utils.multipart_part_limit = 0 env = Rack::MockRequest.env_for '/', multipart_fixture(:three_files_three_fields) params = Rack::Multipart.parse_multipart(env) params['reply'].must_equal 'yes' params['to'].must_equal 'people' params['from'].must_equal 'others' ensure Rack::Utils.multipart_part_limit = previous_limit end end it "reaches a multipart file limit" do begin previous_limit = Rack::Utils.multipart_part_limit Rack::Utils.multipart_part_limit = 3 env = Rack::MockRequest.env_for '/', multipart_fixture(:three_files_three_fields) lambda { Rack::Multipart.parse_multipart(env) }.must_raise Rack::Multipart::MultipartPartLimitError ensure Rack::Utils.multipart_part_limit = previous_limit end end it "reaches a multipart total limit" do begin previous_limit = Rack::Utils.multipart_total_part_limit Rack::Utils.multipart_total_part_limit = 5 env = Rack::MockRequest.env_for '/', multipart_fixture(:three_files_three_fields) lambda { Rack::Multipart.parse_multipart(env) }.must_raise Rack::Multipart::MultipartTotalPartLimitError ensure Rack::Utils.multipart_total_part_limit = previous_limit end end it "returns nil if no UploadedFiles were used" do data = Rack::Multipart.build_multipart("people" => [{ "submit-name" => "Larry", "files" => "contents" }]) data.must_be_nil end it "raises ArgumentError if params is not a Hash" do lambda { Rack::Multipart.build_multipart("foo=bar") }.must_raise(ArgumentError).message.must_equal "value must be a Hash" end it "is able to parse fields with a content type" do data = <<-EOF --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon\r content-disposition: form-data; name="description"\r content-type: text/plain"\r \r Very very blue\r --1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon--\r EOF options = { "CONTENT_TYPE" => "multipart/form-data; boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params.must_equal "description" => "Very very blue" end it "parses multipart upload with no content-length header" do env = Rack::MockRequest.env_for '/', multipart_fixture(:webkit) env['CONTENT_TYPE'] = "multipart/form-data; boundary=----WebKitFormBoundaryWLHCs9qmcJJoyjKR" env.delete 'CONTENT_LENGTH' params = Rack::Multipart.parse_multipart(env) params['profile']['bio'].must_include 'hello' end ['', '"'].each do |quote_char| it "parses very long #{'un' if quote_char.empty?}quoted multipart file names" do data = <<-EOF --AaB03x\r content-type: text/plain\r content-disposition: attachment; name=file; filename=#{quote_char}#{'long' * 100}#{quote_char}\r \r contents\r --AaB03x--\r EOF options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params["file"][:filename].must_equal 'long' * 100 end end it "does not remove escaped quotes in filenames" do data = <<-EOF --AaB03x\r content-type: text/plain\r content-disposition: attachment; name=file; filename="\\"#{'long' * 100}\\""\r \r contents\r --AaB03x--\r EOF options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params["file"][:filename].must_equal "\"#{'long' * 100}\"" end it "limits very long file name extensions in multipart tempfiles" do data = <<-EOF --AaB03x\r content-type: text/plain\r content-disposition: attachment; name=file; filename=foo.#{'a' * 1000}\r \r contents\r --AaB03x--\r EOF options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params["file"][:filename].must_equal "foo.#{'a' * 1000}" File.extname(env["rack.tempfiles"][0]).must_equal ".#{'a' * 128}" end it "parses unquoted parameter values at end of line" do data = <<-EOF --AaB03x\r content-type: text/plain\r content-disposition: attachment; name=inline\r \r true\r --AaB03x--\r EOF options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params["inline"].must_equal 'true' end it "parses quoted chars in name parameter" do data = <<-EOF --AaB03x\r content-type: text/plain\r content-disposition: attachment; name="quoted\\\\chars\\"in\rname"\r \r true\r --AaB03x--\r EOF options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params["quoted\\chars\"in\rname"].must_equal 'true' end it "supports mixed case metadata" do file = multipart_file(:text) data = File.open(file, 'rb') { |io| io.read } type = "Multipart/Form-Data; Boundary=AaB03x" length = data.bytesize e = { "CONTENT_TYPE" => type, "CONTENT_LENGTH" => length.to_s, :input => StringIO.new(data) } env = Rack::MockRequest.env_for("/", e) params = Rack::Multipart.parse_multipart(env) params["submit-name"].must_equal "Larry" params["submit-name-with-content"].must_equal "Berry" params["files"][:type].must_equal "text/plain" params["files"][:filename].must_equal "file1.txt" params["files"][:head].must_equal "content-disposition: form-data; " + "name=\"files\"; filename=\"file1.txt\"\r\n" + "content-type: text/plain\r\n" params["files"][:name].must_equal "files" params["files"][:tempfile].read.must_equal "contents" end it "falls back to content-type for the name" do rack_logo = File.read(multipart_file("rack-logo.png")) data = <<-EOF.dup --AaB03x\r content-type: text/plain\r \r some text\r --AaB03x\r \r \r some more text (I didn't specify content-type)\r --AaB03x\r content-type: image/png\r \r #{rack_logo}\r --AaB03x--\r EOF options = { "CONTENT_TYPE" => "multipart/related; boundary=AaB03x", "CONTENT_LENGTH" => data.bytesize.to_s, :input => StringIO.new(data.dup) } env = Rack::MockRequest.env_for("/", options) params = Rack::Multipart.parse_multipart(env) params["text/plain"].must_equal ["some text", "some more text (I didn't specify content-type)"] params["image/png"].length.must_equal 1 f = Tempfile.new("rack-logo") f.write(params["image/png"][0]) f.length.must_equal 26473 end end rack-3.1.12/test/spec_null_logger.rb000066400000000000000000000012171476365375000173660ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/null_logger' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::NullLogger do it "act as a noop logger" do app = lambda { |env| env['rack.logger'].warn "b00m" [200, { 'content-type' => 'text/plain' }, ["Hello, World!"]] } logger = Rack::Lint.new(Rack::NullLogger.new(app)) res = logger.call(Rack::MockRequest.env_for) res[0..1].must_equal [ 200, { 'content-type' => 'text/plain' } ] res[2].to_enum.to_a.must_equal ["Hello, World!"] end end rack-3.1.12/test/spec_query_parser.rb000066400000000000000000000007321476365375000175770ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/query_parser' end describe Rack::QueryParser do query_parser ||= Rack::QueryParser.make_default(8) it "can normalize values with missing values" do query_parser.parse_nested_query("a=a").must_equal({"a" => "a"}) query_parser.parse_nested_query("a=").must_equal({"a" => ""}) query_parser.parse_nested_query("a").must_equal({"a" => nil}) end end rack-3.1.12/test/spec_recursive.rb000066400000000000000000000040021476365375000170570ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/recursive' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' require_relative '../lib/rack/urlmap' end describe Rack::Recursive do before do @app1 = lambda { |env| res = Rack::Response.new res["x-path-info"] = env["PATH_INFO"] res["x-query-string"] = env["QUERY_STRING"] res.finish do |inner_res| inner_res.write "App1" end } @app2 = lambda { |env| Rack::Response.new.finish do |res| res.write "App2" _, _, body = env['rack.recursive.include'].call(env, "/app1") body.each { |b| res.write b } end } @app3 = lambda { |env| raise Rack::ForwardRequest.new("/app1") } @app4 = lambda { |env| raise Rack::ForwardRequest.new("http://example.org/app1/quux?meh") } end def recursive(map) Rack::Lint.new Rack::Recursive.new(Rack::URLMap.new(map)) end it "allow for subrequests" do res = Rack::MockRequest.new(recursive("/app1" => @app1, "/app2" => @app2)). get("/app2") res.must_be :ok? res.body.must_equal "App2App1" end it "raise error on requests not below the app" do app = Rack::URLMap.new("/app1" => @app1, "/app" => recursive("/1" => @app1, "/2" => @app2)) lambda { Rack::MockRequest.new(app).get("/app/2") }.must_raise(ArgumentError). message.must_match(/can only include below/) end it "support forwarding" do app = recursive("/app1" => @app1, "/app3" => @app3, "/app4" => @app4) res = Rack::MockRequest.new(app).get("/app3") res.must_be :ok? res.body.must_equal "App1" res = Rack::MockRequest.new(app).get("/app4") res.must_be :ok? res.body.must_equal "App1" res["x-path-info"].must_equal "/quux" res["x-query-string"].must_equal "meh" end end rack-3.1.12/test/spec_request.rb000066400000000000000000002036331476365375000165530ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'cgi' require 'forwardable' require 'securerandom' separate_testing do require_relative '../lib/rack/request' require_relative '../lib/rack/mock_request' require_relative '../lib/rack/lint' end class RackRequestTest < Minitest::Spec it "copies the env when duping" do req = make_request(Rack::MockRequest.env_for("http://example.com:8080/")) if req.delegate? skip "delegate requests don't dup environments" end refute_same req.env, req.dup.env end it 'can check if something has been set' do req = make_request(Rack::MockRequest.env_for("http://example.com:8080/")) refute req.has_header?("FOO") end it "can get a key from the env" do req = make_request(Rack::MockRequest.env_for("http://example.com:8080/")) assert_equal "example.com", req.get_header("SERVER_NAME") end it 'can calculate the authority' do req = make_request(Rack::MockRequest.env_for("http://example.com:8080/")) assert_equal "example.com:8080", req.authority end it 'can calculate the authority without a port' do req = make_request(Rack::MockRequest.env_for("http://example.com/")) assert_equal "example.com:80", req.authority end it 'can calculate the authority without a port on ssl' do req = make_request(Rack::MockRequest.env_for("https://example.com/")) assert_equal "example.com:443", req.authority end it 'can calculate the server authority' do req = make_request('SERVER_NAME' => 'example.com') assert_equal "example.com", req.server_authority req = make_request('SERVER_NAME' => 'example.com', 'SERVER_PORT' => 8080) assert_equal "example.com:8080", req.server_authority end it 'can calculate the port without an authority' do req = make_request('SERVER_PORT' => 8080) assert_equal 8080, req.port req = make_request('HTTPS' => 'on') assert_equal 443, req.port end it 'yields to the block if no value has been set' do req = make_request(Rack::MockRequest.env_for("http://example.com:8080/")) yielded = false req.fetch_header("FOO") do yielded = true req.set_header "FOO", 'bar' end assert yielded assert_equal "bar", req.get_header("FOO") end it 'can iterate over values' do req = make_request(Rack::MockRequest.env_for("http://example.com:8080/")) req.set_header 'foo', 'bar' hash = {} req.each_header do |k, v| hash[k] = v end assert_equal 'bar', hash['foo'] end it 'can set values in the env' do req = make_request(Rack::MockRequest.env_for("http://example.com:8080/")) req.set_header("FOO", "BAR") assert_equal "BAR", req.get_header("FOO") end it 'can add to multivalued headers in the env' do req = make_request(Rack::MockRequest.env_for('http://example.com:8080/')) assert_equal '1', req.add_header('FOO', '1') assert_equal '1', req.get_header('FOO') assert_equal '1,2', req.add_header('FOO', '2') assert_equal '1,2', req.get_header('FOO') assert_equal '1,2', req.add_header('FOO', nil) assert_equal '1,2', req.get_header('FOO') end it 'can delete env values' do req = make_request(Rack::MockRequest.env_for("http://example.com:8080/")) req.set_header 'foo', 'bar' assert req.has_header? 'foo' req.delete_header 'foo' refute req.has_header? 'foo' end it "wrap the rack variables" do req = make_request(Rack::MockRequest.env_for("http://example.com:8080/")) req.body.must_be_nil req.scheme.must_equal "http" req.request_method.must_equal "GET" req.must_be :get? req.wont_be :post? req.wont_be :put? req.wont_be :delete? req.wont_be :head? req.wont_be :patch? req.script_name.must_equal "" req.path_info.must_equal "/" req.query_string.must_equal "" req.host.must_equal "example.com" req.port.must_equal 8080 req.content_length.must_be_nil req.content_type.must_be_nil end it "figure out the correct host" do req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org") req.host.must_equal "www2.example.org" req.hostname.must_equal "www2.example.org" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "123foo.example.com") req.host.must_equal "123foo.example.com" req.hostname.must_equal "123foo.example.com" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "♡.com") req.host.must_equal "♡.com" req.hostname.must_equal "♡.com" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "♡.com:80") req.host.must_equal "♡.com" req.hostname.must_equal "♡.com" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "nic.谷歌") req.host.must_equal "nic.谷歌" req.hostname.must_equal "nic.谷歌" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "nic.谷歌:80") req.host.must_equal "nic.谷歌" req.hostname.must_equal "nic.谷歌" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "technically_invalid.example.com") req.host.must_equal "technically_invalid.example.com" req.hostname.must_equal "technically_invalid.example.com" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "technically_invalid.example.com:80") req.host.must_equal "technically_invalid.example.com" req.hostname.must_equal "technically_invalid.example.com" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "trailing_newline.com\n") req.host.must_be_nil req.hostname.must_be_nil req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "really\nbad\ninput") req.host.must_be_nil req.hostname.must_be_nil req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "some_service:3001") req.host.must_equal "some_service" req.hostname.must_equal "some_service" req = make_request \ Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292") req.host.must_equal "example.org" req.hostname.must_equal "example.org" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_FORWARDED" => "host=example.org:9292") req.host.must_equal "example.org" # Test obfuscated identifier: https://tools.ietf.org/html/rfc7239#section-6.3 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_FORWARDED" => "host=ObFuScaTeD") req.host.must_equal "ObFuScaTeD" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_FORWARDED" => "host=example.com; host=example.org:9292") req.host.must_equal "example.org" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292", "HTTP_FORWARDED" => "host=example.com") req.host.must_equal "example.com" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292") req.host.must_equal "example.org" req.hostname.must_equal "example.org" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "[2001:db8:cafe::17]:47011") req.host.must_equal "[2001:db8:cafe::17]" req.hostname.must_equal "2001:db8:cafe::17" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "2001:db8:cafe::17") req.host.must_equal "[2001:db8:cafe::17]" req.hostname.must_equal "2001:db8:cafe::17" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "[::]:47011") req.host.must_equal "[::]" req.hostname.must_equal "::" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "[1111:2222:3333:4444:5555:6666:123.123.123.123]") req.host.must_equal "[1111:2222:3333:4444:5555:6666:123.123.123.123]" req.hostname.must_equal "1111:2222:3333:4444:5555:6666:123.123.123.123" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "[1111:2222:3333:4444:5555:6666:123.123.123.123]:47011") req.host.must_equal "[1111:2222:3333:4444:5555:6666:123.123.123.123]" req.hostname.must_equal "1111:2222:3333:4444:5555:6666:123.123.123.123" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "0.0.0.0") req.host.must_equal "0.0.0.0" req.hostname.must_equal "0.0.0.0" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "0.0.0.0:47011") req.host.must_equal "0.0.0.0" req.hostname.must_equal "0.0.0.0" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "255.255.255.255") req.host.must_equal "255.255.255.255" req.hostname.must_equal "255.255.255.255" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "255.255.255.255:47011") req.host.must_equal "255.255.255.255" req.hostname.must_equal "255.255.255.255" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "really\nbad\ninput") req.host.must_be_nil req.hostname.must_be_nil req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "[0]") req.host.must_be_nil req.hostname.must_be_nil req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "[:::]") req.host.must_be_nil req.hostname.must_be_nil req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "[1111:2222:3333:4444:5555:6666:7777:88888]") req.host.must_be_nil req.hostname.must_be_nil req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "0.0..0.0") req.host.must_equal '0.0..0.0' req.hostname.must_equal '0.0..0.0' req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "255.255.255.0255") req.host.must_equal "255.255.255.0255" req.hostname.must_equal "255.255.255.0255" env = Rack::MockRequest.env_for("/") env.delete("SERVER_NAME") req = make_request(env) req.host.must_be_nil end it "figure out the correct port" do req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org") req.port.must_equal 80 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org:81") req.port.must_equal 81 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "some_service:3001") req.port.must_equal 3001 req = make_request \ Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292") req.port.must_equal 9292 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292") req.port.must_equal 9292 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "[2001:db8:cafe::17]:47011") req.port.must_equal 47011 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "2001:db8:cafe::17") req.port.must_equal 80 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org") req.port.must_equal 80 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_SSL" => "on") req.port.must_equal 443 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_PROTO" => "https") req.port.must_equal 443 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_PORT" => "9393") req.port.must_equal 9393 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9393", "SERVER_PORT" => "80") req.port.must_equal 9393 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "SERVER_PORT" => "9393") req.port.must_equal 80 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost", "HTTP_X_FORWARDED_PROTO" => "https", "SERVER_PORT" => "80") req.port.must_equal 443 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost", "HTTP_X_FORWARDED_PROTO" => "https,https", "SERVER_PORT" => "80") req.port.must_equal 443 req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost", "HTTP_FORWARDED" => "proto=https", "HTTP_X_FORWARDED_PROTO" => "http", "SERVER_PORT" => "9393") req.port.must_equal 443 end it "have forwarded_* methods respect forwarded_priority" do begin default_priority = Rack::Request.forwarded_priority default_proto_priority = Rack::Request.x_forwarded_proto_priority def self.req(headers) req = make_request Rack::MockRequest.env_for("/", headers) req.singleton_class.send(:public, :forwarded_scheme) req end req("HTTP_FORWARDED"=>"for=1.2.3.4", "HTTP_X_FORWARDED_FOR" => "2.3.4.5"). forwarded_for.must_equal ['1.2.3.4'] req("HTTP_FORWARDED"=>"for=1.2.3.4:1234", "HTTP_X_FORWARDED_PORT" => "2345"). forwarded_port.must_equal [1234] req("HTTP_FORWARDED"=>"for=1.2.3.4", "HTTP_X_FORWARDED_PORT" => "2345"). forwarded_port.must_equal [] req("HTTP_FORWARDED"=>"host=1.2.3.4, host=3.4.5.6", "HTTP_X_FORWARDED_HOST" => "2.3.4.5,4.5.6.7"). forwarded_authority.must_equal '3.4.5.6' req("HTTP_X_FORWARDED_PROTO" => "ws", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_equal "ws" req("HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_equal "http" Rack::Request.forwarded_priority = [nil, :x_forwarded, :forwarded] req("HTTP_FORWARDED"=>"for=1.2.3.4", "HTTP_X_FORWARDED_FOR" => "2.3.4.5"). forwarded_for.must_equal ['2.3.4.5'] req("HTTP_FORWARDED"=>"for=1.2.3.4", "HTTP_X_FORWARDED_PORT" => "2345"). forwarded_port.must_equal [2345] req("HTTP_FORWARDED"=>"host=1.2.3.4, host=3.4.5.6", "HTTP_X_FORWARDED_HOST" => "2.3.4.5,4.5.6.7"). forwarded_authority.must_equal '4.5.6.7' req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_PROTO" => "ws", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_equal "ws" req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_equal "http" req("HTTP_FORWARDED"=>"proto=https"). forwarded_scheme.must_equal "https" Rack::Request.x_forwarded_proto_priority = [nil, :scheme, :proto] req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_PROTO" => "ws", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_equal "http" req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_PROTO" => "ws"). forwarded_scheme.must_equal "ws" req("HTTP_FORWARDED"=>"proto=https"). forwarded_scheme.must_equal "https" Rack::Request.forwarded_priority = [:x_forwarded] req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_PROTO" => "ws", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_equal "http" req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_PROTO" => "ws"). forwarded_scheme.must_equal "ws" req("HTTP_FORWARDED"=>"proto=https"). forwarded_scheme.must_be_nil Rack::Request.x_forwarded_proto_priority = [:scheme] req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_PROTO" => "ws", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_equal "http" req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_PROTO" => "ws"). forwarded_scheme.must_be_nil req("HTTP_FORWARDED"=>"proto=https"). forwarded_scheme.must_be_nil Rack::Request.x_forwarded_proto_priority = [:proto] req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_PROTO" => "ws", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_equal "ws" req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_be_nil req("HTTP_FORWARDED"=>"proto=https"). forwarded_scheme.must_be_nil Rack::Request.x_forwarded_proto_priority = [] req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_PROTO" => "ws", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_be_nil req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_be_nil req("HTTP_FORWARDED"=>"proto=https"). forwarded_scheme.must_be_nil Rack::Request.x_forwarded_proto_priority = default_proto_priority Rack::Request.forwarded_priority = [:forwarded] req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_PROTO" => "ws", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_equal 'https' req("HTTP_X_FORWARDED_PROTO" => "ws", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_be_nil req("HTTP_X_FORWARDED_PROTO" => "ws"). forwarded_scheme.must_be_nil Rack::Request.forwarded_priority = [] req("HTTP_FORWARDED"=>"for=1.2.3.4", "HTTP_X_FORWARDED_FOR" => "2.3.4.5"). forwarded_for.must_be_nil req("HTTP_FORWARDED"=>"for=1.2.3.4", "HTTP_X_FORWARDED_PORT" => "2345"). forwarded_port.must_be_nil req("HTTP_FORWARDED"=>"host=1.2.3.4, host=3.4.5.6", "HTTP_X_FORWARDED_HOST" => "2.3.4.5,4.5.6.7"). forwarded_authority.must_be_nil req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_PROTO" => "ws", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_be_nil req("HTTP_FORWARDED"=>"proto=https", "HTTP_X_FORWARDED_SCHEME" => "http"). forwarded_scheme.must_be_nil req("HTTP_FORWARDED"=>"proto=https"). forwarded_scheme.must_be_nil ensure Rack::Request.forwarded_priority = default_priority Rack::Request.x_forwarded_proto_priority = default_proto_priority end end it "figure out the correct host with port" do req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org") req.host_with_port.must_equal "www2.example.org" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81") req.host_with_port.must_equal "localhost:81" req = make_request \ Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292") req.host_with_port.must_equal "example.org:9292" req = make_request \ Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org") req.host_with_port.must_equal "example.org" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292") req.host_with_port.must_equal "example.org:9292" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "[2001:db8:cafe::17]:47011") req.host_with_port.must_equal "[2001:db8:cafe::17]:47011" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "2001:db8:cafe::17") req.host_with_port.must_equal "[2001:db8:cafe::17]" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "SERVER_PORT" => "9393") req.host_with_port.must_equal "example.org" req = make_request \ Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_FORWARDED" => "host=example.com:9292", "SERVER_PORT" => "9393") req.host_with_port.must_equal "example.com:9292" end it "parse the query string" do request = make_request(Rack::MockRequest.env_for("/?foo=bar&quux=bla¬hing&empty=")) request.query_string.must_equal "foo=bar&quux=bla¬hing&empty=" request.GET.must_equal "foo" => "bar", "quux" => "bla", "nothing" => nil, "empty" => "" request.POST.must_be :empty? request.params.must_equal "foo" => "bar", "quux" => "bla", "nothing" => nil, "empty" => "" end it "handles invalid unicode in query string value" do request = make_request(Rack::MockRequest.env_for(qs = "/?foo=%81E")) request.query_string.must_equal "foo=%81E" request.GET.must_equal "foo" => "\x81E" request.POST.must_be :empty? request.params.must_equal "foo" => "\x81E" end it "handles invalid unicode in query string key" do request = make_request(Rack::MockRequest.env_for("/?foo%81E=1")) request.query_string.must_equal "foo%81E=1" request.GET.must_equal "foo\x81E" => "1" request.POST.must_be :empty? request.params.must_equal "foo\x81E" => "1" end it "not truncate query strings containing semi-colons #543 only in POST" do mr = Rack::MockRequest.env_for("/", "REQUEST_METHOD" => 'POST', :input => "foo=bar&quux=b;la") req = make_request mr req.query_string.must_equal "" req.GET.must_be :empty? req.POST.must_equal "foo" => "bar", "quux" => "b;la" req.params.must_equal req.GET.merge(req.POST) end it "should use the query_parser for query parsing" do c = Class.new(Rack::QueryParser::Params) do def initialize(*) super(){|h, k| h[k.to_s] if k.is_a?(Symbol)} end end parser = Rack::QueryParser.new(c, 100) c = Class.new(Rack::Request) do define_method(:query_parser) do parser end end req = c.new(Rack::MockRequest.env_for("/?foo=bar&quux=bla")) req.GET[:foo].must_equal "bar" req.GET[:quux].must_equal "bla" req.params[:foo].must_equal "bar" req.params[:quux].must_equal "bla" end it "does not use semi-colons as separators for query strings in GET" do req = make_request(Rack::MockRequest.env_for("/?foo=bar&quux=b;la;wun=duh")) req.query_string.must_equal "foo=bar&quux=b;la;wun=duh" req.GET.must_equal "foo" => "bar", "quux" => "b;la;wun=duh" req.POST.must_be :empty? req.params.must_equal "foo" => "bar", "quux" => "b;la;wun=duh" end it "limit the allowed parameter depth when parsing parameters" do env = Rack::MockRequest.env_for("/?a#{'[a]' * 40}=b") req = make_request(env) lambda { req.GET }.must_raise Rack::QueryParser::ParamsTooDeepError env = Rack::MockRequest.env_for("/?a#{'[a]' * 30}=b") req = make_request(env) params = req.GET 30.times { params = params['a'] } params['a'].must_equal 'b' old, Rack::Utils.param_depth_limit = Rack::Utils.param_depth_limit, 3 begin env = Rack::MockRequest.env_for("/?a[a][a]=b") req = make_request(env) req.GET['a']['a']['a'].must_equal 'b' env = Rack::MockRequest.env_for("/?a[a][a][a]=b") req = make_request(env) lambda { make_request(env).GET }.must_raise Rack::QueryParser::ParamsTooDeepError ensure Rack::Utils.param_depth_limit = old end end it "not unify GET and POST when calling params" do mr = Rack::MockRequest.env_for("/?foo=quux", "REQUEST_METHOD" => 'POST', :input => "foo=bar&quux=bla" ) req = make_request mr req.params req.GET.must_equal "foo" => "quux" req.POST.must_equal "foo" => "bar", "quux" => "bla" req.params.must_equal req.GET.merge(req.POST) end it "use the query_parser's params_class for multipart params" do c = Class.new(Rack::QueryParser::Params) do def initialize(*) super(){|h, k| h[k.to_s] if k.is_a?(Symbol)} end end parser = Rack::QueryParser.new(c, 100) c = Class.new(Rack::Request) do define_method(:query_parser) do parser end end mr = Rack::MockRequest.env_for("/?foo=quux", "REQUEST_METHOD" => 'POST', :input => "foo=bar&quux=bla" ) req = c.new mr req.params req.GET[:foo].must_equal "quux" req.POST[:foo].must_equal "bar" req.POST[:quux].must_equal "bla" req.params[:foo].must_equal "bar" req.params[:quux].must_equal "bla" end it "raise if input params has invalid %-encoding" do mr = Rack::MockRequest.env_for("/?foo=quux", "REQUEST_METHOD" => 'POST', :input => "a%=1" ) req = make_request mr lambda { req.POST }.must_raise(Rack::Utils::InvalidParameterError). message.must_equal "invalid %-encoding (a%)" end it "return empty POST data if rack.input is missing" do req = make_request({}) req.POST.must_be_empty end it "parse POST data when method is POST and no content-type given" do req = make_request \ Rack::MockRequest.env_for("/?foo=quux", "REQUEST_METHOD" => 'POST', :input => "foo=bar&quux=bla") req.content_type.must_be_nil req.media_type.must_be_nil req.query_string.must_equal "foo=quux" req.GET.must_equal "foo" => "quux" req.POST.must_equal "foo" => "bar", "quux" => "bla" req.params.must_equal "foo" => "bar", "quux" => "bla" end it "parse POST data with explicit content type regardless of method" do req = make_request \ Rack::MockRequest.env_for("/", "CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar', :input => "foo=bar&quux=bla") req.content_type.must_equal 'application/x-www-form-urlencoded;foo=bar' req.media_type.must_equal 'application/x-www-form-urlencoded' req.media_type_params['foo'].must_equal 'bar' req.POST.must_equal "foo" => "bar", "quux" => "bla" req.params.must_equal "foo" => "bar", "quux" => "bla" end it "not parse POST data when media type is not form-data" do req = make_request \ Rack::MockRequest.env_for("/?foo=quux", "REQUEST_METHOD" => 'POST', "CONTENT_TYPE" => 'text/plain;charset=utf-8', :input => "foo=bar&quux=bla") req.content_type.must_equal 'text/plain;charset=utf-8' req.media_type.must_equal 'text/plain' req.media_type_params['charset'].must_equal 'utf-8' req.content_charset.must_equal 'utf-8' post = req.POST post.must_be_empty req.POST.must_be_same_as post req.params.must_equal "foo" => "quux" req.body.read.must_equal "foo=bar&quux=bla" end it "parse POST data on PUT when media type is form-data" do req = make_request \ Rack::MockRequest.env_for("/?foo=quux", "REQUEST_METHOD" => 'PUT', "CONTENT_TYPE" => 'application/x-www-form-urlencoded', :input => "foo=bar&quux=bla") req.POST.must_equal "foo" => "bar", "quux" => "bla" end it "safely accepts POST requests with empty body" do mr = Rack::MockRequest.env_for("/", "REQUEST_METHOD" => "POST", "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => '0', :input => nil) req = make_request mr req.query_string.must_equal "" req.GET.must_be :empty? req.POST.must_be :empty? req.params.must_equal({}) end it "clean up Safari's ajax POST body" do req = make_request \ Rack::MockRequest.env_for("/", 'REQUEST_METHOD' => 'POST', :input => "foo=bar&quux=bla\0") req.POST.must_equal "foo" => "bar", "quux" => "bla" end it "return values for the keys in the order given from values_at" do req = make_request Rack::MockRequest.env_for("?foo=baz&wun=der&bar=ful") assert_output(nil, /deprecated/) do req.values_at('foo').must_equal ['baz'] req.values_at('foo', 'wun').must_equal ['baz', 'der'] req.values_at('bar', 'foo', 'wun').must_equal ['ful', 'baz', 'der'] end next if self.class == TestProxyRequest capture_warnings(req) do |warnings| req.values_at('foo').must_equal ['baz'] warnings.pop.must_equal ["Request#values_at is deprecated and will be removed in a future version of Rack. Please use request.params.values_at instead", { uplevel: 1 }] end end it "extract referrer correctly" do req = make_request \ Rack::MockRequest.env_for("/", "HTTP_REFERER" => "/some/path") req.referer.must_equal "/some/path" req = make_request \ Rack::MockRequest.env_for("/") req.referer.must_be_nil end it "extract user agent correctly" do req = make_request \ Rack::MockRequest.env_for("/", "HTTP_USER_AGENT" => "Mozilla/4.0 (compatible)") req.user_agent.must_equal "Mozilla/4.0 (compatible)" req = make_request \ Rack::MockRequest.env_for("/") req.user_agent.must_be_nil end it "treat missing content type as nil" do req = make_request \ Rack::MockRequest.env_for("/") req.content_type.must_be_nil end it "treat empty content type as nil" do req = make_request \ Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "") req.content_type.must_be_nil end it "return nil media type for empty content type" do req = make_request \ Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "") req.media_type.must_be_nil end deprecated "cache, but invalidates the cache" do req = make_request \ Rack::MockRequest.env_for("/?foo=quux", "CONTENT_TYPE" => "application/x-www-form-urlencoded", :input => "foo=bar&quux=bla") req.GET.must_equal "foo" => "quux" req.GET.must_equal "foo" => "quux" req.set_header("QUERY_STRING", "bla=foo") req.GET.must_equal "bla" => "foo" req.GET.must_equal "bla" => "foo" req.POST.must_equal "foo" => "bar", "quux" => "bla" req.POST.must_equal "foo" => "bar", "quux" => "bla" req.set_header("rack.input", StringIO.new("foo=bla&quux=bar")) req.POST.must_equal "foo" => "bla", "quux" => "bar" req.POST.must_equal "foo" => "bla", "quux" => "bar" end it "figure out if called via XHR" do req = make_request(Rack::MockRequest.env_for("")) req.wont_be :xhr? req = make_request \ Rack::MockRequest.env_for("", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest") req.must_be :xhr? end it "ssl detection" do request = make_request(Rack::MockRequest.env_for("/")) request.scheme.must_equal "http" request.wont_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'ws')) request.scheme.must_equal "ws" request.wont_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'ws')) request.scheme.must_equal "ws" request = make_request(Rack::MockRequest.env_for("/", 'HTTP_FORWARDED' => 'proto=https')) request.scheme.must_equal "https" request.must_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_FORWARDED' => 'proto=https, proto=http')) request.scheme.must_equal "http" request.wont_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_FORWARDED' => 'proto=http, proto=https')) request.scheme.must_equal "https" request.must_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTPS' => 'on')) request.scheme.must_equal "https" request.must_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'rack.url_scheme' => 'https')) request.scheme.must_equal "https" request.must_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'rack.url_scheme' => 'wss')) request.scheme.must_equal "wss" request.must_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8080')) request.scheme.must_equal "http" request.wont_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8443', 'HTTPS' => 'on')) request.scheme.must_equal "https" request.must_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8443', 'HTTP_X_FORWARDED_SSL' => 'on')) request.scheme.must_equal "https" request.must_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'https')) request.scheme.must_equal "https" request.must_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'wss')) request.scheme.must_equal "wss" request.must_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https')) request.scheme.must_equal "https" request.must_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https, http, http')) request.scheme.must_equal "http" request.wont_be :ssl? request = make_request(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'wss')) request.scheme.must_equal "wss" request.must_be :ssl? end it "prevents scheme abuse" do request = make_request(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'a.">')) request.scheme.must_equal 'http' end it "parse cookies" do req = make_request \ Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m") req.cookies.must_equal "foo" => "bar", "quux" => "h&m" req.delete_header("HTTP_COOKIE") req.cookies.must_equal({}) end it "always return the same hash object" do req = make_request \ Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m") hash = req.cookies req.env.delete("HTTP_COOKIE") req.cookies.must_equal hash req.env["HTTP_COOKIE"] = "zoo=m" req.cookies.must_equal hash end it "modify the cookies hash in place" do req = make_request(Rack::MockRequest.env_for("")) req.cookies.must_equal({}) req.cookies['foo'] = 'bar' req.cookies.must_equal 'foo' => 'bar' end it "not modify the params hash in place" do e = Rack::MockRequest.env_for("") req1 = make_request(e) if req1.delegate? skip "delegate requests don't cache params, so mutations have no impact" end req1.params.must_equal({}) req1.params['foo'] = 'bar' req1.params.must_equal 'foo' => 'bar' req2 = make_request(e) req2.params.must_equal({}) end it "modify params hash if param is in GET" do e = Rack::MockRequest.env_for("?foo=duh") req1 = make_request(e) req1.params.must_equal 'foo' => 'duh' req1.update_param 'foo', 'bar' req1.params.must_equal 'foo' => 'bar' req2 = make_request(e) req2.params.must_equal 'foo' => 'bar' end it "modify params hash if param is in POST" do e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => 'foo=duh') req1 = make_request(e) req1.params.must_equal 'foo' => 'duh' req1.update_param 'foo', 'bar' req1.params.must_equal 'foo' => 'bar' req2 = make_request(e) req2.params.must_equal 'foo' => 'bar' end it "modify params hash, even if param didn't exist before" do e = Rack::MockRequest.env_for("") req1 = make_request(e) req1.params.must_equal({}) req1.update_param 'foo', 'bar' req1.params.must_equal 'foo' => 'bar' req2 = make_request(e) req2.params.must_equal 'foo' => 'bar' end it "modify params hash by changing only GET" do e = Rack::MockRequest.env_for("?foo=duhget") req = make_request(e) req.GET.must_equal 'foo' => 'duhget' req.POST.must_equal({}) req.update_param 'foo', 'bar' req.GET.must_equal 'foo' => 'bar' req.POST.must_equal({}) end it "modify params hash by changing only POST" do e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => "foo=duhpost") req = make_request(e) req.GET.must_equal({}) req.POST.must_equal 'foo' => 'duhpost' req.update_param 'foo', 'bar' req.GET.must_equal({}) req.POST.must_equal 'foo' => 'bar' end it "modify params hash, even if param is defined in both POST and GET" do e = Rack::MockRequest.env_for("?foo=duhget", "REQUEST_METHOD" => 'POST', :input => "foo=duhpost") req1 = make_request(e) req1.GET.must_equal 'foo' => 'duhget' req1.POST.must_equal 'foo' => 'duhpost' req1.params.must_equal 'foo' => 'duhpost' req1.update_param 'foo', 'bar' req1.GET.must_equal 'foo' => 'bar' req1.POST.must_equal 'foo' => 'bar' req1.params.must_equal 'foo' => 'bar' req2 = make_request(e) req2.GET.must_equal 'foo' => 'bar' req2.POST.must_equal 'foo' => 'bar' req2.params.must_equal 'foo' => 'bar' req2.params.must_equal 'foo' => 'bar' end it "allow deleting from params hash if param is in GET" do e = Rack::MockRequest.env_for("?foo=bar") req1 = make_request(e) req1.params.must_equal 'foo' => 'bar' req1.delete_param('foo').must_equal 'bar' req1.params.must_equal({}) req2 = make_request(e) req2.params.must_equal({}) end it "allow deleting from params hash if param is in POST" do e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => 'foo=bar') req1 = make_request(e) req1.params.must_equal 'foo' => 'bar' req1.delete_param('foo').must_equal 'bar' req1.params.must_equal({}) req2 = make_request(e) req2.params.must_equal({}) end it "pass through non-uri escaped cookies as-is" do req = make_request Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=%") req.cookies["foo"].must_equal "%" end it "parse cookies according to RFC 2109" do req = make_request \ Rack::MockRequest.env_for('', 'HTTP_COOKIE' => 'foo=bar;foo=car') req.cookies.must_equal 'foo' => 'bar' end it 'parse cookies with quotes' do req = make_request Rack::MockRequest.env_for('', { 'HTTP_COOKIE' => '$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"; Part_Number="Rocket_Launcher_0001"; $Path="/acme"' }) req.cookies.must_equal({ '$Version' => '"1"', 'Customer' => '"WILE_E_COYOTE"', '$Path' => '"/acme"', 'Part_Number' => '"Rocket_Launcher_0001"', }) end it "provide setters" do req = make_request(e = Rack::MockRequest.env_for("")) req.script_name.must_equal "" req.script_name = "/foo" req.script_name.must_equal "/foo" e["SCRIPT_NAME"].must_equal "/foo" req.path_info.must_equal "/" req.path_info = "/foo" req.path_info.must_equal "/foo" e["PATH_INFO"].must_equal "/foo" end it "provide the original env" do req = make_request(e = Rack::MockRequest.env_for("")) req.env.must_equal e end it "restore the base URL" do make_request(Rack::MockRequest.env_for("")).base_url. must_equal "http://example.org" make_request(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).base_url. must_equal "http://example.org" end it "restore the URL" do make_request(Rack::MockRequest.env_for("")).url. must_equal "http://example.org/" make_request(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).url. must_equal "http://example.org/foo/" make_request(Rack::MockRequest.env_for("/foo")).url. must_equal "http://example.org/foo" make_request(Rack::MockRequest.env_for("?foo")).url. must_equal "http://example.org/?foo" make_request(Rack::MockRequest.env_for("http://example.org:8080/")).url. must_equal "http://example.org:8080/" make_request(Rack::MockRequest.env_for("https://example.org/")).url. must_equal "https://example.org/" make_request(Rack::MockRequest.env_for("coffee://example.org/")).url. must_equal "coffee://example.org/" make_request(Rack::MockRequest.env_for("coffee://example.org:443/")).url. must_equal "coffee://example.org:443/" make_request(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).url. must_equal "https://example.com:8080/foo?foo" end it "restore the full path" do make_request(Rack::MockRequest.env_for("")).fullpath. must_equal "/" make_request(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).fullpath. must_equal "/foo/" make_request(Rack::MockRequest.env_for("/foo")).fullpath. must_equal "/foo" make_request(Rack::MockRequest.env_for("?foo")).fullpath. must_equal "/?foo" make_request(Rack::MockRequest.env_for("http://example.org:8080/")).fullpath. must_equal "/" make_request(Rack::MockRequest.env_for("https://example.org/")).fullpath. must_equal "/" make_request(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).fullpath. must_equal "/foo?foo" end it "handle multiple media type parameters" do req = make_request \ Rack::MockRequest.env_for("/", "CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam;blong="boo";zump="zoo\"o";weird=lol"') req.wont_be :form_data? req.media_type_params.must_include 'foo' req.media_type_params['foo'].must_equal 'BAR' req.media_type_params.must_include 'baz' req.media_type_params['baz'].must_equal 'bizzle dizzle' req.media_type_params.wont_include 'BLING' req.media_type_params.must_include 'bling' req.media_type_params['bling'].must_equal 'bam' req.media_type_params['blong'].must_equal 'boo' req.media_type_params['zump'].must_equal 'zoo\"o' req.media_type_params['weird'].must_equal 'lol"' end it "returns the same error for invalid post inputs" do env = { 'REQUEST_METHOD' => 'POST', 'PATH_INFO' => '/foo', 'rack.input' => StringIO.new('invalid=bar&invalid[foo]=bar'), 'HTTP_CONTENT_TYPE' => "application/x-www-form-urlencoded", } 2.times do # The actual exception type here is unimportant - just that it fails. assert_raises(Rack::Utils::ParameterTypeError) do Rack::Request.new(env).POST end end end it "parse with junk before boundary" do # Adapted from RFC 1867. input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) req.POST.must_include "fileupload" req.POST.must_include "reply" req.must_be :form_data? req.content_length.must_equal input.size req.media_type.must_equal 'multipart/form-data' req.media_type_params.must_include 'boundary' req.media_type_params['boundary'].must_equal 'AaB03x' req.POST["reply"].must_equal "yes" f = req.POST["fileupload"] f.must_be_kind_of Hash f[:type].must_equal "image/jpeg" f[:filename].must_equal "dj.jpg" f.must_include :tempfile f[:tempfile].size.must_equal 76 end it "not infinite loop with a malformed HTTP request" do # Adapted from RFC 1867. input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda{req.POST}.must_raise EOFError end it "parse multipart form data" do # Adapted from RFC 1867. input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) req.POST.must_include "fileupload" req.POST.must_include "reply" req.must_be :form_data? req.content_length.must_equal input.size req.media_type.must_equal 'multipart/form-data' req.media_type_params.must_include 'boundary' req.media_type_params['boundary'].must_equal 'AaB03x' req.POST["reply"].must_equal "yes" f = req.POST["fileupload"] f.must_be_kind_of Hash f[:type].must_equal "image/jpeg" f[:filename].must_equal "dj.jpg" f.must_include :tempfile f[:tempfile].size.must_equal 76 req.env['rack.request.form_pairs'].must_equal [["reply", "yes"], ["fileupload", f]] end it "parse multipart delimiter-only boundary" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input ) req = make_request mr req.query_string.must_equal "" req.GET.must_be :empty? req.POST.must_be :empty? req.params.must_equal({}) end it "MultipartPartLimitError when request has too many multipart file parts if limit set" do begin data = 10000.times.map { "--AaB03x\r\ncontent-type: text/plain\r\ncontent-disposition: attachment; name=#{SecureRandom.hex(10)}; filename=#{SecureRandom.hex(10)}\r\n\r\ncontents\r\n" }.join("\r\n") data += "--AaB03x--\r" options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } request = make_request Rack::MockRequest.env_for("/", options) lambda { request.POST }.must_raise Rack::Multipart::MultipartPartLimitError end end it "MultipartPartLimitError when request has too many multipart total parts if limit set" do begin data = 10000.times.map { "--AaB03x\r\ncontent-type: text/plain\r\ncontent-disposition: attachment; name=#{SecureRandom.hex(10)}\r\n\r\ncontents\r\n" }.join("\r\n") data += "--AaB03x--\r" options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, :input => StringIO.new(data) } request = make_request Rack::MockRequest.env_for("/", options) lambda { request.POST }.must_raise Rack::Multipart::MultipartTotalPartLimitError end end it 'closes tempfiles it created in the case of too many created' do begin data = 10000.times.map { "--AaB03x\r\ncontent-type: text/plain\r\ncontent-disposition: attachment; name=#{SecureRandom.hex(10)}; filename=#{SecureRandom.hex(10)}\r\n\r\ncontents\r\n" }.join("\r\n") data += "--AaB03x--\r" files = [] options = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", "CONTENT_LENGTH" => data.length.to_s, Rack::RACK_MULTIPART_TEMPFILE_FACTORY => lambda { |filename, content_type| file = Tempfile.new(["RackMultipart", ::File.extname(filename)]) files << file file }, :input => StringIO.new(data) } request = make_request Rack::MockRequest.env_for("/", options) assert_raises(Rack::Multipart::MultipartPartLimitError) do request.POST end refute_predicate files, :empty? files.each { |f| assert_predicate f, :closed? } end end it "parse big multipart form data" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) req.POST["huge"][:tempfile].size.must_equal 32768 req.POST["mean"][:tempfile].size.must_equal 10 req.POST["mean"][:tempfile].read.must_equal "--AaB03xha" end it "record tempfiles from multipart form data in env[rack.tempfiles]" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) req = make_request(env) req.params env['rack.tempfiles'].size.must_equal 2 end it "detect invalid multipart form data" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda { req.POST }.must_raise EOFError input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda { req.POST }.must_raise EOFError input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda { req.POST }.must_raise EOFError end it "consistently raise EOFError on bad multipart form data" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) lambda { req.POST }.must_raise EOFError lambda { req.POST }.must_raise EOFError end it "correctly parse the part name from Content-Id header" do input = <\r content-transfer-encoding: 7bit\r \r foo\r --AaB03x--\r EOF req = make_request Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "multipart/related, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) req.params.keys.must_equal [""] end it "not try to interpret binary as utf8" do input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size, :input => input) req.POST["fileupload"][:tempfile].size.must_equal 4 end it "use form_hash when form_input is a Tempfile" do input = "{foo: 'bar'}" rack_input = Tempfile.new("rackspec") rack_input.write(input) rack_input.rewind form_hash = {} req = make_request Rack::MockRequest.env_for( "/", "rack.request.form_hash" => form_hash, "rack.request.form_input" => rack_input, :input => rack_input ) req.POST.must_be_same_as form_hash end it "conform to the Rack spec" do app = lambda { |env| content = make_request(env).POST["file"].inspect size = content.bytesize [200, { "content-type" => "text/html", "content-length" => size.to_s }, [content]] } input = < "multipart/form-data, boundary=AaB03x", "CONTENT_LENGTH" => input.size.to_s, "rack.input" => StringIO.new(input) res.must_be :ok? end it "parse Accept-Encoding correctly" do parser = lambda do |x| make_request(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => x)).accept_encoding end parser.call(nil).must_equal [] parser.call("compress, gzip").must_equal [["compress", 1.0], ["gzip", 1.0]] parser.call("").must_equal [] parser.call("*").must_equal [["*", 1.0]] parser.call("compress;q=0.5, gzip;q=1.0").must_equal [["compress", 0.5], ["gzip", 1.0]] parser.call("gzip;q=1.0, identity; q=0.5, *;q=0").must_equal [["gzip", 1.0], ["identity", 0.5], ["*", 0] ] parser.call("gzip ; q=0.9").must_equal [["gzip", 0.9]] parser.call("gzip ; deflate").must_equal [["gzip", 1.0]] parser.call(", ").must_equal [] parser.call(", gzip").must_equal [["gzip", 1.0]] parser.call("gzip, ").must_equal [["gzip", 1.0]] end it "parse Accept-Language correctly" do parser = lambda do |x| make_request(Rack::MockRequest.env_for("", "HTTP_ACCEPT_LANGUAGE" => x)).accept_language end parser.call(nil).must_equal [] parser.call("fr, en").must_equal [["fr", 1.0], ["en", 1.0]] parser.call("").must_equal [] parser.call("*").must_equal [["*", 1.0]] parser.call("fr;q=0.5, en;q=1.0").must_equal [["fr", 0.5], ["en", 1.0]] parser.call("fr;q=1.0, en; q=0.5, *;q=0").must_equal [["fr", 1.0], ["en", 0.5], ["*", 0] ] parser.call("fr ; q=0.9").must_equal [["fr", 0.9]] parser.call("fr").must_equal [["fr", 1.0]] parser.call(", ").must_equal [] parser.call(", en").must_equal [["en", 1.0]] parser.call("en, ").must_equal [["en", 1.0]] end def ip_app lambda { |env| request = make_request(env) response = Rack::Response.new response.write request.ip response.finish } end it 'provide ip information' do mock = Rack::MockRequest.new(Rack::Lint.new(ip_app)) res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4' res.body.must_equal '1.2.3.4' res = mock.get '/', 'REMOTE_ADDR' => 'fe80::202:b3ff:fe1e:8329' res.body.must_equal 'fe80::202:b3ff:fe1e:8329' res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6' res.body.must_equal '3.4.5.6' res = mock.get '/', 'REMOTE_ADDR' => '127.0.0.1' res.body.must_equal '127.0.0.1' res = mock.get '/', 'REMOTE_ADDR' => '127.0.0.1,127.0.0.1' res.body.must_equal '127.0.0.1' end it 'deals with proxies' do mock = Rack::MockRequest.new(Rack::Lint.new(ip_app)) res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_FORWARDED' => 'for=3.4.5.6' res.body.must_equal '1.2.3.4' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6', 'HTTP_FORWARDED' => 'for=5.6.7.8' res.body.must_equal '5.6.7.8' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6', 'HTTP_FORWARDED' => 'for=5.6.7.8, for=7.8.9.0' res.body.must_equal '7.8.9.0' res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' res.body.must_equal '1.2.3.4' res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => 'unknown' res.body.must_equal '1.2.3.4' res = mock.get '/', 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' res.body.must_equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,3.4.5.6' res.body.must_equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6' res.body.must_equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6' res.body.must_equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6' res.body.must_equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '127.0.0.1, 3.4.5.6' res.body.must_equal '3.4.5.6' # IPv6 format with optional port: "[2001:db8:cafe::17]:47011" res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '[2001:db8:cafe::17]:47011' res.body.must_equal '2001:db8:cafe::17' res = mock.get '/', 'HTTP_FORWARDED' => 'for="[2001:db8:cafe::17]:47011"' res.body.must_equal '2001:db8:cafe::17' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '1.2.3.4, [2001:db8:cafe::17]:47011' res.body.must_equal '2001:db8:cafe::17' # IPv4 format with optional port: "192.0.2.43:47011" res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '192.0.2.43:47011' res.body.must_equal '192.0.2.43' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '1.2.3.4, 192.0.2.43:47011' res.body.must_equal '192.0.2.43' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1' res.body.must_equal 'unknown' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'other,unknown,192.168.0.1' res.body.must_equal 'unknown' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,localhost,192.168.0.1' res.body.must_equal 'unknown' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4' res.body.must_equal '3.4.5.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '::1,2620:0:1c00:0:812c:9583:754b:ca11' res.body.must_equal '2620:0:1c00:0:812c:9583:754b:ca11' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,::1' res.body.must_equal '2620:0:1c00:0:812c:9583:754b:ca11' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'fd5b:982e:9130:247f:0000:0000:0000:0000,2620:0:1c00:0:812c:9583:754b:ca11' res.body.must_equal '2620:0:1c00:0:812c:9583:754b:ca11' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,fd5b:982e:9130:247f:0000:0000:0000:0000' res.body.must_equal '2620:0:1c00:0:812c:9583:754b:ca11' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '1.1.1.1, 127.0.0.1', 'HTTP_CLIENT_IP' => '1.1.1.1' res.body.must_equal '1.1.1.1' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9' res.body.must_equal '9.9.9.9' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, fe80::202:b3ff:fe1e:8329' res.body.must_equal 'fe80::202:b3ff:fe1e:8329' # Unix Sockets res = mock.get '/', 'REMOTE_ADDR' => 'unix', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' res.body.must_equal '3.4.5.6' res = mock.get '/', 'REMOTE_ADDR' => 'unix:/tmp/foo', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' res.body.must_equal '3.4.5.6' end it "not allow IP spoofing via Client-IP and X-Forwarded-For headers" do mock = Rack::MockRequest.new(Rack::Lint.new(ip_app)) # IP Spoofing attempt: # Client sends X-Forwarded-For: 6.6.6.6 # Client-IP: 6.6.6.6 # Load balancer adds X-Forwarded-For: 2.2.2.3, 192.168.0.7 # App receives: X-Forwarded-For: 6.6.6.6 # X-Forwarded-For: 2.2.2.3, 192.168.0.7 # Client-IP: 6.6.6.6 # Rack env: HTTP_X_FORWARDED_FOR: '6.6.6.6, 2.2.2.3, 192.168.0.7' # HTTP_CLIENT_IP: '6.6.6.6' res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '6.6.6.6, 2.2.2.3, 192.168.0.7', 'HTTP_CLIENT_IP' => '6.6.6.6' res.body.must_equal '2.2.2.3' end it "preserves ip for trusted proxy chain" do mock = Rack::MockRequest.new(Rack::Lint.new(ip_app)) res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '192.168.0.11, 192.168.0.7', 'HTTP_CLIENT_IP' => '127.0.0.1' res.body.must_equal '192.168.0.11' end it "uses a custom trusted proxy filter" do old_ip = Rack::Request.ip_filter Rack::Request.ip_filter = lambda { |ip| ip == 'foo' } req = make_request(Rack::MockRequest.env_for("/")) assert req.trusted_proxy?('foo') Rack::Request.ip_filter = old_ip end it "regards local addresses as proxies" do req = make_request(Rack::MockRequest.env_for("/")) req.trusted_proxy?('127.0.0.1').must_equal true req.trusted_proxy?('127.000.000.001').must_equal true req.trusted_proxy?('127.0.0.6').must_equal true req.trusted_proxy?('127.0.0.30').must_equal true req.trusted_proxy?('10.0.0.1').must_equal true req.trusted_proxy?('10.000.000.001').must_equal true req.trusted_proxy?('172.16.0.1').must_equal true req.trusted_proxy?('172.20.0.1').must_equal true req.trusted_proxy?('172.30.0.1').must_equal true req.trusted_proxy?('172.31.0.1').must_equal true req.trusted_proxy?('172.31.000.001').must_equal true req.trusted_proxy?('192.168.0.1').must_equal true req.trusted_proxy?('192.168.000.001').must_equal true req.trusted_proxy?('::1').must_equal true req.trusted_proxy?('fd00::').must_equal true req.trusted_proxy?('FD00::').must_equal true req.trusted_proxy?('localhost').must_equal true req.trusted_proxy?('unix').must_equal true req.trusted_proxy?('unix:/tmp/sock').must_equal true req.trusted_proxy?("unix.example.org").must_equal false req.trusted_proxy?("example.org\n127.0.0.1").must_equal false req.trusted_proxy?("127.0.0.1\nexample.org").must_equal false req.trusted_proxy?("127.256.0.1").must_equal false req.trusted_proxy?("127.0.256.1").must_equal false req.trusted_proxy?("127.0.0.256").must_equal false req.trusted_proxy?('127.0.0.300').must_equal false req.trusted_proxy?("10.256.0.1").must_equal false req.trusted_proxy?("10.0.256.1").must_equal false req.trusted_proxy?("10.0.0.256").must_equal false req.trusted_proxy?("11.0.0.1").must_equal false req.trusted_proxy?("11.000.000.001").must_equal false req.trusted_proxy?("172.15.0.1").must_equal false req.trusted_proxy?("172.32.0.1").must_equal false req.trusted_proxy?("172.16.256.1").must_equal false req.trusted_proxy?("172.16.0.256").must_equal false req.trusted_proxy?("2001:470:1f0b:18f8::1").must_equal false end it "sets the default session to an empty hash" do req = make_request(Rack::MockRequest.env_for("http://example.com:8080/")) session = req.session assert_equal Hash.new, session req.env['rack.session'].must_be_same_as session end it "sets the default session options to an empty hash" do req = make_request(Rack::MockRequest.env_for("http://example.com:8080/")) session_options = req.session_options assert_equal Hash.new, session_options req.env['rack.session.options'].must_be_same_as session_options assert_equal Hash.new, req.session_options end class MyRequest < Rack::Request def params { foo: "bar" } end end it "allow subclass request to be instantiated after parent request" do env = Rack::MockRequest.env_for("/?foo=bar") req1 = make_request(env) req1.GET.must_equal "foo" => "bar" req1.params.must_equal "foo" => "bar" req2 = MyRequest.new(env) req2.GET.must_equal "foo" => "bar" req2.params.must_equal foo: "bar" end it "allow parent request to be instantiated after subclass request" do env = Rack::MockRequest.env_for("/?foo=bar") req1 = MyRequest.new(env) req1.GET.must_equal "foo" => "bar" req1.params.must_equal foo: "bar" req2 = make_request(env) req2.GET.must_equal "foo" => "bar" req2.params.must_equal "foo" => "bar" end it "raise TypeError every time if request parameters are broken" do broken_query = Rack::MockRequest.env_for("/?foo%5B%5D=0&foo%5Bbar%5D=1") req = make_request(broken_query) lambda{req.GET}.must_raise TypeError lambda{req.params}.must_raise TypeError end (0x20...0x7E).collect { |a| b = a.chr c = CGI.escape(b) it "not strip '#{a}' => '#{c}' => '#{b}' escaped character from parameters when accessed as string" do url = "/?foo=#{c}bar#{c}" env = Rack::MockRequest.env_for(url) req2 = make_request(env) req2.GET.must_equal "foo" => "#{b}bar#{b}" req2.params.must_equal "foo" => "#{b}bar#{b}" end } (24..27).each do |exp| length = 2**exp it "handles ASCII NUL input of #{length} bytes" do mr = Rack::MockRequest.env_for("/", "REQUEST_METHOD" => 'POST', :input => "\0"*length) req = make_request mr req.query_string.must_equal "" req.GET.must_be :empty? keys = req.POST.keys keys.length.must_equal 1 keys.first.length.must_equal(length-1) keys.first.must_equal("\0"*(length-1)) end end it "Env sets @env on initialization" do c = Class.new do include Rack::Request::Env end h = {} c.new(h).env.must_be_same_as h end class NonDelegate < Rack::Request def delegate?; false; end end def make_request(env) NonDelegate.new env end class TestProxyRequest < RackRequestTest class DelegateRequest include Rack::Request::Helpers extend Forwardable def_delegators :@req, :env, :has_header?, :get_header, :fetch_header, :each_header, :set_header, :add_header, :delete_header def_delegators :@req, :[], :[]=, :values_at def initialize(req) @req = req end def delegate?; true; end end def make_request(env) DelegateRequest.new super(env) end end end rack-3.1.12/test/spec_response.rb000066400000000000000000000602771476365375000167260ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/response' end describe Rack::Response do it 'has standard constructor' do headers = { "header" => "value" } body = ["body"] response = Rack::Response[200, headers, body] response.status.must_equal 200 response.headers.must_equal headers response.body.must_equal body end it 'has cache-control methods' do response = Rack::Response.new cc = 'foo' response.cache_control = cc assert_equal cc, response.cache_control assert_equal cc, response.to_a[1]['cache-control'] end it 'has an etag method' do response = Rack::Response.new etag = 'foo' response.etag = etag assert_equal etag, response.etag assert_equal etag, response.to_a[1]['etag'] end it 'has a content-type method' do response = Rack::Response.new content_type = 'foo' response.content_type = content_type assert_equal content_type, response.content_type assert_equal content_type, response.to_a[1]['content-type'] end it "have sensible default values" do response = Rack::Response.new status, header, body = response.finish status.must_equal 200 header.must_equal({}) response.each { |part| part.must_equal "" } response = Rack::Response.new status, header, body = *response status.must_equal 200 header.must_equal({}) body.each { |part| part.must_equal "" } end it "can be written to inside finish block and it does not generate a content-length header" do response = Rack::Response.new('foo') response.write "bar" _, h, body = response.finish do response.write "baz" end parts = [] body.each { |part| parts << part } parts.must_equal ["foo", "bar", "baz"] h['content-length'].must_be_nil end it "#write calls #<< on non-iterable body" do content = [] body = proc{|x| content << x} body.singleton_class.class_eval{alias << call} response = Rack::Response.new(body) response.write "bar" content.must_equal ["bar"] end it "can set and read headers" do response = Rack::Response.new response["content-type"].must_be_nil response["content-type"] = "text/plain" response["content-type"].must_equal "text/plain" end it "doesn't mutate given headers" do headers = {}.freeze response = Rack::Response.new([], 200, headers) response.headers["content-type"] = "text/plain" response.headers["content-type"].must_equal "text/plain" headers.wont_include("content-type") end it "can override the initial content-type with a different case" do response = Rack::Response.new("", 200, "content-type" => "text/plain") response["content-type"].must_equal "text/plain" end it "can get and set set-cookie header" do response = Rack::Response.new response.set_cookie_header.must_be_nil response.set_cookie_header = 'v=1;' response.set_cookie_header.must_equal 'v=1;' response.headers['set-cookie'].must_equal 'v=1;' end it "can set cookies" do response = Rack::Response.new response.set_cookie "foo", "bar" response["set-cookie"].must_equal "foo=bar" response.set_cookie "foo2", "bar2" response["set-cookie"].must_equal ["foo=bar", "foo2=bar2"] response.set_cookie "foo3", "bar3" response["set-cookie"].must_equal ["foo=bar", "foo2=bar2", "foo3=bar3"] end it "can set cookies with the same name for multiple domains" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", domain: "sample.example.com" } response.set_cookie "foo", { value: "bar", domain: ".example.com" } response["set-cookie"].must_equal ["foo=bar; domain=sample.example.com", "foo=bar; domain=.example.com"] end it "formats the Cookie expiration date accordingly to RFC 6265" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", expires: Time.now + 10 } response["set-cookie"].must_match( /expires=..., \d\d ... \d\d\d\d \d\d:\d\d:\d\d .../) end it "can set secure cookies" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", secure: true } response["set-cookie"].must_equal "foo=bar; secure" end it "can set http only cookies" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", httponly: true } response["set-cookie"].must_equal "foo=bar; httponly" end it "can set http only cookies with :http_only" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", http_only: true } response["set-cookie"].must_equal "foo=bar; httponly" end it "can set prefers :httponly for http only cookie setting when :httponly and :http_only provided" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", httponly: false, http_only: true } response["set-cookie"].must_equal "foo=bar" end it "can set same site cookies with symbol value :none" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: :none } response["set-cookie"].must_equal "foo=bar; samesite=none" end it "can set same site cookies with symbol value :None" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: :None } response["set-cookie"].must_equal "foo=bar; samesite=none" end it "can set same site cookies with string value 'None'" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: "None" } response["set-cookie"].must_equal "foo=bar; samesite=none" end it "can set same site cookies with symbol value :lax" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: :lax } response["set-cookie"].must_equal "foo=bar; samesite=lax" end it "can set same site cookies with symbol value :Lax" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: :lax } response["set-cookie"].must_equal "foo=bar; samesite=lax" end it "can set same site cookies with string value 'Lax'" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: "Lax" } response["set-cookie"].must_equal "foo=bar; samesite=lax" end it "can set same site cookies with boolean value true" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: true } response["set-cookie"].must_equal "foo=bar; samesite=strict" end it "can set same site cookies with symbol value :strict" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: :strict } response["set-cookie"].must_equal "foo=bar; samesite=strict" end it "can set same site cookies with symbol value :Strict" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: :Strict } response["set-cookie"].must_equal "foo=bar; samesite=strict" end it "can set same site cookies with string value 'Strict'" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: "Strict" } response["set-cookie"].must_equal "foo=bar; samesite=strict" end it "validates the same site option value" do response = Rack::Response.new lambda { response.set_cookie "foo", { value: "bar", same_site: "Foo" } }.must_raise(ArgumentError). message.must_match(/Invalid :same_site value: "Foo"/) end it "can set same site cookies with symbol value" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: :Strict } response["set-cookie"].must_equal "foo=bar; samesite=strict" end [ nil, false ].each do |non_truthy| it "omits same site attribute given a #{non_truthy.inspect} value" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", same_site: non_truthy } response["set-cookie"].must_equal "foo=bar" end end it "can delete cookies" do response = Rack::Response.new response.set_cookie "foo", "bar" response.set_cookie "foo2", "bar2" response.delete_cookie "foo" response["set-cookie"].must_equal [ "foo=bar", "foo2=bar2", "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" ] end it "can delete cookies with the same name from multiple domains" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", domain: "sample.example.com" } response.set_cookie "foo", { value: "bar", domain: ".example.com" } response["set-cookie"].must_equal [ "foo=bar; domain=sample.example.com", "foo=bar; domain=.example.com" ] response.delete_cookie "foo", domain: ".example.com" response["set-cookie"].must_equal [ "foo=bar; domain=sample.example.com", "foo=bar; domain=.example.com", "foo=; domain=.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" ] response.delete_cookie "foo", domain: "sample.example.com" response["set-cookie"].must_equal [ "foo=bar; domain=sample.example.com", "foo=bar; domain=.example.com", "foo=; domain=.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT", "foo=; domain=sample.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" ] end it "only deletes cookies for the domain specified" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", domain: "example.com.example.com" } response.set_cookie "foo", { value: "bar", domain: "example.com" } response["set-cookie"].must_equal [ "foo=bar; domain=example.com.example.com", "foo=bar; domain=example.com" ] response.delete_cookie "foo", { domain: "example.com" } response["set-cookie"].must_equal [ "foo=bar; domain=example.com.example.com", "foo=bar; domain=example.com", "foo=; domain=example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" ] response.delete_cookie "foo", { domain: "example.com.example.com" } response["set-cookie"].must_equal [ "foo=bar; domain=example.com.example.com", "foo=bar; domain=example.com", "foo=; domain=example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT", "foo=; domain=example.com.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" ] end it "can delete cookies with the same name with different paths" do response = Rack::Response.new response.set_cookie "foo", { value: "bar", path: "/" } response.set_cookie "foo", { value: "bar", path: "/path" } response["set-cookie"].must_equal [ "foo=bar; path=/", "foo=bar; path=/path" ] response.delete_cookie "foo", path: "/path" response["set-cookie"].must_equal [ "foo=bar; path=/", "foo=bar; path=/path", "foo=; path=/path; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" ] end it "only delete cookies with the path specified" do response = Rack::Response.new response.set_cookie "foo", value: "bar", path: "/a/b" response["set-cookie"].must_equal( "foo=bar; path=/a/b" ) response.delete_cookie "foo", path: "/a" response["set-cookie"].must_equal [ "foo=bar; path=/a/b", "foo=; path=/a; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" ] end it "only delete cookies with the domain and path specified" do response = Rack::Response.new response.delete_cookie "foo", path: "/a", domain: "example.com" response["set-cookie"].must_equal( "foo=; domain=example.com; path=/a; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT", ) response.delete_cookie "foo", path: "/a/b", domain: "example.com" response["set-cookie"].must_equal [ "foo=; domain=example.com; path=/a; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT", "foo=; domain=example.com; path=/a/b; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT", ] end it "can do redirects" do response = Rack::Response.new response.redirect "/foo" status, header = response.finish status.must_equal 302 header["location"].must_equal "/foo" response = Rack::Response.new response.redirect "/foo", 307 status, = response.finish status.must_equal 307 end it "has a useful constructor" do r = Rack::Response.new("foo") body = r.finish[2] str = "".dup; body.each { |part| str << part } str.must_equal "foo" r = Rack::Response.new(["foo", "bar"]) body = r.finish[2] str = "".dup; body.each { |part| str << part } str.must_equal "foobar" object_with_each = Object.new def object_with_each.each yield "foo" yield "bar" end r = Rack::Response.new(object_with_each) r.write "foo" body = r.finish[2] str = "".dup; body.each { |part| str << part } str.must_equal "foobarfoo" r = Rack::Response.new([], 500) r.status.must_equal 500 r = Rack::Response.new([], "200 OK") r.status.must_equal 200 end it "has a constructor that can take a block" do r = Rack::Response.new { |res| res.status = 404 res.write "foo" } status, _, body = r.finish str = "".dup; body.each { |part| str << part } str.must_equal "foo" status.must_equal 404 end it "correctly updates content-length when writing when initialized without body" do r = Rack::Response.new r.write('foo') r.write('bar') r.write('baz') _, header, body = r.finish str = "".dup; body.each { |part| str << part } str.must_equal "foobarbaz" header['content-length'].must_equal '9' end it "correctly updates content-length when writing when initialized with Array body" do r = Rack::Response.new(["foo"]) r.write('bar') r.write('baz') _, header, body = r.finish str = "".dup; body.each { |part| str << part } str.must_equal "foobarbaz" header['content-length'].must_equal '9' end it "correctly updates content-length when writing when initialized with String body" do r = Rack::Response.new("foo") r.write('bar') r.write('baz') _, header, body = r.finish str = "".dup; body.each { |part| str << part } str.must_equal "foobarbaz" header['content-length'].must_equal '9' end it "correctly updates content-length when writing when initialized with object body that responds to #each" do obj = Object.new def obj.each yield 'foo' yield 'bar' end r = Rack::Response.new(obj) r.write('baz') r.write('baz') _, header, body = r.finish str = "".dup; body.each { |part| str << part } str.must_equal "foobarbazbaz" header['content-length'].must_equal '12' end it "doesn't return invalid responses" do r = Rack::Response.new(["foo", "bar"], 204) _, header, body = r.finish str = "".dup; body.each { |part| str << part } str.must_be :empty? header["content-type"].must_be_nil header['content-length'].must_be_nil lambda { Rack::Response.new(Object.new).each{} }.must_raise(NoMethodError). message.must_match(/undefined method .each. for/) end it "knows if it's empty" do r = Rack::Response.new r.must_be :empty? r.write "foo" r.wont_be :empty? r = Rack::Response.new r.must_be :empty? r.finish r.must_be :empty? r = Rack::Response.new r.must_be :empty? r.finish { } r.wont_be :empty? end it "provide access to the HTTP status" do res = Rack::Response.new res.status = 200 res.must_be :successful? res.must_be :ok? res.status = 201 res.must_be :successful? res.must_be :created? res.status = 202 res.must_be :successful? res.must_be :accepted? res.status = 204 res.must_be :successful? res.must_be :no_content? res.status = 301 res.must_be :redirect? res.must_be :moved_permanently? res.status = 302 res.must_be :redirect? res.status = 303 res.must_be :redirect? res.status = 307 res.must_be :redirect? res.status = 308 res.must_be :redirect? res.status = 400 res.wont_be :successful? res.must_be :client_error? res.must_be :bad_request? res.status = 401 res.wont_be :successful? res.must_be :client_error? res.must_be :unauthorized? res.status = 404 res.wont_be :successful? res.must_be :client_error? res.must_be :not_found? res.status = 405 res.wont_be :successful? res.must_be :client_error? res.must_be :method_not_allowed? res.status = 406 res.wont_be :successful? res.must_be :client_error? res.must_be :not_acceptable? res.status = 408 res.wont_be :successful? res.must_be :client_error? res.must_be :request_timeout? res.status = 412 res.wont_be :successful? res.must_be :client_error? res.must_be :precondition_failed? res.status = 422 res.wont_be :successful? res.must_be :client_error? res.must_be :unprocessable? res.status = 501 res.wont_be :successful? res.must_be :server_error? end it "provide access to the HTTP headers" do res = Rack::Response.new res["content-type"] = "text/yaml; charset=UTF-8" res.must_include "content-type" res.headers["content-type"].must_equal "text/yaml; charset=UTF-8" res["content-type"].must_equal "text/yaml; charset=UTF-8" res.content_type.must_equal "text/yaml; charset=UTF-8" res.media_type.must_equal "text/yaml" res.media_type_params.must_equal "charset" => "UTF-8" res.content_length.must_be_nil res.location.must_be_nil end it "does not add or change content-length when #finish()ing" do res = Rack::Response.new res.status = 200 res.finish res.headers["content-length"].must_be_nil res = Rack::Response.new res.status = 200 res.headers["content-length"] = "10" res.finish # We don't overwrite the content-length if it's already set - e.g. HEAD response may not have a body... res.headers["content-length"].must_equal "10" end it "updates length when body appended to using #write" do res = Rack::Response.new res.status = 200 res.length.must_be_nil res.write "Hi" res.length.must_equal 2 res.write " there" res.length.must_equal 8 res.finish res.headers["content-length"].must_equal "8" end it "does not wrap body" do body = Object.new res = Rack::Response.new(body) # It was passed through unchanged: res.finish.last.must_equal body end it "does wraps body when using #write" do body = ["Foo"] res = Rack::Response.new(body) # Write something using the response object: res.write("Bar") # The original body was not modified: body.must_equal ["Foo"] # But a new buffered body was created: res.finish.last.must_equal ["Foo", "Bar"] end it "handles string reuse in existing body when calling #write" do body_class = Class.new do def initialize(file) @file = file end def each buffer = String.new while @file.read(5, buffer) yield(buffer) end end end body = body_class.new(StringIO.new('Large large file content')) res = Rack::Response.new(body) res.write(" written") res.finish.last.must_equal ["Large", " larg", "e fil", "e con", "tent", " written"] end it "calls close on #body" do res = Rack::Response.new res.body = StringIO.new res.close res.body.must_be :closed? end it "calls close on #body when 204 or 304" do res = Rack::Response.new res.body = StringIO.new res.finish res.body.wont_be :closed? res.status = 204 _, _, b = res.finish res.body.must_be :closed? b.wont_equal res.body res.body = StringIO.new res.status = 304 _, _, b = res.finish res.body.must_be :closed? b.wont_equal res.body end it "doesn't call close on #body when 205" do res = Rack::Response.new res.body = StringIO.new res.status = 205 res.finish res.body.wont_be :closed? end it "doesn't clear #body when 101 and streaming" do res = Rack::Response.new streaming_body = proc{|stream| stream.close} res.body = streaming_body res.status = 101 res.finish res.body.must_equal streaming_body end it "flatten doesn't cause infinite loop" do # https://github.com/rack/rack/issues/419 res = Rack::Response.new("Hello World") res.finish.flatten.must_be_kind_of(Array) end it "should specify not to cache content" do response = Rack::Response.new response.cache!(1000) response.do_not_cache! expect(response['cache-control']).must_equal "no-cache, must-revalidate" expires_header = Time.parse(response['expires']) expect(expires_header).must_be :<=, Time.now end it "should not cache content if calling cache! after do_not_cache!" do response = Rack::Response.new response.do_not_cache! response.cache!(1000) expect(response['cache-control']).must_equal "no-cache, must-revalidate" expires_header = Time.parse(response['expires']) expect(expires_header).must_be :<=, Time.now end it "should specify to cache content" do response = Rack::Response.new duration = 120 expires = Time.now + 100 # At least this far into the future response.cache!(duration) expect(response['cache-control']).must_equal "public, max-age=120" expires_header = Time.parse(response['expires']) expect(expires_header).must_be :>=, expires end end describe Rack::Response, 'headers' do before do @response = Rack::Response.new([], 200, { 'foo' => '1' }) end it 'has_header?' do lambda { @response.has_header? nil }.must_raise ArgumentError @response.has_header?('foo').must_equal true end it 'get_header' do lambda { @response.get_header nil }.must_raise ArgumentError @response.get_header('foo').must_equal '1' end it 'set_header' do lambda { @response.set_header nil, '1' }.must_raise ArgumentError @response.set_header('foo', '2').must_equal '2' @response.has_header?('foo').must_equal true @response.get_header('foo').must_equal('2') @response.set_header('foo', nil).must_be_nil @response.get_header('foo').must_be_nil end it 'add_header' do lambda { @response.add_header nil, '1' }.must_raise ArgumentError # Add a value to an existing header @response.add_header('foo', '2').must_equal ["1", "2"] @response.get_header('foo').must_equal ["1", "2"] # Add nil to an existing header @response.add_header('foo', nil).must_equal ["1", "2"] @response.get_header('foo').must_equal ["1", "2"] # Add nil to a nonexistent header @response.add_header('bar', nil).must_be_nil @response.has_header?('bar').must_equal false @response.get_header('bar').must_be_nil # Add a value to a nonexistent header @response.add_header('bar', '1').must_equal '1' @response.has_header?('bar').must_equal true @response.get_header('bar').must_equal '1' end it 'delete_header' do lambda { @response.delete_header nil }.must_raise ArgumentError @response.delete_header('foo').must_equal '1' @response.has_header?('foo').must_equal false @response.delete_header('foo').must_be_nil @response.has_header?('foo').must_equal false @response.set_header('foo', 1) @response.delete_header('foo').must_equal 1 @response.has_header?('foo').must_equal false end end describe Rack::Response::Raw do before do @response = Rack::Response::Raw.new(200, { 'foo' => '1' }) end it 'has_header?' do @response.has_header?('foo').must_equal true @response.has_header?(nil).must_equal false end it 'get_header' do @response.get_header('foo').must_equal '1' @response.get_header(nil).must_be_nil end it 'set_header' do @response.set_header('foo', '2').must_equal '2' @response.has_header?('foo').must_equal true @response.get_header('foo').must_equal('2') @response.set_header(nil, '1').must_equal '1' @response.get_header(nil).must_equal '1' @response.set_header('foo', nil).must_be_nil @response.get_header('foo').must_be_nil end it 'delete_header' do @response.delete_header('foo').must_equal '1' @response.has_header?('foo').must_equal false @response.delete_header('foo').must_be_nil @response.has_header?('foo').must_equal false @response.set_header('foo', 1) @response.delete_header('foo').must_equal 1 @response.has_header?('foo').must_equal false end end rack-3.1.12/test/spec_rewindable_input.rb000066400000000000000000000101741476365375000204120ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/rewindable_input' end module RewindableTest extend Minitest::Spec::DSL def setup @rio = Rack::RewindableInput.new(@io) end it "be able to handle to read()" do @rio.read.must_equal "hello world" end it "be able to handle to read(nil)" do @rio.read(nil).must_equal "hello world" end it "be able to handle to read(length)" do @rio.read(1).must_equal "h" end it "be able to handle to read(length, buffer)" do buffer = "".dup result = @rio.read(1, buffer) result.must_equal "h" result.object_id.must_equal buffer.object_id end it "be able to handle to read(nil, buffer)" do buffer = "".dup result = @rio.read(nil, buffer) result.must_equal "hello world" result.object_id.must_equal buffer.object_id end it "rewind to the beginning when #rewind is called" do @rio.rewind @rio.read(1).must_equal 'h' @rio.rewind @rio.read.must_equal "hello world" end it "be able to handle gets" do @rio.gets.must_equal "hello world" @rio.rewind @rio.gets.must_equal "hello world" end it "be able to handle size" do @rio.size.must_equal "hello world".size @rio.size.must_equal "hello world".size @rio.rewind @rio.gets.must_equal "hello world" end it "be able to handle each" do array = [] @rio.each do |data| array << data end array.must_equal ["hello world"] @rio.rewind array = [] @rio.each do |data| array << data end array.must_equal ["hello world"] end it "not buffer into a Tempfile if no data has been read yet" do @rio.instance_variable_get(:@rewindable_io).must_be_nil end it "buffer into a Tempfile when data has been consumed for the first time" do @rio.read(1) tempfile = @rio.instance_variable_get(:@rewindable_io) tempfile.wont_be :nil? @rio.read(1) tempfile2 = @rio.instance_variable_get(:@rewindable_io) tempfile2.path.must_equal tempfile.path end it "close the underlying tempfile upon calling #close" do @rio.read(1) tempfile = @rio.instance_variable_get(:@rewindable_io) @rio.close tempfile.must_be :closed? end it "handle partial writes to tempfile" do def @rio.filesystem_has_posix_semantics? def @rewindable_io.write(buffer) super(buffer[0..1]) end super end @rio.read(1) tempfile = @rio.instance_variable_get(:@rewindable_io) @rio.close tempfile.must_be :closed? end it "close the underlying tempfile upon calling #close when not using posix semantics" do def @rio.filesystem_has_posix_semantics?; false end @rio.read(1) tempfile = @rio.instance_variable_get(:@rewindable_io) @rio.close tempfile.must_be :closed? end it "be possible to call #close when no data has been buffered yet" do @rio.close.must_be_nil end it "be possible to call #close multiple times" do @rio.close.must_be_nil @rio.close.must_be_nil end after do @rio.close @rio = nil end end describe Rack::RewindableInput do describe "given an IO object that is already rewindable" do def setup @io = StringIO.new("hello world".dup) super end include RewindableTest end describe "given an IO object that is not rewindable" do def setup @io = StringIO.new("hello world".dup) @io.instance_eval do undef :rewind end super end include RewindableTest end describe "given an IO object whose rewind method raises Errno::ESPIPE" do def setup @io = StringIO.new("hello world".dup) def @io.rewind raise Errno::ESPIPE, "You can't rewind this!" end super end include RewindableTest end end describe Rack::RewindableInput::Middleware do it "wraps rack.input in RewindableInput" do app = proc{|env| [200, {}, [env['rack.input'].class.to_s]]} app.call('rack.input'=>StringIO.new(''))[2].must_equal ['StringIO'] app = Rack::RewindableInput::Middleware.new(app) app.call('rack.input'=>StringIO.new(''))[2].must_equal ['Rack::RewindableInput'] end end rack-3.1.12/test/spec_runtime.rb000066400000000000000000000032721476365375000165430ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/runtime' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::Runtime do def runtime_app(app, *args) Rack::Lint.new Rack::Runtime.new(app, *args) end def request Rack::MockRequest.env_for end it "sets x-runtime is none is set" do app = lambda { |env| [200, { 'content-type' => 'text/plain' }, "Hello, World!"] } response = runtime_app(app).call(request) response[1]['x-runtime'].must_match(/[\d\.]+/) end it "doesn't set the x-runtime if it is already set" do app = lambda { |env| [200, { 'content-type' => 'text/plain', "x-runtime" => "foobar" }, "Hello, World!"] } response = runtime_app(app).call(request) response[1]['x-runtime'].must_equal "foobar" end it "allow a suffix to be set" do app = lambda { |env| [200, { 'content-type' => 'text/plain' }, "Hello, World!"] } response = runtime_app(app, "Test").call(request) response[1]['x-runtime-test'].must_match(/[\d\.]+/) end it "allow multiple timers to be set" do app = lambda { |env| sleep 0.1; [200, { 'content-type' => 'text/plain' }, "Hello, World!"] } runtime = runtime_app(app, "App") # wrap many times to guarantee a measurable difference 100.times do |i| runtime = Rack::Runtime.new(runtime, i.to_s) end runtime = Rack::Runtime.new(runtime, "All") response = runtime.call(request) response[1]['x-runtime-app'].must_match(/[\d\.]+/) response[1]['x-runtime-all'].must_match(/[\d\.]+/) Float(response[1]['x-runtime-all']).must_be :>, Float(response[1]['x-runtime-app']) end end rack-3.1.12/test/spec_sendfile.rb000066400000000000000000000160661476365375000166560ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'fileutils' require 'tmpdir' separate_testing do require_relative '../lib/rack/sendfile' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::Sendfile do def sendfile_body(filename = "rack_sendfile") FileUtils.touch File.join(Dir.tmpdir, filename) res = ['Hello World'] res.define_singleton_method(:to_path) { File.join(Dir.tmpdir, filename) } res end def simple_app(body = sendfile_body) lambda { |env| [200, { 'content-type' => 'text/plain' }, body] } end def sendfile_app(body, mappings = []) Rack::Lint.new Rack::Sendfile.new(simple_app(body), nil, mappings) end def request(headers = {}, body = sendfile_body, mappings = []) yield Rack::MockRequest.new(sendfile_app(body, mappings)).get('/', headers) end def open_file(path) Class.new(File) do unless method_defined?(:to_path) alias :to_path :path end end.open(path, 'wb+') end it "does nothing when no x-sendfile-type header present" do request do |response| response.must_be :ok? response.body.must_equal 'Hello World' response.headers.wont_include 'x-sendfile' end end it "does nothing and logs to rack.errors when incorrect x-sendfile-type header present" do io = StringIO.new request 'HTTP_X_SENDFILE_TYPE' => 'X-Banana', 'rack.errors' => io do |response| response.must_be :ok? response.body.must_equal 'Hello World' response.headers.wont_include 'x-sendfile' io.rewind io.read.must_equal "Unknown x-sendfile variation: \"X-Banana\"\n" end end it "sets x-sendfile response header and discards body" do request 'HTTP_X_SENDFILE_TYPE' => 'x-sendfile' do |response| response.must_be :ok? response.body.must_be :empty? response.headers['content-length'].must_equal '0' response.headers['x-sendfile'].must_equal File.join(Dir.tmpdir, "rack_sendfile") end end it "closes body when x-sendfile used" do body = sendfile_body closed = false body.define_singleton_method(:close){closed = true} request({'HTTP_X_SENDFILE_TYPE' => 'x-sendfile'}, body) do |response| response.must_be :ok? response.body.must_be :empty? response.headers['content-length'].must_equal '0' response.headers['x-sendfile'].must_equal File.join(Dir.tmpdir, "rack_sendfile") end closed.must_equal true end it "sets x-lighttpd-send-file response header and discards body" do request 'HTTP_X_SENDFILE_TYPE' => 'x-lighttpd-send-file' do |response| response.must_be :ok? response.body.must_be :empty? response.headers['content-length'].must_equal '0' response.headers['x-lighttpd-send-file'].must_equal File.join(Dir.tmpdir, "rack_sendfile") end end it "sets x-accel-redirect response header and discards body" do headers = { 'HTTP_X_SENDFILE_TYPE' => 'x-accel-redirect', 'HTTP_X_ACCEL_MAPPING' => "#{Dir.tmpdir}/=/foo/bar/" } request headers do |response| response.must_be :ok? response.body.must_be :empty? response.headers['content-length'].must_equal '0' response.headers['x-accel-redirect'].must_equal '/foo/bar/rack_sendfile' end end it "sets x-accel-redirect response header to percent-encoded path" do headers = { 'HTTP_X_SENDFILE_TYPE' => 'x-accel-redirect', 'HTTP_X_ACCEL_MAPPING' => "#{Dir.tmpdir}/=/foo/bar%/" } request headers, sendfile_body('file_with_%_?_symbol') do |response| response.must_be :ok? response.body.must_be :empty? response.headers['content-length'].must_equal '0' response.headers['x-accel-redirect'].must_equal '/foo/bar%25/file_with_%25_%3F_symbol' end end it 'writes to rack.error when no x-accel-mapping is specified' do request 'HTTP_X_SENDFILE_TYPE' => 'x-accel-redirect' do |response| response.must_be :ok? response.body.must_equal 'Hello World' response.headers.wont_include 'x-accel-redirect' response.errors.must_include 'x-accel-mapping' end end it 'does nothing when body does not respond to #to_path' do request({ 'HTTP_X_SENDFILE_TYPE' => 'x-sendfile' }, ['Not a file...']) do |response| response.body.must_equal 'Not a file...' response.headers.wont_include 'x-sendfile' end end it "sets x-accel-redirect response header and discards body when initialized with multiple mappings" do begin dir1 = Dir.mktmpdir dir2 = Dir.mktmpdir first_body = open_file(File.join(dir1, 'rack_sendfile')) first_body.puts 'hello world' second_body = open_file(File.join(dir2, 'rack_sendfile')) second_body.puts 'goodbye world' mappings = [ ["#{dir1}/", '/foo/bar/'], ["#{dir2}/", '/wibble/'] ] request({ 'HTTP_X_SENDFILE_TYPE' => 'x-accel-redirect' }, first_body, mappings) do |response| response.must_be :ok? response.body.must_be :empty? response.headers['content-length'].must_equal '0' response.headers['x-accel-redirect'].must_equal '/foo/bar/rack_sendfile' end request({ 'HTTP_X_SENDFILE_TYPE' => 'x-accel-redirect' }, second_body, mappings) do |response| response.must_be :ok? response.body.must_be :empty? response.headers['content-length'].must_equal '0' response.headers['x-accel-redirect'].must_equal '/wibble/rack_sendfile' end ensure FileUtils.remove_entry_secure dir1 FileUtils.remove_entry_secure dir2 end end it "sets x-accel-redirect response header and discards body when initialized with multiple mappings via header" do begin dir1 = Dir.mktmpdir dir2 = Dir.mktmpdir dir3 = Dir.mktmpdir first_body = open_file(File.join(dir1, 'rack_sendfile')) first_body.puts 'hello world' second_body = open_file(File.join(dir2, 'rack_sendfile')) second_body.puts 'goodbye world' third_body = open_file(File.join(dir3, 'rack_sendfile')) third_body.puts 'hello again world' headers = { 'HTTP_X_SENDFILE_TYPE' => 'x-accel-redirect', 'HTTP_X_ACCEL_MAPPING' => "#{dir1}/=/foo/bar/, #{dir2}/=/wibble/" } request(headers, first_body) do |response| response.must_be :ok? response.body.must_be :empty? response.headers['content-length'].must_equal '0' response.headers['x-accel-redirect'].must_equal '/foo/bar/rack_sendfile' end request(headers, second_body) do |response| response.must_be :ok? response.body.must_be :empty? response.headers['content-length'].must_equal '0' response.headers['x-accel-redirect'].must_equal '/wibble/rack_sendfile' end request(headers, third_body) do |response| response.must_be :ok? response.body.must_be :empty? response.headers['content-length'].must_equal '0' response.headers['x-accel-redirect'].must_equal "#{dir3}/rack_sendfile" end ensure FileUtils.remove_entry_secure dir1 FileUtils.remove_entry_secure dir2 end end end rack-3.1.12/test/spec_show_exceptions.rb000066400000000000000000000122421476365375000202760ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/show_exceptions' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::ShowExceptions do def show_exceptions(app) Rack::Lint.new Rack::ShowExceptions.new(app) end it "catches exceptions" do res = nil req = Rack::MockRequest.new( show_exceptions( lambda{|env| raise RuntimeError } )) res = req.get("/", "HTTP_ACCEPT" => "text/html") res.must_be :server_error? res.status.must_equal 500 assert_match(res, /RuntimeError/) assert_match(res, /ShowExceptions/) assert_match(res, /No GET data/) assert_match(res, /No POST data/) end it "handles exceptions with backtrace lines for files that are not readable" do res = nil req = Rack::MockRequest.new( show_exceptions( lambda{|env| raise RuntimeError, "foo", ["nonexistent.rb:2:in `a': adf (RuntimeError)", "bad-backtrace"] } )) res = req.get("/", "HTTP_ACCEPT" => "text/html") res.must_be :server_error? res.status.must_equal 500 assert_includes(res.body, 'RuntimeError') assert_includes(res.body, 'ShowExceptions') assert_includes(res.body, 'No GET data') assert_includes(res.body, 'No POST data') assert_includes(res.body, 'nonexistent.rb') refute_includes(res.body, 'bad-backtrace') end it "handles invalid POST data exceptions" do res = nil req = Rack::MockRequest.new( show_exceptions( lambda{|env| raise RuntimeError } )) res = req.post("/", "HTTP_ACCEPT" => "text/html", "rack.input" => StringIO.new(String.new << '(%bad-params%)')) res.must_be :server_error? res.status.must_equal 500 assert_match(res, /RuntimeError/) assert_match(res, /ShowExceptions/) assert_match(res, /No GET data/) assert_match(res, /Invalid POST data/) end it "works with binary data in the Rack environment" do res = nil # "\xCC" is not a valid UTF-8 string req = Rack::MockRequest.new( show_exceptions( lambda{|env| env['foo'] = "\xCC"; raise RuntimeError } )) res = req.get("/", "HTTP_ACCEPT" => "text/html") res.must_be :server_error? res.status.must_equal 500 assert_match(res, /RuntimeError/) assert_match(res, /ShowExceptions/) end it "responds with HTML only to requests accepting HTML" do res = nil req = Rack::MockRequest.new( show_exceptions( lambda{|env| raise RuntimeError, "It was never supposed to work" } )) [ # Serve text/html when the client accepts text/html ["text/html", ["/", { "HTTP_ACCEPT" => "text/html" }]], ["text/html", ["/", { "HTTP_ACCEPT" => "*/*" }]], # Serve text/plain when the client does not accept text/html ["text/plain", ["/"]], ["text/plain", ["/", { "HTTP_ACCEPT" => "application/json" }]] ].each do |exmime, rargs| res = req.get(*rargs) res.must_be :server_error? res.status.must_equal 500 res.content_type.must_equal exmime res.body.must_include "RuntimeError" res.body.must_include "It was never supposed to work" if exmime == "text/html" res.body.must_include '' else res.body.wont_include '' end end end it "handles exceptions without a backtrace" do res = nil req = Rack::MockRequest.new( show_exceptions( lambda{|env| raise RuntimeError, "", [] } ) ) res = req.get("/", "HTTP_ACCEPT" => "text/html") res.must_be :server_error? res.status.must_equal 500 assert_match(res, /RuntimeError/) assert_match(res, /ShowExceptions/) assert_match(res, /unknown location/) end it "allows subclasses to override template" do c = Class.new(Rack::ShowExceptions) do TEMPLATE = ERB.new("foo") def template TEMPLATE end end app = lambda { |env| raise RuntimeError, "", [] } req = Rack::MockRequest.new( Rack::Lint.new c.new(app) ) res = req.get("/", "HTTP_ACCEPT" => "text/html") res.must_be :server_error? res.status.must_equal 500 res.body.must_equal "foo" end it "knows to prefer plaintext for non-html" do # We don't need an app for this exc = Rack::ShowExceptions.new(nil) [ [{ "HTTP_ACCEPT" => "text/plain" }, true], [{ "HTTP_ACCEPT" => "text/foo" }, true], [{ "HTTP_ACCEPT" => "text/html" }, false] ].each do |env, expected| assert_equal(expected, exc.prefers_plaintext?(env)) end end it "prefers Exception#detailed_message instead of Exception#message if available" do res = nil custom_exc_class = Class.new(RuntimeError) do def detailed_message(highlight: false) "detailed_message_test" end end req = Rack::MockRequest.new( show_exceptions( lambda{|env| raise custom_exc_class } )) res = req.get("/", "HTTP_ACCEPT" => "text/html") res.must_be :server_error? res.status.must_equal 500 assert_match(res, /detailed_message_test/) assert_match(res, /ShowExceptions/) assert_match(res, /No GET data/) assert_match(res, /No POST data/) end end rack-3.1.12/test/spec_show_status.rb000066400000000000000000000074111476365375000174420ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/show_status' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::ShowStatus do def show_status(app) Rack::Lint.new Rack::ShowStatus.new(app) end it "provide a default status message" do req = Rack::MockRequest.new( show_status(lambda{|env| [404, { "content-type" => "text/plain", "content-length" => "0" }, []] })) res = req.get("/", lint: true) res.must_be :not_found? res.wont_be_empty res["content-type"].must_equal "text/html" assert_match(res, /404/) assert_match(res, /Not Found/) end it "let the app provide additional information" do req = Rack::MockRequest.new( show_status( lambda{|env| env["rack.showstatus.detail"] = "gone too meta." [404, { "content-type" => "text/plain", "content-length" => "0" }, []] })) res = req.get("/", lint: true) res.must_be :not_found? res.wont_be_empty res["content-type"].must_equal "text/html" assert_match(res, /404/) assert_match(res, /Not Found/) assert_match(res, /too meta/) end it "let the app provide additional information with non-String details" do req = Rack::MockRequest.new( show_status( lambda{|env| env["rack.showstatus.detail"] = ['gone too meta.'] [404, { "content-type" => "text/plain", "content-length" => "0" }, []] })) res = req.get("/", lint: true) res.must_be :not_found? res.wont_be_empty res["content-type"].must_equal "text/html" assert_includes(res.body, '404') assert_includes(res.body, 'Not Found') assert_includes(res.body, '["gone too meta."]') end it "escape error" do detail = "" req = Rack::MockRequest.new( show_status( lambda{|env| env["rack.showstatus.detail"] = detail [500, { "content-type" => "text/plain", "content-length" => "0" }, []] })) res = req.get("/", lint: true) res.wont_be_empty res["content-type"].must_equal "text/html" assert_match(res, /500/) res.wont_include detail res.body.must_include Rack::Utils.escape_html(detail) end it "not replace existing messages" do req = Rack::MockRequest.new( show_status( lambda{|env| [404, { "content-type" => "text/plain", "content-length" => "4" }, ["foo!"]] })) res = req.get("/", lint: true) res.must_be :not_found? res.body.must_equal "foo!" end it "pass on original headers" do headers = { "www-authenticate" => "Basic blah" } req = Rack::MockRequest.new( show_status(lambda{|env| [401, headers, []] })) res = req.get("/", lint: true) res["www-authenticate"].must_equal "Basic blah" end it "replace existing messages if there is detail" do req = Rack::MockRequest.new( show_status( lambda{|env| env["rack.showstatus.detail"] = "gone too meta." [404, { "content-type" => "text/plain", "content-length" => "4" }, ["foo!"]] })) res = req.get("/", lint: true) res.must_be :not_found? res.wont_be_empty res["content-type"].must_equal "text/html" res["content-length"].wont_equal "4" assert_match(res, /404/) assert_match(res, /too meta/) res.body.wont_match(/foo/) end it "close the original body" do closed = false body = Object.new def body.each; yield 's' end body.define_singleton_method(:close) { closed = true } req = Rack::MockRequest.new( show_status(lambda{|env| [404, { "content-type" => "text/plain", "content-length" => "0" }, body] })) req.get("/", lint: true) closed.must_equal true end end rack-3.1.12/test/spec_static.rb000066400000000000000000000210611476365375000163430ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'zlib' separate_testing do require_relative '../lib/rack/static' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end class DummyApp def call(env) [200, { "content-type" => "text/plain" }, ["Hello World"]] end end describe Rack::Static do DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT def static(app, *args) Rack::Lint.new Rack::Static.new(app, *args) end root = File.expand_path(File.dirname(__FILE__)) OPTIONS = { urls: ["/cgi"], root: root } CASCADE_OPTIONS = { urls: ["/cgi"], root: root, cascade: true } STATIC_OPTIONS = { urls: [""], root: "#{root}/static", index: 'index.html' } STATIC_URLS_OPTIONS = { urls: ["/static"], root: "#{root}", index: 'index.html' } HASH_OPTIONS = { urls: { "/cgi/sekret" => 'cgi/test' }, root: root } HASH_ROOT_OPTIONS = { urls: { "/" => "static/foo.html" }, root: root } GZIP_OPTIONS = { urls: ["/cgi"], root: root, gzip: true } before do @request = Rack::MockRequest.new(static(DummyApp.new, OPTIONS)) @cascade_request = Rack::MockRequest.new(static(DummyApp.new, CASCADE_OPTIONS)) @static_request = Rack::MockRequest.new(static(DummyApp.new, STATIC_OPTIONS)) @static_urls_request = Rack::MockRequest.new(static(DummyApp.new, STATIC_URLS_OPTIONS)) @hash_request = Rack::MockRequest.new(static(DummyApp.new, HASH_OPTIONS)) @hash_root_request = Rack::MockRequest.new(static(DummyApp.new, HASH_ROOT_OPTIONS)) @gzip_request = Rack::MockRequest.new(static(DummyApp.new, GZIP_OPTIONS)) @header_request = Rack::MockRequest.new(static(DummyApp.new, HEADER_OPTIONS)) end it "serves files" do res = @request.get("/cgi/test") res.must_be :ok? res.body.must_match(/ruby/) end it "does not serve files outside :urls" do res = @request.get("/cgi/../#{File.basename(__FILE__)}") res.must_be :ok? res.body.must_equal "Hello World" end it "404s if url root is known but it can't find the file" do res = @request.get("/cgi/foo") res.must_be :not_found? end it "serves files when using :cascade option" do res = @cascade_request.get("/cgi/test") res.must_be :ok? res.body.must_match(/ruby/) end it "calls down the chain if if can't find the file when using the :cascade option" do res = @cascade_request.get("/cgi/foo") res.must_be :ok? res.body.must_equal "Hello World" end it "calls down the chain if url root is not known" do res = @request.get("/something/else") res.must_be :ok? res.body.must_equal "Hello World" end it "calls index file when requesting root in the given folder" do res = @static_request.get("/") res.must_be :ok? res.body.must_match(/index!/) res = @static_request.get("/other/") res.must_be :not_found? res = @static_request.get("/another/") res.must_be :ok? res.body.must_match(/another index!/) end it "does not call index file when requesting folder with unknown prefix" do res = @static_urls_request.get("/static/another/") res.must_be :ok? res.body.must_match(/index!/) res = @static_urls_request.get("/something/else/") res.must_be :ok? res.body.must_equal "Hello World" end it "doesn't call index file if :index option was omitted" do res = @request.get("/") res.body.must_equal "Hello World" end it "serves hidden files" do res = @hash_request.get("/cgi/sekret") res.must_be :ok? res.body.must_match(/ruby/) end it "calls down the chain if the URI is not specified" do res = @hash_request.get("/something/else") res.must_be :ok? res.body.must_equal "Hello World" end it "allows the root URI to be configured via hash options" do res = @hash_root_request.get("/") res.must_be :ok? res.body.must_match(/foo.html!/) end it "serves gzipped files if client accepts gzip encoding and gzip files are present" do res = @gzip_request.get("/cgi/test", 'HTTP_ACCEPT_ENCODING' => 'deflate, gzip') res.must_be :ok? res.headers['content-encoding'].must_equal 'gzip' res.headers['content-type'].must_equal 'text/plain' Zlib::GzipReader.wrap(StringIO.new(res.body), &:read).must_match(/ruby/) end it "serves regular files if client accepts gzip encoding and gzip files are not present" do res = @gzip_request.get("/cgi/rackup_stub.rb", 'HTTP_ACCEPT_ENCODING' => 'deflate, gzip') res.must_be :ok? res.headers['content-encoding'].must_be_nil res.headers['content-type'].must_equal 'text/x-script.ruby' res.body.must_match(/ruby/) end it "serves regular files if client does not accept gzip encoding" do res = @gzip_request.get("/cgi/test") res.must_be :ok? res.headers['content-encoding'].must_be_nil res.headers['content-type'].must_equal 'text/plain' res.body.must_match(/ruby/) end it "returns 304 if gzipped file isn't modified since last serve" do path = File.join(DOCROOT, "/cgi/test") res = @gzip_request.get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => File.mtime(path).httpdate) res.status.must_equal 304 res.body.must_be :empty? res.headers['content-encoding'].must_be_nil res.headers['content-type'].must_be_nil end it "return 304 if gzipped file isn't modified since last serve" do path = File.join(DOCROOT, "/cgi/test") res = @gzip_request.get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => File.mtime(path+'.gz').httpdate, 'HTTP_ACCEPT_ENCODING' => 'deflate, gzip') res.status.must_equal 304 res.body.must_be :empty? end it "supports serving fixed cache-control (legacy option)" do opts = OPTIONS.merge(cache_control: 'public') request = Rack::MockRequest.new(static(DummyApp.new, opts)) res = request.get("/cgi/test") res.must_be :ok? res.headers['cache-control'].must_equal 'public' end HEADER_OPTIONS = { urls: ["/cgi"], root: root, header_rules: [ [:all, { 'cache-control' => 'public, max-age=100' }], [:fonts, { 'cache-control' => 'public, max-age=200' }], [%w(png jpg), { 'cache-control' => 'public, max-age=300' }], ['/cgi/assets/folder/', { 'cache-control' => 'public, max-age=400' }], ['cgi/assets/javascripts', { 'cache-control' => 'public, max-age=500' }], [/\.(css|erb)\z/, { 'cache-control' => 'public, max-age=600' }], [false, { 'cache-control' => 'public, max-age=600' }] ] } it "supports header rule :all" do # Headers for all files via :all shortcut res = @header_request.get('/cgi/assets/index.html') res.must_be :ok? res.headers['cache-control'].must_equal 'public, max-age=100' end it "supports header rule :fonts" do # Headers for web fonts via :fonts shortcut res = @header_request.get('/cgi/assets/fonts/font.eot') res.must_be :ok? res.headers['cache-control'].must_equal 'public, max-age=200' end it "supports file extension header rules provided as an Array" do # Headers for file extensions via array res = @header_request.get('/cgi/assets/images/image.png') res.must_be :ok? res.headers['cache-control'].must_equal 'public, max-age=300' end it "supports folder rules provided as a String" do # Headers for files in folder via string res = @header_request.get('/cgi/assets/folder/test.js') res.must_be :ok? res.headers['cache-control'].must_equal 'public, max-age=400' end it "supports folder header rules provided as a String not starting with a slash" do res = @header_request.get('/cgi/assets/javascripts/app.js') res.must_be :ok? res.headers['cache-control'].must_equal 'public, max-age=500' end it "supports flexible header rules provided as Regexp" do # Flexible Headers via Regexp res = @header_request.get('/cgi/assets/stylesheets/app.css') res.must_be :ok? res.headers['cache-control'].must_equal 'public, max-age=600' end it "prioritizes header rules over fixed cache-control setting (legacy option)" do opts = OPTIONS.merge( cache_control: 'public, max-age=24', header_rules: [ [:all, { 'cache-control' => 'public, max-age=42' }] ]) request = Rack::MockRequest.new(static(DummyApp.new, opts)) res = request.get("/cgi/test") res.must_be :ok? res.headers['cache-control'].must_equal 'public, max-age=42' end it "expands the root path upon the middleware initialization" do relative_path = STATIC_OPTIONS[:root].sub("#{Dir.pwd}/", '') opts = { urls: [""], root: relative_path, index: 'index.html' } request = Rack::MockRequest.new(static(DummyApp.new, opts)) Dir.chdir '..' do res = request.get("") res.must_be :ok? res.body.must_match(/index!/) end end end rack-3.1.12/test/spec_tempfile_reaper.rb000066400000000000000000000053651476365375000202300ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/tempfile_reaper' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::TempfileReaper do class MockTempfile attr_reader :closed def initialize @closed = false end def close! @closed = true end end before do @env = Rack::MockRequest.env_for end def call(app) Rack::Lint.new(Rack::TempfileReaper.new(app)).call(@env) end it 'do nothing (i.e. not bomb out) without env[rack.tempfiles]' do app = lambda { |_| [200, {}, ['Hello, World!']] } response = call(app) response[2].close response[0].must_equal 200 end it 'close env[rack.tempfiles] when app raises an error' do tempfile1, tempfile2 = MockTempfile.new, MockTempfile.new @env['rack.tempfiles'] = [ tempfile1, tempfile2 ] app = lambda { |_| raise 'foo' } proc{call(app)}.must_raise RuntimeError tempfile1.closed.must_equal true tempfile2.closed.must_equal true end it 'close env[rack.tempfiles] when app raises an non-StandardError' do tempfile1, tempfile2 = MockTempfile.new, MockTempfile.new @env['rack.tempfiles'] = [ tempfile1, tempfile2 ] app = lambda { |_| raise LoadError, 'foo' } proc{call(app)}.must_raise LoadError tempfile1.closed.must_equal true tempfile2.closed.must_equal true end it 'close env[rack.tempfiles] when body is closed' do tempfile1, tempfile2 = MockTempfile.new, MockTempfile.new @env['rack.tempfiles'] = [ tempfile1, tempfile2 ] app = lambda { |_| [200, {}, ['Hello, World!']] } call(app)[2].close tempfile1.closed.must_equal true tempfile2.closed.must_equal true end it 'initialize env[rack.tempfiles] when not already present' do tempfile = MockTempfile.new app = lambda do |env| env['rack.tempfiles'] << tempfile [200, {}, ['Hello, World!']] end call(app)[2].close tempfile.closed.must_equal true end it 'append env[rack.tempfiles] when already present' do tempfile1, tempfile2 = MockTempfile.new, MockTempfile.new @env['rack.tempfiles'] = [ tempfile1 ] app = lambda do |env| env['rack.tempfiles'] << tempfile2 [200, {}, ['Hello, World!']] end call(app)[2].close tempfile1.closed.must_equal true tempfile2.closed.must_equal true end it 'handle missing rack.tempfiles on normal response' do app = lambda do |env| env.delete('rack.tempfiles') [200, {}, ['Hello, World!']] end call(app)[2].close end it 'handle missing rack.tempfiles on error' do app = lambda do |env| env.delete('rack.tempfiles') raise 'Foo' end proc{call(app)}.must_raise RuntimeError end end rack-3.1.12/test/spec_urlmap.rb000066400000000000000000000230111476365375000163510ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/urlmap' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' end describe Rack::URLMap do it "dispatches paths correctly" do app = lambda { |env| [200, { 'x-scriptname' => env['SCRIPT_NAME'], 'x-pathinfo' => env['PATH_INFO'], 'content-type' => 'text/plain' }, [""]] } map = Rack::Lint.new(Rack::URLMap.new({ 'http://foo.org/bar' => app, '/foo' => app, '/foo/bar' => app })) res = Rack::MockRequest.new(map).get("/") res.must_be :not_found? res = Rack::MockRequest.new(map).get("/qux") res.must_be :not_found? res = Rack::MockRequest.new(map).get("/foo") res.must_be :ok? res["x-scriptname"].must_equal "/foo" res["x-pathinfo"].must_equal "" res = Rack::MockRequest.new(map).get("/foo/") res.must_be :ok? res["x-scriptname"].must_equal "/foo" res["x-pathinfo"].must_equal "/" res = Rack::MockRequest.new(map).get("/foo/bar") res.must_be :ok? res["x-scriptname"].must_equal "/foo/bar" res["x-pathinfo"].must_equal "" res = Rack::MockRequest.new(map).get("/foo/bard") res.must_be :ok? res["x-scriptname"].must_equal "/foo" res["x-pathinfo"].must_equal "/bard" res = Rack::MockRequest.new(map).get("/foo/bar/") res.must_be :ok? res["x-scriptname"].must_equal "/foo/bar" res["x-pathinfo"].must_equal "/" res = Rack::MockRequest.new(map).get("/foo///bar//quux") res.status.must_equal 200 res.must_be :ok? res["x-scriptname"].must_equal "/foo/bar" res["x-pathinfo"].must_equal "//quux" res = Rack::MockRequest.new(map).get("/foo/quux", "SCRIPT_NAME" => "/bleh") res.must_be :ok? res["x-scriptname"].must_equal "/bleh/foo" res["x-pathinfo"].must_equal "/quux" res = Rack::MockRequest.new(map).get("/bar", 'HTTP_HOST' => 'foo.org') res.must_be :ok? res["x-scriptname"].must_equal "/bar" res["x-pathinfo"].must_be :empty? res = Rack::MockRequest.new(map).get("/bar/", 'HTTP_HOST' => 'foo.org') res.must_be :ok? res["x-scriptname"].must_equal "/bar" res["x-pathinfo"].must_equal '/' end it "dispatches hosts correctly" do map = Rack::Lint.new(Rack::URLMap.new("http://foo.org/" => lambda { |env| [200, { "content-type" => "text/plain", "x-position" => "foo.org", "x-host" => env["HTTP_HOST"] || env["SERVER_NAME"], }, [""]]}, "http://subdomain.foo.org/" => lambda { |env| [200, { "content-type" => "text/plain", "x-position" => "subdomain.foo.org", "x-host" => env["HTTP_HOST"] || env["SERVER_NAME"], }, [""]]}, "http://bar.org/" => lambda { |env| [200, { "content-type" => "text/plain", "x-position" => "bar.org", "x-host" => env["HTTP_HOST"] || env["SERVER_NAME"], }, [""]]}, "/" => lambda { |env| [200, { "content-type" => "text/plain", "x-position" => "default.org", "x-host" => env["HTTP_HOST"] || env["SERVER_NAME"], }, [""]]} )) res = Rack::MockRequest.new(map).get("/") res.must_be :ok? res["x-position"].must_equal "default.org" res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "bar.org") res.must_be :ok? res["x-position"].must_equal "bar.org" res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "foo.org") res.must_be :ok? res["x-position"].must_equal "foo.org" res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "subdomain.foo.org", "SERVER_NAME" => "foo.org") res.must_be :ok? res["x-position"].must_equal "subdomain.foo.org" res = Rack::MockRequest.new(map).get("http://foo.org/") res.must_be :ok? res["x-position"].must_equal "foo.org" res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "example.org") res.must_be :ok? res["x-position"].must_equal "default.org" res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "any-host.org") res.must_be :ok? res["x-position"].must_equal "default.org" res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "any-host.org", "HTTP_X_FORWARDED_HOST" => "any-host.org") res.must_be :ok? res["x-position"].must_equal "default.org" res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "example.org:9292", "SERVER_PORT" => "9292") res.must_be :ok? res["x-position"].must_equal "default.org" end it "be nestable" do map = Rack::Lint.new(Rack::URLMap.new("/foo" => Rack::URLMap.new("/bar" => Rack::URLMap.new("/quux" => lambda { |env| [200, { "content-type" => "text/plain", "x-position" => "/foo/bar/quux", "x-pathinfo" => env["PATH_INFO"], "x-scriptname" => env["SCRIPT_NAME"], }, [""]]} )))) res = Rack::MockRequest.new(map).get("/foo/bar") res.must_be :not_found? res = Rack::MockRequest.new(map).get("/foo/bar/quux") res.must_be :ok? res["x-position"].must_equal "/foo/bar/quux" res["x-pathinfo"].must_equal "" res["x-scriptname"].must_equal "/foo/bar/quux" end it "route root apps correctly" do map = Rack::Lint.new(Rack::URLMap.new("/" => lambda { |env| [200, { "content-type" => "text/plain", "x-position" => "root", "x-pathinfo" => env["PATH_INFO"], "x-scriptname" => env["SCRIPT_NAME"] }, [""]]}, "/foo" => lambda { |env| [200, { "content-type" => "text/plain", "x-position" => "foo", "x-pathinfo" => env["PATH_INFO"], "x-scriptname" => env["SCRIPT_NAME"] }, [""]]} )) res = Rack::MockRequest.new(map).get("/foo/bar") res.must_be :ok? res["x-position"].must_equal "foo" res["x-pathinfo"].must_equal "/bar" res["x-scriptname"].must_equal "/foo" res = Rack::MockRequest.new(map).get("/foo") res.must_be :ok? res["x-position"].must_equal "foo" res["x-pathinfo"].must_equal "" res["x-scriptname"].must_equal "/foo" res = Rack::MockRequest.new(map).get("/bar") res.must_be :ok? res["x-position"].must_equal "root" res["x-pathinfo"].must_equal "/bar" res["x-scriptname"].must_equal "" res = Rack::MockRequest.new(map).get("") res.must_be :ok? res["x-position"].must_equal "root" res["x-pathinfo"].must_equal "/" res["x-scriptname"].must_equal "" end it "not squeeze slashes" do map = Rack::Lint.new(Rack::URLMap.new("/" => lambda { |env| [200, { "content-type" => "text/plain", "x-position" => "root", "x-pathinfo" => env["PATH_INFO"], "x-scriptname" => env["SCRIPT_NAME"] }, [""]]}, "/foo" => lambda { |env| [200, { "content-type" => "text/plain", "x-position" => "foo", "x-pathinfo" => env["PATH_INFO"], "x-scriptname" => env["SCRIPT_NAME"] }, [""]]} )) res = Rack::MockRequest.new(map).get("/http://example.org/bar") res.must_be :ok? res["x-position"].must_equal "root" res["x-pathinfo"].must_equal "/http://example.org/bar" res["x-scriptname"].must_equal "" end it "not be case sensitive with hosts" do map = Rack::Lint.new(Rack::URLMap.new("http://example.org/" => lambda { |env| [200, { "content-type" => "text/plain", "x-position" => "root", "x-pathinfo" => env["PATH_INFO"], "x-scriptname" => env["SCRIPT_NAME"] }, [""]]} )) res = Rack::MockRequest.new(map).get("http://example.org/") res.must_be :ok? res["x-position"].must_equal "root" res["x-pathinfo"].must_equal "/" res["x-scriptname"].must_equal "" res = Rack::MockRequest.new(map).get("http://EXAMPLE.ORG/") res.must_be :ok? res["x-position"].must_equal "root" res["x-pathinfo"].must_equal "/" res["x-scriptname"].must_equal "" end it "not allow locations unless they start with /" do lambda do Rack::URLMap.new("a/" => lambda { |env| }) end.must_raise ArgumentError end end rack-3.1.12/test/spec_utils.rb000066400000000000000000000777171476365375000162370ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' require 'timeout' separate_testing do require_relative '../lib/rack/utils' require_relative '../lib/rack/lint' require_relative '../lib/rack/mock_request' require_relative '../lib/rack/request' require_relative '../lib/rack/content_length' end describe Rack::Utils do def assert_sets(exp, act) exp = Set.new exp.split '&' act = Set.new act.split '&' assert_equal exp, act end def assert_query(exp, act) assert_sets exp, Rack::Utils.build_query(act) end def assert_nested_query(exp, act) assert_sets exp, Rack::Utils.build_nested_query(act) end it 'can be mixed in and used' do instance = Class.new { include Rack::Utils public :parse_nested_query public :parse_query }.new assert_equal({ "foo" => "bar" }, instance.parse_nested_query("foo=bar")) assert_equal({ "foo" => "bar" }, instance.parse_query("foo=bar")) end it "round trip binary data" do r = [218, 0].pack 'CC' z = Rack::Utils.unescape(Rack::Utils.escape(r), Encoding::BINARY) r.must_equal z end it "escape correctly" do Rack::Utils.escape("fobar").must_equal "fo%3Co%3Ebar" Rack::Utils.escape("a space").must_equal "a+space" Rack::Utils.escape("q1!2\"'w$5&7/z8)?\\"). must_equal "q1%212%22%27w%245%267%2Fz8%29%3F%5C" end it "escape correctly for multibyte characters" do matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto matz_name.force_encoding(Encoding::UTF_8) Rack::Utils.escape(matz_name).must_equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8' matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding Rack::Utils.escape(matz_name_sep).must_equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8' end it "escape objects that responds to to_s" do Rack::Utils.escape(:id).must_equal "id" end it "escape non-UTF8 strings" do Rack::Utils.escape("ø".encode("ISO-8859-1")).must_equal "%F8" end it "not hang on escaping long strings that end in % (http://redmine.ruby-lang.org/issues/5149)" do Timeout.timeout(1) do lambda { URI.decode_www_form_component "A string that causes catastrophic backtracking as it gets longer %" }.must_raise ArgumentError end end it "escape path spaces with %20" do Rack::Utils.escape_path("foo bar").must_equal "foo%20bar" end it "unescape correctly" do Rack::Utils.unescape("fo%3Co%3Ebar").must_equal "fobar" Rack::Utils.unescape("a+space").must_equal "a space" Rack::Utils.unescape("a%20space").must_equal "a space" Rack::Utils.unescape("q1%212%22%27w%245%267%2Fz8%29%3F%5C"). must_equal "q1!2\"'w$5&7/z8)?\\" end it "parse query strings correctly" do Rack::Utils.parse_query("foo=bar"). must_equal "foo" => "bar" Rack::Utils.parse_query("foo=\"bar\""). must_equal "foo" => "\"bar\"" Rack::Utils.parse_query("foo=bar&foo=quux"). must_equal "foo" => ["bar", "quux"] Rack::Utils.parse_query("foo=1&bar=2"). must_equal "foo" => "1", "bar" => "2" Rack::Utils.parse_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"). must_equal "my weird field" => "q1!2\"'w$5&7/z8)?" Rack::Utils.parse_query("foo%3Dbaz=bar").must_equal "foo=baz" => "bar" Rack::Utils.parse_query("=").must_equal "" => "" Rack::Utils.parse_query("=value").must_equal "" => "value" Rack::Utils.parse_query("key=").must_equal "key" => "" Rack::Utils.parse_query("&key&").must_equal "key" => nil Rack::Utils.parse_query(";key;", ";,").must_equal "key" => nil Rack::Utils.parse_query(",key,", ";,").must_equal "key" => nil Rack::Utils.parse_query(";foo=bar,;", ";,").must_equal "foo" => "bar" Rack::Utils.parse_query(",foo=bar;,", ";,").must_equal "foo" => "bar" end it "parse query strings correctly using arrays" do Rack::Utils.parse_query("a[]=1").must_equal "a[]" => "1" Rack::Utils.parse_query("a[]=1&a[]=2").must_equal "a[]" => ["1", "2"] Rack::Utils.parse_query("a[]=1&a[]=2&a[]=3").must_equal "a[]" => ["1", "2", "3"] end it "not create infinite loops with cycle structures" do params = Rack::Utils::KeySpaceConstrainedParams.new params['foo'] = params h = params.to_params_hash h['foo'].to_s.must_equal h['foo']['foo'].to_s end it "parse nil as an empty query string" do Rack::Utils.parse_nested_query(nil).must_equal({}) end it "raise an exception if the params are too deep" do len = Rack::Utils.param_depth_limit lambda { Rack::Utils.parse_nested_query("foo#{"[a]" * len}=bar") }.must_raise(Rack::QueryParser::ParamsTooDeepError) Rack::Utils.parse_nested_query("foo#{"[a]" * (len - 1)}=bar") end it "parse nested query strings correctly" do Rack::Utils.parse_nested_query("foo"). must_equal "foo" => nil Rack::Utils.parse_nested_query("foo="). must_equal "foo" => "" Rack::Utils.parse_nested_query("foo=bar"). must_equal "foo" => "bar" Rack::Utils.parse_nested_query("foo=\"bar\""). must_equal "foo" => "\"bar\"" Rack::Utils.parse_nested_query("foo=bar&foo=quux"). must_equal "foo" => "quux" Rack::Utils.parse_nested_query("foo&foo="). must_equal "foo" => "" Rack::Utils.parse_nested_query("foo=1&bar=2"). must_equal "foo" => "1", "bar" => "2" Rack::Utils.parse_nested_query("&foo=1&&bar=2"). must_equal "foo" => "1", "bar" => "2" Rack::Utils.parse_nested_query("foo&bar="). must_equal "foo" => nil, "bar" => "" Rack::Utils.parse_nested_query("foo=bar&baz="). must_equal "foo" => "bar", "baz" => "" Rack::Utils.parse_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"). must_equal "my weird field" => "q1!2\"'w$5&7/z8)?" Rack::Utils.parse_nested_query("a=b&pid%3D1234=1023"). must_equal "pid=1234" => "1023", "a" => "b" Rack::Utils.parse_nested_query("foo[]"). must_equal "foo" => [nil] Rack::Utils.parse_nested_query("foo[]="). must_equal "foo" => [""] Rack::Utils.parse_nested_query("foo[]=bar"). must_equal "foo" => ["bar"] Rack::Utils.parse_nested_query("foo[]=bar&foo"). must_equal "foo" => nil Rack::Utils.parse_nested_query("foo[]=bar&foo["). must_equal "foo" => ["bar"], "foo[" => nil Rack::Utils.parse_nested_query("foo[]=bar&foo[=baz"). must_equal "foo" => ["bar"], "foo[" => "baz" Rack::Utils.parse_nested_query("foo[]=bar&foo[]"). must_equal "foo" => ["bar", nil] Rack::Utils.parse_nested_query("foo[]=bar&foo[]="). must_equal "foo" => ["bar", ""] Rack::Utils.parse_nested_query("foo[]=1&foo[]=2"). must_equal "foo" => ["1", "2"] Rack::Utils.parse_nested_query("foo=bar&baz[]=1&baz[]=2&baz[]=3"). must_equal "foo" => "bar", "baz" => ["1", "2", "3"] Rack::Utils.parse_nested_query("foo[]=bar&baz[]=1&baz[]=2&baz[]=3"). must_equal "foo" => ["bar"], "baz" => ["1", "2", "3"] Rack::Utils.parse_nested_query("x[y][z]"). must_equal "x" => { "y" => { "z" => nil } } Rack::Utils.parse_nested_query("x[y][z]=1"). must_equal "x" => { "y" => { "z" => "1" } } Rack::Utils.parse_nested_query("x[y][z][]=1"). must_equal "x" => { "y" => { "z" => ["1"] } } Rack::Utils.parse_nested_query("x[y][z]=1&x[y][z]=2"). must_equal "x" => { "y" => { "z" => "2" } } Rack::Utils.parse_nested_query("x[y][z][]=1&x[y][z][]=2"). must_equal "x" => { "y" => { "z" => ["1", "2"] } } Rack::Utils.parse_nested_query("x[y][][z]=1"). must_equal "x" => { "y" => [{ "z" => "1" }] } Rack::Utils.parse_nested_query("x[y][][z][]=1"). must_equal "x" => { "y" => [{ "z" => ["1"] }] } Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2"). must_equal "x" => { "y" => [{ "z" => "1", "w" => "2" }] } Rack::Utils.parse_nested_query("x[y][][v][w]=1"). must_equal "x" => { "y" => [{ "v" => { "w" => "1" } }] } Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][v][w]=2"). must_equal "x" => { "y" => [{ "z" => "1", "v" => { "w" => "2" } }] } Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][z]=2"). must_equal "x" => { "y" => [{ "z" => "1" }, { "z" => "2" }] } Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3"). must_equal "x" => { "y" => [{ "z" => "1", "w" => "a" }, { "z" => "2", "w" => "3" }] } Rack::Utils.parse_nested_query("x[][y]=1&x[][z][w]=a&x[][y]=2&x[][z][w]=b"). must_equal "x" => [{ "y" => "1", "z" => { "w" => "a" } }, { "y" => "2", "z" => { "w" => "b" } }] Rack::Utils.parse_nested_query("x[][z][w]=a&x[][y]=1&x[][z][w]=b&x[][y]=2"). must_equal "x" => [{ "y" => "1", "z" => { "w" => "a" } }, { "y" => "2", "z" => { "w" => "b" } }] Rack::Utils.parse_nested_query("data[books][][data][page]=1&data[books][][data][page]=2"). must_equal "data" => { "books" => [{ "data" => { "page" => "1" } }, { "data" => { "page" => "2" } }] } lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }. must_raise(Rack::Utils::ParameterTypeError). message.must_equal "expected Hash (got String) for param `y'" lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }. must_raise(Rack::Utils::ParameterTypeError). message.must_match(/expected Array \(got [^)]*\) for param `x'/) lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }. must_raise(Rack::Utils::ParameterTypeError). message.must_equal "expected Array (got String) for param `y'" end it "can parse a query string with a key that has invalid UTF-8 encoded bytes" do Rack::Utils.parse_nested_query("foo%81E=1").must_equal "foo\x81E"=>"1" end it "only moves to a new array when the full key has been seen" do Rack::Utils.parse_nested_query("x[][y][][z]=1&x[][y][][w]=2"). must_equal "x" => [{ "y" => [{ "z" => "1", "w" => "2" }] }] Rack::Utils.parse_nested_query( "x[][id]=1&x[][y][a]=5&x[][y][b]=7&x[][z][id]=3&x[][z][w]=0&x[][id]=2&x[][y][a]=6&x[][y][b]=8&x[][z][id]=4&x[][z][w]=0" ).must_equal "x" => [ { "id" => "1", "y" => { "a" => "5", "b" => "7" }, "z" => { "id" => "3", "w" => "0" } }, { "id" => "2", "y" => { "a" => "6", "b" => "8" }, "z" => { "id" => "4", "w" => "0" } }, ] end it "handles unexpected use of [ and ] in parameter keys as normal characters" do Rack::Utils.parse_nested_query("[]=1&[a]=2&b[=3&c]=4"). must_equal "[]" => "1", "[a]" => "2", "b[" => "3", "c]" => "4" Rack::Utils.parse_nested_query("d[[]=5&e][]=6&f[[]]=7"). must_equal "d" => {"[" => "5"}, "e]" => ["6"], "f" => { "[" => { "]" => "7" } } Rack::Utils.parse_nested_query("g[h]i=8&j[k]l[m]=9"). must_equal "g" => { "h" => { "i" => "8" } }, "j" => { "k" => { "l[m]" =>"9" } } Rack::Utils.parse_nested_query("l[[[[[[[[]]]]]]]=10"). must_equal "l"=>{"[[[[[[["=>{"]]]]]]"=>"10"}} end it "allow setting the params hash class to use for parsing query strings" do begin default_parser = Rack::Utils.default_query_parser param_parser_class = Class.new(Rack::QueryParser::Params) do def initialize(*) super(){|h, k| h[k.to_s] if k.is_a?(Symbol)} end end Rack::Utils.default_query_parser = Rack::QueryParser.new(param_parser_class, 100) h1 = Rack::Utils.parse_query(",foo=bar;,", ";,") h1[:foo].must_equal "bar" h2 = Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2") h2[:x][:y][0][:z].must_equal "1" h3 = Rack::Utils.parse_nested_query("") h3.merge(h1)[:foo].must_equal "bar" ensure Rack::Utils.default_query_parser = default_parser end end it "build query strings correctly" do assert_query "foo=bar", "foo" => "bar" assert_query "foo=bar&foo=quux", "foo" => ["bar", "quux"] assert_query "foo=1&bar=2", "foo" => "1", "bar" => "2" assert_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F", "my weird field" => "q1!2\"'w$5&7/z8)?") end it "build nested query strings correctly" do Rack::Utils.build_nested_query("foo" => nil).must_equal "foo" Rack::Utils.build_nested_query("foo" => "").must_equal "foo=" Rack::Utils.build_nested_query("foo" => "bar").must_equal "foo=bar" assert_nested_query("foo=1&bar=2", "foo" => "1", "bar" => "2") assert_nested_query("foo=1&bar=2", "foo" => 1, "bar" => 2) assert_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F", "my weird field" => "q1!2\"'w$5&7/z8)?") Rack::Utils.build_nested_query("foo" => [nil]).must_equal "foo%5B%5D" Rack::Utils.build_nested_query("foo" => [""]).must_equal "foo%5B%5D=" Rack::Utils.build_nested_query("foo" => ["bar"]).must_equal "foo%5B%5D=bar" Rack::Utils.build_nested_query('foo' => []).must_equal '' Rack::Utils.build_nested_query('foo' => {}).must_equal '' Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => []).must_equal 'foo=bar' Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => {}).must_equal 'foo=bar' Rack::Utils.build_nested_query('foo' => nil, 'bar' => ''). must_equal 'foo&bar=' Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => ''). must_equal 'foo=bar&baz=' Rack::Utils.build_nested_query('foo' => ['1', '2']). must_equal 'foo%5B%5D=1&foo%5B%5D=2' Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => ['1', '2', '3']). must_equal 'foo=bar&baz%5B%5D=1&baz%5B%5D=2&baz%5B%5D=3' Rack::Utils.build_nested_query('foo' => ['bar'], 'baz' => ['1', '2', '3']). must_equal 'foo%5B%5D=bar&baz%5B%5D=1&baz%5B%5D=2&baz%5B%5D=3' Rack::Utils.build_nested_query('foo' => ['bar'], 'baz' => ['1', '2', '3']). must_equal 'foo%5B%5D=bar&baz%5B%5D=1&baz%5B%5D=2&baz%5B%5D=3' Rack::Utils.build_nested_query('x' => { 'y' => { 'z' => '1' } }). must_equal 'x%5By%5D%5Bz%5D=1' Rack::Utils.build_nested_query('x' => { 'y' => { 'z' => ['1'] } }). must_equal 'x%5By%5D%5Bz%5D%5B%5D=1' Rack::Utils.build_nested_query('x' => { 'y' => { 'z' => ['1', '2'] } }). must_equal 'x%5By%5D%5Bz%5D%5B%5D=1&x%5By%5D%5Bz%5D%5B%5D=2' Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1' }] }). must_equal 'x%5By%5D%5B%5D%5Bz%5D=1' Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => ['1'] }] }). must_equal 'x%5By%5D%5B%5D%5Bz%5D%5B%5D=1' Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1', 'w' => '2' }] }). must_equal 'x%5By%5D%5B%5D%5Bz%5D=1&x%5By%5D%5B%5D%5Bw%5D=2' Rack::Utils.build_nested_query('x' => { 'y' => [{ 'v' => { 'w' => '1' } }] }). must_equal 'x%5By%5D%5B%5D%5Bv%5D%5Bw%5D=1' Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1', 'v' => { 'w' => '2' } }] }). must_equal 'x%5By%5D%5B%5D%5Bz%5D=1&x%5By%5D%5B%5D%5Bv%5D%5Bw%5D=2' Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1' }, { 'z' => '2' }] }). must_equal 'x%5By%5D%5B%5D%5Bz%5D=1&x%5By%5D%5B%5D%5Bz%5D=2' Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1', 'w' => 'a' }, { 'z' => '2', 'w' => '3' }] }). must_equal 'x%5By%5D%5B%5D%5Bz%5D=1&x%5By%5D%5B%5D%5Bw%5D=a&x%5By%5D%5B%5D%5Bz%5D=2&x%5By%5D%5B%5D%5Bw%5D=3' Rack::Utils.build_nested_query({ "foo" => ["1", ["2"]] }). must_equal 'foo%5B%5D=1&foo%5B%5D%5B%5D=2' # A nested hash is the same as string keys with brackets. Rack::Utils.build_nested_query('foo' => { 'bar' => 'baz' }). must_equal Rack::Utils.build_nested_query('foo[bar]' => 'baz') lambda { Rack::Utils.build_nested_query("foo=bar") }. must_raise(ArgumentError). message.must_equal "value must be a Hash" end it 'performs the inverse function of #parse_nested_query' do [{ "bar" => "" }, { "foo" => "bar", "baz" => "" }, { "foo" => ["1", "2"] }, { "foo" => "bar", "baz" => ["1", "2", "3"] }, { "foo" => ["bar"], "baz" => ["1", "2", "3"] }, { "foo" => ["1", "2"] }, { "foo" => "bar", "baz" => ["1", "2", "3"] }, { "x" => { "y" => { "z" => "1" } } }, { "x" => { "y" => { "z" => ["1"] } } }, { "x" => { "y" => { "z" => ["1", "2"] } } }, { "x" => { "y" => [{ "z" => "1" }] } }, { "x" => { "y" => [{ "z" => ["1"] }] } }, { "x" => { "y" => [{ "z" => "1", "w" => "2" }] } }, { "x" => { "y" => [{ "v" => { "w" => "1" } }] } }, { "x" => { "y" => [{ "z" => "1", "v" => { "w" => "2" } }] } }, { "x" => { "y" => [{ "z" => "1" }, { "z" => "2" }] } }, { "x" => { "y" => [{ "z" => "1", "w" => "a" }, { "z" => "2", "w" => "3" }] } }, { "foo" => ["1", ["2"]] }, ].each { |params| qs = Rack::Utils.build_nested_query(params) Rack::Utils.parse_nested_query(qs).must_equal params } lambda { Rack::Utils.build_nested_query("foo=bar") }. must_raise(ArgumentError). message.must_equal "value must be a Hash" end it "parse query strings that have a non-existent value" do key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit" Rack::Utils.parse_query(key).must_equal Rack::Utils.unescape(key) => nil end it "build query strings without = with non-existent values" do key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit" key = Rack::Utils.unescape(key) Rack::Utils.build_query(key => nil).must_equal Rack::Utils.escape(key) end it "parse q-values" do # XXX handle accept-extension Rack::Utils.q_values("foo;q=0.5,bar,baz;q=0.9").must_equal [ [ 'foo', 0.5 ], [ 'bar', 1.0 ], [ 'baz', 0.9 ] ] end it "parses RFC 7239 Forwarded header" do Rack::Utils.forwarded_values('for=3.4.5.6').must_equal({ for: [ '3.4.5.6' ], }) Rack::Utils.forwarded_values(';;;for=3.4.5.6,,').must_equal({ for: [ '3.4.5.6' ], }) Rack::Utils.forwarded_values('for=3.4.5.6').must_equal({ for: [ '3.4.5.6' ], }) Rack::Utils.forwarded_values('for = 3.4.5.6').must_equal({ for: [ '3.4.5.6' ], }) Rack::Utils.forwarded_values('for="3.4.5.6"').must_equal({ for: [ '3.4.5.6' ], }) Rack::Utils.forwarded_values('for=3.4.5.6;proto=https').must_equal({ for: [ '3.4.5.6' ], proto: [ 'https' ] }) Rack::Utils.forwarded_values('for=3.4.5.6; proto=http, proto=https').must_equal({ for: [ '3.4.5.6' ], proto: [ 'http', 'https' ] }) Rack::Utils.forwarded_values('for=3.4.5.6; proto=http, proto=https; for=1.2.3.4').must_equal({ for: [ '3.4.5.6', '1.2.3.4' ], proto: [ 'http', 'https' ] }) Rack::Utils.forwarded_values('for=3.4.5.6; foo=bar').must_be_nil end it "select best quality match" do Rack::Utils.best_q_match("text/html", %w[text/html]).must_equal "text/html" # More specific matches are preferred Rack::Utils.best_q_match("text/*;q=0.5,text/html;q=1.0", %w[text/html]).must_equal "text/html" # Higher quality matches are preferred Rack::Utils.best_q_match("text/*;q=0.5,text/plain;q=1.0", %w[text/plain text/html]).must_equal "text/plain" # Respect requested content type Rack::Utils.best_q_match("application/json", %w[application/vnd.lotus-1-2-3 application/json]).must_equal "application/json" # All else equal, the available mimes are preferred in order Rack::Utils.best_q_match("text/*", %w[text/html text/plain]).must_equal "text/html" Rack::Utils.best_q_match("text/plain,text/html", %w[text/html text/plain]).must_equal "text/html" # When there are no matches, return nil: Rack::Utils.best_q_match("application/json", %w[text/html text/plain]).must_be_nil end it "escape html entities [&><'\"/]" do Rack::Utils.escape_html("foo").must_equal "foo" Rack::Utils.escape_html("f&o").must_equal "f&o" Rack::Utils.escape_html("fo").must_equal "f>o" Rack::Utils.escape_html("f'o").must_equal "f'o" Rack::Utils.escape_html('f"o').must_equal "f"o" Rack::Utils.escape_html("").must_equal "<foo></foo>" Rack::Utils.escape_html("\300<").must_equal "\300<" end it "escape html entities in unicode strings" do # the following will cause warnings if the regex is poorly encoded: Rack::Utils.escape_html("☃").must_equal "☃" end it 'escape_html handles non-strings' do Rack::Utils.escape_html(nil).must_equal "" Rack::Utils.escape_html(123).must_equal "123" end it "figure out which encodings are acceptable" do helper = lambda do |a, b| Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a)) Rack::Utils.select_best_encoding(a, b) end helper.call(%w(), [["x", 1]]).must_be_nil helper.call(%w(identity), [["identity", 0.0]]).must_be_nil helper.call(%w(identity), [["*", 0.0]]).must_be_nil helper.call(%w(identity), [["compress", 1.0], ["gzip", 1.0]]).must_equal "identity" helper.call(%w(compress gzip identity), [["compress", 1.0], ["gzip", 1.0]]).must_equal "compress" helper.call(%w(compress gzip identity), [["compress", 0.5], ["gzip", 1.0]]).must_equal "gzip" helper.call(%w(compress gzip identity), [["gzip", 1.0], ["compress", 1.0]]).must_equal "compress" helper.call(%w(foo bar identity), []).must_equal "identity" helper.call(%w(foo bar identity), [["*", 1.0]]).must_equal "foo" helper.call(%w(foo bar identity), [["*", 1.0], ["foo", 0.9]]).must_equal "bar" helper.call(%w(foo bar identity), [["foo", 0], ["bar", 0]]).must_equal "identity" helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).must_equal "identity" end it "should perform constant time string comparison" do Rack::Utils.secure_compare('a', 'a').must_equal true Rack::Utils.secure_compare('a', 'b').must_equal false Rack::Utils.secure_compare('a', 'bb').must_equal false end it "return status code for integer" do Rack::Utils.status_code(200).must_equal 200 end it "return status code for string" do Rack::Utils.status_code("200").must_equal 200 end it "return status code for symbol" do Rack::Utils.status_code(:ok).must_equal 200 end it "return status code and give deprecation warning for obsolete symbols" do replaced_statuses = { payload_too_large: {status_code: 413, standard_symbol: :content_too_large}, unprocessable_entity: {status_code: 422, standard_symbol: :unprocessable_content} } dropped_statuses = {bandwidth_limit_exceeded: 509, not_extended: 510} capture_warnings(Rack::Utils) do |warnings| replaced_statuses.each do |symbol, value_hash| Rack::Utils.status_code(symbol).must_equal value_hash[:status_code] warnings.must_be_empty end dropped_statuses.each do |symbol, code| Rack::Utils.status_code(symbol).must_equal code warnings.pop.must_equal ["Status code #{symbol.inspect} is deprecated and will be removed in a future version of Rack.", { uplevel: 3 }] end end end it "raise an error for an invalid symbol" do error = assert_raises(ArgumentError) do Rack::Utils.status_code(:foobar) end error.message.must_equal "Unrecognized status code :foobar" end it "return rfc2822 format from rfc2822 helper" do Rack::Utils.rfc2822(Time.at(0).gmtime).must_equal "Thu, 01 Jan 1970 00:00:00 -0000" end it "clean directory traversal" do Rack::Utils.clean_path_info("/cgi/../cgi/test").must_equal "/cgi/test" Rack::Utils.clean_path_info(".").must_be_empty Rack::Utils.clean_path_info("test/..").must_be_empty end it "clean unsafe directory traversal to safe path" do Rack::Utils.clean_path_info("/../README.rdoc").must_equal "/README.rdoc" Rack::Utils.clean_path_info("../test/spec_utils.rb").must_equal "test/spec_utils.rb" end it "not clean directory traversal with encoded periods" do Rack::Utils.clean_path_info("/%2E%2E/README").must_equal "/%2E%2E/README" end it "clean slash only paths" do Rack::Utils.clean_path_info("/").must_equal "/" end end describe Rack::Utils, "cookies" do it "parses cookies" do env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "a=b; ; c=d") Rack::Utils.parse_cookies(env).must_equal({ "a" => "b", "c" => "d" }) env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "zoo=m") Rack::Utils.parse_cookies(env).must_equal({ "zoo" => "m" }) env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=%") Rack::Utils.parse_cookies(env).must_equal({ "foo" => "%" }) env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;foo=car") Rack::Utils.parse_cookies(env).must_equal({ "foo" => "bar" }) env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m") Rack::Utils.parse_cookies(env).must_equal({ "foo" => "bar", "quux" => "h&m" }) env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar; quux=h&m") Rack::Utils.parse_cookies(env).must_equal({ "foo" => "bar", "quux" => "h&m" }) env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar").freeze Rack::Utils.parse_cookies(env).must_equal({ "foo" => "bar" }) env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "%66oo=baz;foo=bar") cookies = Rack::Utils.parse_cookies(env) cookies.must_equal({ "%66oo" => "baz", "foo" => "bar" }) end it "generates appropriate cookie header value" do Rack::Utils.set_cookie_header('name', 'value').must_equal 'name=value' Rack::Utils.set_cookie_header('name', %w[value]).must_equal 'name=value' Rack::Utils.set_cookie_header('name', %w[va ue]).must_equal 'name=va&ue' end it "sets and deletes cookies in header hash" do headers = {} Rack::Utils.set_cookie_header!(headers, 'name', 'value') headers['set-cookie'].must_equal 'name=value' Rack::Utils.set_cookie_header!(headers, 'name2', 'value2') headers['set-cookie'].must_equal ['name=value', 'name2=value2'] Rack::Utils.set_cookie_header!(headers, 'name2', 'value3') headers['set-cookie'].must_equal ['name=value', 'name2=value2', 'name2=value3'] end it "encodes cookie key values by default" do capture_warnings(Rack::Utils) do |warnings| Rack::Utils.set_cookie_header('na e', 'value').must_equal 'na+e=value' warnings.pop.first.must_match(/Cookie key "na e" is not valid/) end end it "does not encode cookie key values if :escape_key is false" do Rack::Utils.set_cookie_header('na e', value: 'value', escape_key: false).must_equal 'na e=value' end it "sets partitioned cookie attribute" do Rack::Utils.set_cookie_header('name', {value: 'value', partitioned: true}).must_equal 'name=value; partitioned' end it "deletes cookies in header field" do header = [] Rack::Utils.delete_set_cookie_header!(header, 'name2') header.must_equal [ "name2=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" ] Rack::Utils.delete_set_cookie_header!(header, 'name') header.must_equal [ "name2=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT", "name=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" ] end it "deletes cookies in header field with domain" do header = [] Rack::Utils.delete_set_cookie_header!(header, 'name', {domain: "mydomain.com"}) header.must_equal [ "name=; domain=mydomain.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" ] end it "deletes cookies in header field with path" do header = [] Rack::Utils.delete_set_cookie_header!(header, 'name', {path: "/a/b"}) header.must_equal [ "name=; path=/a/b; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" ] end it "sets and deletes cookies in header hash" do header = { 'set-cookie' => nil } Rack::Utils.delete_cookie_header!(header, 'name').must_be_nil header['set-cookie'].must_equal "name=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" header = { 'set-cookie' => nil } Rack::Utils.delete_cookie_header!(header, 'name').must_be_nil header['set-cookie'].must_equal "name=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT" end end describe Rack::Utils, "get_byte_ranges" do it "returns an empty list if the sum of the ranges is too large" do assert_equal [], Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=0-20,0-500" }, 500) end it "parse simple byte ranges from env" do Rack::Utils.byte_ranges({ "HTTP_RANGE" => "bytes=123-456" }, 500).must_equal [(123..456)] end it "ignore missing or syntactically invalid byte ranges" do Rack::Utils.get_byte_ranges(nil, 500).must_be_nil Rack::Utils.get_byte_ranges("foobar", 500).must_be_nil Rack::Utils.get_byte_ranges("furlongs=123-456", 500).must_be_nil Rack::Utils.get_byte_ranges("bytes=", 500).must_be_nil Rack::Utils.get_byte_ranges("bytes=-", 500).must_be_nil Rack::Utils.get_byte_ranges("bytes=123,456", 500).must_be_nil # A range of non-positive length is syntactically invalid and ignored: Rack::Utils.get_byte_ranges("bytes=456-123", 500).must_be_nil Rack::Utils.get_byte_ranges("bytes=456-455", 500).must_be_nil end it "parse simple byte ranges" do Rack::Utils.get_byte_ranges("bytes=123-456", 500).must_equal [(123..456)] Rack::Utils.get_byte_ranges("bytes=123-", 500).must_equal [(123..499)] Rack::Utils.get_byte_ranges("bytes=-100", 500).must_equal [(400..499)] Rack::Utils.get_byte_ranges("bytes=0-0", 500).must_equal [(0..0)] Rack::Utils.get_byte_ranges("bytes=499-499", 500).must_equal [(499..499)] end it "parse several byte ranges" do Rack::Utils.get_byte_ranges("bytes=500-600,601-999", 1000).must_equal [(500..600), (601..999)] end it "truncate byte ranges" do Rack::Utils.get_byte_ranges("bytes=123-999", 500).must_equal [(123..499)] Rack::Utils.get_byte_ranges("bytes=600-999", 500).must_equal [] Rack::Utils.get_byte_ranges("bytes=-999", 500).must_equal [(0..499)] end it "ignore unsatisfiable byte ranges" do Rack::Utils.get_byte_ranges("bytes=500-501", 500).must_equal [] Rack::Utils.get_byte_ranges("bytes=500-", 500).must_equal [] Rack::Utils.get_byte_ranges("bytes=999-", 500).must_equal [] Rack::Utils.get_byte_ranges("bytes=-0", 500).must_equal [] end it "handle byte ranges of empty files" do Rack::Utils.get_byte_ranges("bytes=123-456", 0).must_be_nil Rack::Utils.get_byte_ranges("bytes=0-", 0).must_be_nil Rack::Utils.get_byte_ranges("bytes=-100", 0).must_be_nil Rack::Utils.get_byte_ranges("bytes=0-0", 0).must_be_nil Rack::Utils.get_byte_ranges("bytes=-0", 0).must_be_nil end end describe Rack::Utils::Context do class ContextTest attr_reader :app def initialize(app); @app = app; end def call(env); context env; end def context(env, app = @app); app.call(env); end end test_target1 = proc{|e| e.to_s + ' world' } test_target2 = proc{|e| e.to_i + 2 } test_target3 = proc{|e| nil } test_target4 = proc{|e| [200, { 'content-type' => 'text/plain', 'content-length' => '0' }, ['']] } test_app = ContextTest.new test_target4 it "set context correctly" do test_app.app.must_equal test_target4 c1 = Rack::Utils::Context.new(test_app, test_target1) c1.for.must_equal test_app c1.app.must_equal test_target1 c2 = Rack::Utils::Context.new(test_app, test_target2) c2.for.must_equal test_app c2.app.must_equal test_target2 end it "alter app on recontexting" do c1 = Rack::Utils::Context.new(test_app, test_target1) c2 = c1.recontext(test_target2) c2.for.must_equal test_app c2.app.must_equal test_target2 c3 = c2.recontext(test_target3) c3.for.must_equal test_app c3.app.must_equal test_target3 end it "run different apps" do c1 = Rack::Utils::Context.new test_app, test_target1 c2 = c1.recontext test_target2 c3 = c2.recontext test_target3 c4 = c3.recontext test_target4 a4 = Rack::Lint.new c4 a5 = Rack::Lint.new test_app r1 = c1.call('hello') r1.must_equal 'hello world' r2 = c2.call(2) r2.must_equal 4 r3 = c3.call(:misc_symbol) r3.must_be_nil r3 = c2.context(:misc_symbol, test_target3) r3.must_be_nil r4 = Rack::MockRequest.new(a4).get('/') r4.status.must_equal 200 r5 = Rack::MockRequest.new(a5).get('/') r5.status.must_equal 200 r4.body.must_equal r5.body end it "raises for invalid context" do proc do Rack::Utils::Context.new(nil, test_target1) end.must_raise RuntimeError end end rack-3.1.12/test/spec_version.rb000066400000000000000000000004071476365375000165420ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'helper' separate_testing do require_relative '../lib/rack/version' end describe Rack do describe 'version' do it 'is a version string' do Rack::RELEASE.must_match(/\d+\.\d+\.\d+/) end end end rack-3.1.12/test/static/000077500000000000000000000000001476365375000150045ustar00rootroot00000000000000rack-3.1.12/test/static/another/000077500000000000000000000000001476365375000164445ustar00rootroot00000000000000rack-3.1.12/test/static/another/index.html000066400000000000000000000000171476365375000204370ustar00rootroot00000000000000another index! rack-3.1.12/test/static/foo.html000066400000000000000000000000121476365375000164460ustar00rootroot00000000000000foo.html! rack-3.1.12/test/static/index.html000066400000000000000000000000071476365375000167760ustar00rootroot00000000000000index!