Merge pull request #15954 from Bo98/vendor-cleanup
vendor/bundle/ruby: cleanup unneeded files
This commit is contained in:
		
						commit
						e5018531ae
					
				
							
								
								
									
										54
									
								
								.github/workflows/vendor-gems.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								.github/workflows/vendor-gems.yml
									
									
									
									
										vendored
									
									
								
							@ -22,6 +22,60 @@ permissions:
 | 
			
		||||
  pull-requests: read
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  check-vendor-version:
 | 
			
		||||
    if: github.event_name == 'pull_request'
 | 
			
		||||
    runs-on: ubuntu-22.04
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Set up Homebrew
 | 
			
		||||
        id: set-up-homebrew
 | 
			
		||||
        uses: Homebrew/actions/setup-homebrew@master
 | 
			
		||||
        with:
 | 
			
		||||
          core: false
 | 
			
		||||
          cask: false
 | 
			
		||||
          test-bot: false
 | 
			
		||||
 | 
			
		||||
      - name: Install Bundler RubyGems
 | 
			
		||||
        run: brew install-bundler-gems --groups=all
 | 
			
		||||
 | 
			
		||||
      - name: Get Ruby ABI version
 | 
			
		||||
        id: ruby-abi
 | 
			
		||||
        run: echo "version=$(brew ruby -e "puts Gem.ruby_api_version")" >> "${GITHUB_OUTPUT}"
 | 
			
		||||
 | 
			
		||||
      - name: Get gem info
 | 
			
		||||
        id: gem-info
 | 
			
		||||
        working-directory: ${{ steps.set-up-homebrew.outputs.gems-path }}/${{ steps.ruby-abi.outputs.version }}/gems
 | 
			
		||||
        run: |
 | 
			
		||||
          {
 | 
			
		||||
            echo "vendor-version=$(cat ../.homebrew_vendor_version)"
 | 
			
		||||
            echo "ignored<<EOS"
 | 
			
		||||
            git check-ignore -- *
 | 
			
		||||
            echo "EOS"
 | 
			
		||||
          } >> "${GITHUB_OUTPUT}"
 | 
			
		||||
 | 
			
		||||
      - name: Compare to base ref
 | 
			
		||||
        working-directory: ${{ steps.set-up-homebrew.outputs.gems-path }}/${{ steps.ruby-abi.outputs.version }}
 | 
			
		||||
        run: |
 | 
			
		||||
          git checkout "origin/${GITHUB_BASE_REF}"
 | 
			
		||||
          rm .homebrew_vendor_version
 | 
			
		||||
          brew install-bundler-gems --groups=all
 | 
			
		||||
          if [[ "$(cat .homebrew_vendor_version)" == "${{ steps.gem-info.outputs.vendor-version }}" ]]; then
 | 
			
		||||
            ignored_gems="${{ steps.gem-info.outputs.ignored }}"
 | 
			
		||||
            while IFS= read -r gem; do
 | 
			
		||||
              gem_dir="./gems/${gem}"
 | 
			
		||||
              [[ -d "${gem_dir}" ]] || continue
 | 
			
		||||
              exit_code=0
 | 
			
		||||
              git check-ignore --quiet "${gem_dir}" || exit_code=$?
 | 
			
		||||
              if (( exit_code != 0 )); then
 | 
			
		||||
                if (( exit_code == 1 )); then
 | 
			
		||||
                  echo "::error::VENDOR_VERSION needs bumping in utils/gems.rb" >&2
 | 
			
		||||
                else
 | 
			
		||||
                  echo "::error::git check-ignore failed" >&2
 | 
			
		||||
                fi
 | 
			
		||||
                exit "${exit_code}"
 | 
			
		||||
              fi
 | 
			
		||||
            done <<< "${ignored_gems}"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
  vendor-gems:
 | 
			
		||||
    if: >
 | 
			
		||||
      github.repository_owner == 'Homebrew' && (
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										221
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										221
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -27,6 +27,7 @@
 | 
			
		||||
**/.bundle/bin
 | 
			
		||||
**/.bundle/cache
 | 
			
		||||
**/vendor/bundle/ruby/.homebrew_gem_groups
 | 
			
		||||
**/vendor/bundle/ruby/*/.homebrew_vendor_version
 | 
			
		||||
**/vendor/bundle/ruby/*/bundler.lock
 | 
			
		||||
**/vendor/bundle/ruby/*/bin
 | 
			
		||||
**/vendor/bundle/ruby/*/build_info/
 | 
			
		||||
@ -43,6 +44,7 @@
 | 
			
		||||
**/.yardoc
 | 
			
		||||
 | 
			
		||||
# Unignore vendored gems
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/*/*LICENSE*
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/*/lib
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/addressable-*/data
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/public_suffix-*/data
 | 
			
		||||
@ -51,181 +53,30 @@
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/rubocop-rspec-*/config
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/rubocop-sorbet-*/config
 | 
			
		||||
 | 
			
		||||
# Ignore activesupport files we don't need
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/cache/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/array.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/array/conversions.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/array/extract.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/array/extract_options.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/array/grouping.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/array/inquiry.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/array/wrap.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/benchmark.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/big_decimal.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/big_decimal/conversions.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/class.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/class/attribute.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/class/attribute_accessors.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/class/subclasses.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date/acts_like.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date/blank.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date/calculations.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date/conversions.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date/zones.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date_and_time/calculations.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date_and_time/compatibility.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date_and_time/zones.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date_time.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date_time/acts_like.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date_time/blank.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date_time/calculations.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date_time/compatibility.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/date_time/conversions.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/digest.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/digest/uuid.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/file.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/hash.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/hash/conversions.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/hash/indifferent_access.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/hash/reverse_merge.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/integer.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/integer/inflections.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/integer/multiple.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/integer/time.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/kernel.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/kernel/concern.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/kernel/reporting.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/kernel/singleton_class.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/load_error.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/marshal.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/module.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/name_error.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/numeric.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/acts_like.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/conversions.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/inclusion.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/instance_variables.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/json.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/to_param.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/to_query.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/with_options.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/range.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/range/compare_range.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/range/conversions.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/range/each.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/range/include_time_with_zone.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/range/overlaps.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/regexp.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/securerandom.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string/access.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string/behavior.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string/conversions.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string/inquiry.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string/output_safety.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string/starts_ends_with.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string/strip.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string/zones.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/symbol.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/symbol/starts_ends_with.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/time.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/time/acts_like.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/time/calculations.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/time/compatibility.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/time/conversions.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/time/zones.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/uri.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/concurrency/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/current_attributes/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/dependencies/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/deprecation/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/duration/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/json/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/locale/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/log_subscriber/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/messages/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/multibyte/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/notifications/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/number_helper/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/testing/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/values/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/xml_mini/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/actionable_error.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/all.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/array_inquirer.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/backtrace_cleaner.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/benchmarkable.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/builder.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/cache.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/callbacks.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/concern.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/configurable.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/configuration_file.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/current_attributes.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/dependencies.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/deprecation.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/descendants_tracker.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/digest.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/duration.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/encrypted_configuration.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/encrypted_file.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/environment_inquirer.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/evented_file_update_checker.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/execution_wrapper.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/executor.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/file_update_checker.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/fork_tracker.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/gem_version.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/gzip.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/hash_with_indifferent_access.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/i18n_railtie.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/json.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/key_generator.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/log_subscriber.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/logger.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/logger_silence.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/logger_thread_safe_level.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/message_encryptor.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/message_verifier.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/notifications.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/number_helper.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/option_merger.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/ordered_hash.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/ordered_options.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/parameter_filter.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/per_thread_registry.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/proxy_object.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/rails.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/railtie.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/reloader.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/rescuable.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/secure_compare_rotator.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/security_utils.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/string_inquirer.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/subscriber.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/tagged_logging.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/test_case.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/time.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/time_with_zone.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/version.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/xml_mini.rb
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support.rb
 | 
			
		||||
# Ignore activesupport, except the ones we need.
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/**/*
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/*/
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/array/access.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/enumerable.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/file/atomic.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/hash/deep_merge.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/hash/deep_transform_values.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/hash/except.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/hash/keys.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/hash/slice.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/blank.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/deep_dup.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/duplicable.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/object/try.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string/exclude.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string/filters.rb
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support/core_ext/string/indent.rb
 | 
			
		||||
 | 
			
		||||
# Ignore partially included gems where we don't need all files
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/concurrent-ruby-*/lib/atomic/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/concurrent-ruby-*/lib/atomic_reference/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/concurrent-ruby-*/lib/collection/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/concurrent-ruby-*/lib/concern/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/concurrent-ruby-*/lib/executor/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/concurrent-ruby-*/lib/synchronization/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/concurrent-ruby-*/lib/thread_safe/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/concurrent-ruby-*/lib/utility/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/concurrent-ruby-*/lib/*/*.jar
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/i18n-*/lib/i18n/tests*
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/thread_safe-*/lib/thread_safe/util
 | 
			
		||||
**/vendor/gems/mechanize-*/.*
 | 
			
		||||
**/vendor/gems/mechanize-*/*.md
 | 
			
		||||
**/vendor/gems/mechanize-*/*.rdoc
 | 
			
		||||
@ -233,21 +84,11 @@
 | 
			
		||||
**/vendor/gems/mechanize-*/Gemfile
 | 
			
		||||
**/vendor/gems/mechanize-*/Rakefile
 | 
			
		||||
**/vendor/gems/mechanize-*/examples/
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/*.rb
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/*.rb
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/http/agent.rb
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/http/*auth*.rb
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/c*
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/d*
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/e*
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/f*
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/h*.rb
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/i*
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/p*
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/r*
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/t*
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/u*
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/mechanize/x*
 | 
			
		||||
**/vendor/gems/mechanize-*/lib/**/*
 | 
			
		||||
!**/vendor/gems/mechanize-*/lib/mechanize/
 | 
			
		||||
!**/vendor/gems/mechanize-*/lib/mechanize/http/
 | 
			
		||||
!**/vendor/gems/mechanize-*/lib/mechanize/http/content_disposition_parser.rb
 | 
			
		||||
!**/vendor/gems/mechanize-*/lib/mechanize/version.rb
 | 
			
		||||
**/vendor/gems/mechanize-*/test/
 | 
			
		||||
 | 
			
		||||
# Ignore dependencies we don't wish to vendor
 | 
			
		||||
@ -281,6 +122,7 @@
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/psych-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/pry-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/racc-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rack-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rainbow-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rbi-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rdiscount-*/
 | 
			
		||||
@ -299,9 +141,16 @@
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rspec-wait-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rubocop-1*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rubocop-ast-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rubocop-capybara-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rubocop-performance-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rubocop-rails-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rubocop-rspec-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/rubocop-sorbet-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/ruby-prof-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/ruby-progressbar-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/simplecov-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/simplecov-html-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/simplecov_json_formatter-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/simpleidn-*/
 | 
			
		||||
**/vendor/bundle/ruby/*/gems/sorbet-*/
 | 
			
		||||
!**/vendor/bundle/ruby/*/gems/sorbet-runtime-*/
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,6 @@ require "livecheck/livecheck_version"
 | 
			
		||||
require "livecheck/skip_conditions"
 | 
			
		||||
require "livecheck/strategy"
 | 
			
		||||
require "addressable"
 | 
			
		||||
require "ruby-progressbar"
 | 
			
		||||
require "uri"
 | 
			
		||||
 | 
			
		||||
module Homebrew
 | 
			
		||||
@ -208,6 +207,7 @@ module Homebrew
 | 
			
		||||
          stderr.puts Formatter.headline("Running checks", color: :blue)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        require "ruby-progressbar"
 | 
			
		||||
        progress = ProgressBar.create(
 | 
			
		||||
          total:          formulae_and_casks_total,
 | 
			
		||||
          progress_mark:  "#",
 | 
			
		||||
 | 
			
		||||
@ -46,3 +46,5 @@ module Kernel
 | 
			
		||||
  sig { params(arg0: NilClass).returns(NilClass) }
 | 
			
		||||
  def set_trace_func(arg0); end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
class Gem::Security::Exception < Gem::Exception; end
 | 
			
		||||
 | 
			
		||||
@ -12,9 +12,24 @@ module Homebrew
 | 
			
		||||
  # After updating this, run `brew vendor-gems --update=--bundler`.
 | 
			
		||||
  HOMEBREW_BUNDLER_VERSION = "2.4.18"
 | 
			
		||||
 | 
			
		||||
  GEM_GROUPS_FILE = (HOMEBREW_LIBRARY_PATH/"vendor/bundle/ruby/.homebrew_gem_groups").freeze
 | 
			
		||||
  # Bump this whenever a committed vendored gem is later added to gitignore.
 | 
			
		||||
  # This will trigger it to reinstall properly if `brew install-bundler-gems` needs it.
 | 
			
		||||
  VENDOR_VERSION = 1
 | 
			
		||||
  private_constant :VENDOR_VERSION
 | 
			
		||||
 | 
			
		||||
  RUBY_BUNDLE_VENDOR_DIRECTORY = (HOMEBREW_LIBRARY_PATH/"vendor/bundle/ruby").freeze
 | 
			
		||||
  private_constant :RUBY_BUNDLE_VENDOR_DIRECTORY
 | 
			
		||||
 | 
			
		||||
  # This is tracked across Ruby versions.
 | 
			
		||||
  GEM_GROUPS_FILE = (RUBY_BUNDLE_VENDOR_DIRECTORY/".homebrew_gem_groups").freeze
 | 
			
		||||
  private_constant :GEM_GROUPS_FILE
 | 
			
		||||
 | 
			
		||||
  # This is tracked per Ruby version.
 | 
			
		||||
  VENDOR_VERSION_FILE = (
 | 
			
		||||
    RUBY_BUNDLE_VENDOR_DIRECTORY/"#{RbConfig::CONFIG["ruby_version"]}/.homebrew_vendor_version"
 | 
			
		||||
  ).freeze
 | 
			
		||||
  private_constant :VENDOR_VERSION_FILE
 | 
			
		||||
 | 
			
		||||
  module_function
 | 
			
		||||
 | 
			
		||||
  # @api private
 | 
			
		||||
@ -22,6 +37,11 @@ module Homebrew
 | 
			
		||||
    File.join(ENV.fetch("HOMEBREW_LIBRARY"), "Homebrew", "Gemfile")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # @api private
 | 
			
		||||
  def bundler_definition
 | 
			
		||||
    @bundler_definition ||= Bundler::Definition.build(Bundler.default_gemfile, Bundler.default_lockfile, false)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # @api private
 | 
			
		||||
  def valid_gem_groups
 | 
			
		||||
    install_bundler!
 | 
			
		||||
@ -29,7 +49,7 @@ module Homebrew
 | 
			
		||||
 | 
			
		||||
    Bundler.with_unbundled_env do
 | 
			
		||||
      ENV["BUNDLE_GEMFILE"] = gemfile
 | 
			
		||||
      groups = Bundler::Definition.build(Bundler.default_gemfile, Bundler.default_lockfile, false).groups
 | 
			
		||||
      groups = bundler_definition.groups
 | 
			
		||||
      groups.delete(:default)
 | 
			
		||||
      groups.map(&:to_s)
 | 
			
		||||
    end
 | 
			
		||||
@ -71,7 +91,7 @@ module Homebrew
 | 
			
		||||
    ENV["BUNDLER_NO_OLD_RUBYGEMS_WARNING"] = "1"
 | 
			
		||||
 | 
			
		||||
    # Match where our bundler gems are.
 | 
			
		||||
    gem_home = "#{HOMEBREW_LIBRARY_PATH}/vendor/bundle/ruby/#{RbConfig::CONFIG["ruby_version"]}"
 | 
			
		||||
    gem_home = "#{RUBY_BUNDLE_VENDOR_DIRECTORY}/#{RbConfig::CONFIG["ruby_version"]}"
 | 
			
		||||
    Gem.paths = {
 | 
			
		||||
      "GEM_HOME" => gem_home,
 | 
			
		||||
      "GEM_PATH" => gem_home,
 | 
			
		||||
@ -181,6 +201,14 @@ module Homebrew
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def user_vendor_version
 | 
			
		||||
    @user_vendor_version ||= if VENDOR_VERSION_FILE.exist?
 | 
			
		||||
      VENDOR_VERSION_FILE.read.to_i
 | 
			
		||||
    else
 | 
			
		||||
      0
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def install_bundler_gems!(only_warn_on_failure: false, setup_path: true, groups: [])
 | 
			
		||||
    old_path = ENV.fetch("PATH", nil)
 | 
			
		||||
    old_gem_path = ENV.fetch("GEM_PATH", nil)
 | 
			
		||||
@ -229,7 +257,49 @@ module Homebrew
 | 
			
		||||
      bundle_check_failed = !$CHILD_STATUS.success?
 | 
			
		||||
 | 
			
		||||
      # for some reason sometimes the exit code lies so check the output too.
 | 
			
		||||
      bundle_installed = if bundle_check_failed || bundle_check_output.include?("Install missing gems")
 | 
			
		||||
      bundle_install_required = bundle_check_failed || bundle_check_output.include?("Install missing gems")
 | 
			
		||||
 | 
			
		||||
      if user_vendor_version != VENDOR_VERSION
 | 
			
		||||
        # Check if the install is intact. This is useful if any gems are added to gitignore.
 | 
			
		||||
        # We intentionally map over everything and then call `any?` so that we remove the spec of each bad gem.
 | 
			
		||||
        specs = bundler_definition.resolve.materialize(bundler_definition.locked_dependencies)
 | 
			
		||||
        vendor_reinstall_required = specs.map do |spec|
 | 
			
		||||
          spec_file = "#{Gem.dir}/specifications/#{spec.full_name}.gemspec"
 | 
			
		||||
          next false unless File.exist?(spec_file)
 | 
			
		||||
 | 
			
		||||
          cache_file = "#{Gem.dir}/cache/#{spec.full_name}.gem"
 | 
			
		||||
          if File.exist?(cache_file)
 | 
			
		||||
            require "rubygems/package"
 | 
			
		||||
            package = Gem::Package.new(cache_file)
 | 
			
		||||
 | 
			
		||||
            package_install_intact = begin
 | 
			
		||||
              contents = package.contents
 | 
			
		||||
 | 
			
		||||
              # If the gem has contents, ensure we have every file installed it contains.
 | 
			
		||||
              contents&.all? do |gem_file|
 | 
			
		||||
                File.exist?("#{Gem.dir}/gems/#{spec.full_name}/#{gem_file}")
 | 
			
		||||
              end
 | 
			
		||||
            rescue Gem::Package::Error, Gem::Security::Exception
 | 
			
		||||
              # Malformed, assume broken
 | 
			
		||||
              File.unlink(cache_file)
 | 
			
		||||
              false
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
            next false if package_install_intact
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          # Mark gem for reinstallation
 | 
			
		||||
          File.unlink(spec_file)
 | 
			
		||||
          true
 | 
			
		||||
        end.any?
 | 
			
		||||
 | 
			
		||||
        VENDOR_VERSION_FILE.dirname.mkpath
 | 
			
		||||
        VENDOR_VERSION_FILE.write(VENDOR_VERSION.to_s)
 | 
			
		||||
 | 
			
		||||
        bundle_install_required ||= vendor_reinstall_required
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      bundle_installed = if bundle_install_required
 | 
			
		||||
        if system bundle, "install"
 | 
			
		||||
          true
 | 
			
		||||
        else
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/activesupport-6.1.7.6/MIT-LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/activesupport-6.1.7.6/MIT-LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
Copyright (c) 2005-2022 David Heinemeier Hansson
 | 
			
		||||
 | 
			
		||||
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 OR COPYRIGHT HOLDERS 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.
 | 
			
		||||
@ -1,31 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Module
 | 
			
		||||
  # Allows you to make aliases for attributes, which includes
 | 
			
		||||
  # getter, setter, and a predicate.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Content < ActiveRecord::Base
 | 
			
		||||
  #     # has a title attribute
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   class Email < Content
 | 
			
		||||
  #     alias_attribute :subject, :title
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   e = Email.find(1)
 | 
			
		||||
  #   e.title    # => "Superstars"
 | 
			
		||||
  #   e.subject  # => "Superstars"
 | 
			
		||||
  #   e.subject? # => true
 | 
			
		||||
  #   e.subject = "Megastars"
 | 
			
		||||
  #   e.title    # => "Megastars"
 | 
			
		||||
  def alias_attribute(new_name, old_name)
 | 
			
		||||
    # The following reader methods use an explicit `self` receiver in order to
 | 
			
		||||
    # support aliases that start with an uppercase letter. Otherwise, they would
 | 
			
		||||
    # be resolved as constants instead.
 | 
			
		||||
    module_eval <<-STR, __FILE__, __LINE__ + 1
 | 
			
		||||
      def #{new_name}; self.#{old_name}; end          # def subject; self.title; end
 | 
			
		||||
      def #{new_name}?; self.#{old_name}?; end        # def subject?; self.title?; end
 | 
			
		||||
      def #{new_name}=(v); self.#{old_name} = v; end  # def subject=(v); self.title = v; end
 | 
			
		||||
    STR
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,30 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Module
 | 
			
		||||
  # A module may or may not have a name.
 | 
			
		||||
  #
 | 
			
		||||
  #   module M; end
 | 
			
		||||
  #   M.name # => "M"
 | 
			
		||||
  #
 | 
			
		||||
  #   m = Module.new
 | 
			
		||||
  #   m.name # => nil
 | 
			
		||||
  #
 | 
			
		||||
  # +anonymous?+ method returns true if module does not have a name, false otherwise:
 | 
			
		||||
  #
 | 
			
		||||
  #   Module.new.anonymous? # => true
 | 
			
		||||
  #
 | 
			
		||||
  #   module M; end
 | 
			
		||||
  #   M.anonymous?          # => false
 | 
			
		||||
  #
 | 
			
		||||
  # A module gets a name when it is first assigned to a constant. Either
 | 
			
		||||
  # via the +module+ or +class+ keyword or by an explicit assignment:
 | 
			
		||||
  #
 | 
			
		||||
  #   m = Module.new # creates an anonymous module
 | 
			
		||||
  #   m.anonymous?   # => true
 | 
			
		||||
  #   M = m          # m gets a name here as a side-effect
 | 
			
		||||
  #   m.name         # => "M"
 | 
			
		||||
  #   m.anonymous?   # => false
 | 
			
		||||
  def anonymous?
 | 
			
		||||
    name.nil?
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,38 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Module
 | 
			
		||||
  # Declares an attribute reader backed by an internally-named instance variable.
 | 
			
		||||
  def attr_internal_reader(*attrs)
 | 
			
		||||
    attrs.each { |attr_name| attr_internal_define(attr_name, :reader) }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Declares an attribute writer backed by an internally-named instance variable.
 | 
			
		||||
  def attr_internal_writer(*attrs)
 | 
			
		||||
    attrs.each { |attr_name| attr_internal_define(attr_name, :writer) }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Declares an attribute reader and writer backed by an internally-named instance
 | 
			
		||||
  # variable.
 | 
			
		||||
  def attr_internal_accessor(*attrs)
 | 
			
		||||
    attr_internal_reader(*attrs)
 | 
			
		||||
    attr_internal_writer(*attrs)
 | 
			
		||||
  end
 | 
			
		||||
  alias_method :attr_internal, :attr_internal_accessor
 | 
			
		||||
 | 
			
		||||
  class << self; attr_accessor :attr_internal_naming_format end
 | 
			
		||||
  self.attr_internal_naming_format = "@_%s"
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
    def attr_internal_ivar_name(attr)
 | 
			
		||||
      Module.attr_internal_naming_format % attr
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def attr_internal_define(attr_name, type)
 | 
			
		||||
      internal_name = attr_internal_ivar_name(attr_name).delete_prefix("@")
 | 
			
		||||
      # use native attr_* methods as they are faster on some Ruby implementations
 | 
			
		||||
      public_send("attr_#{type}", internal_name)
 | 
			
		||||
      attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
 | 
			
		||||
      alias_method attr_name, internal_name
 | 
			
		||||
      remove_method internal_name
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
@ -1,206 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
# Extends the module object with class/module and instance accessors for
 | 
			
		||||
# class/module attributes, just like the native attr* accessors for instance
 | 
			
		||||
# attributes.
 | 
			
		||||
class Module
 | 
			
		||||
  # Defines a class attribute and creates a class and instance reader methods.
 | 
			
		||||
  # The underlying class variable is set to +nil+, if it is not previously
 | 
			
		||||
  # defined. All class and instance methods created will be public, even if
 | 
			
		||||
  # this method is called with a private or protected access modifier.
 | 
			
		||||
  #
 | 
			
		||||
  #   module HairColors
 | 
			
		||||
  #     mattr_reader :hair_colors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   HairColors.hair_colors # => nil
 | 
			
		||||
  #   HairColors.class_variable_set("@@hair_colors", [:brown, :black])
 | 
			
		||||
  #   HairColors.hair_colors # => [:brown, :black]
 | 
			
		||||
  #
 | 
			
		||||
  # The attribute name must be a valid method name in Ruby.
 | 
			
		||||
  #
 | 
			
		||||
  #   module Foo
 | 
			
		||||
  #     mattr_reader :"1_Badname"
 | 
			
		||||
  #   end
 | 
			
		||||
  #   # => NameError: invalid attribute name: 1_Badname
 | 
			
		||||
  #
 | 
			
		||||
  # To omit the instance reader method, pass
 | 
			
		||||
  # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
 | 
			
		||||
  #
 | 
			
		||||
  #   module HairColors
 | 
			
		||||
  #     mattr_reader :hair_colors, instance_reader: false
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   class Person
 | 
			
		||||
  #     include HairColors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Person.new.hair_colors # => NoMethodError
 | 
			
		||||
  #
 | 
			
		||||
  # You can set a default value for the attribute.
 | 
			
		||||
  #
 | 
			
		||||
  #   module HairColors
 | 
			
		||||
  #     mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   class Person
 | 
			
		||||
  #     include HairColors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Person.new.hair_colors # => [:brown, :black, :blonde, :red]
 | 
			
		||||
  def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil, location: nil)
 | 
			
		||||
    raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
 | 
			
		||||
    location ||= caller_locations(1, 1).first
 | 
			
		||||
 | 
			
		||||
    definition = []
 | 
			
		||||
    syms.each do |sym|
 | 
			
		||||
      raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
 | 
			
		||||
 | 
			
		||||
      definition << "def self.#{sym}; @@#{sym}; end"
 | 
			
		||||
 | 
			
		||||
      if instance_reader && instance_accessor
 | 
			
		||||
        definition << "def #{sym}; @@#{sym}; end"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sym_default_value = (block_given? && default.nil?) ? yield : default
 | 
			
		||||
      class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    module_eval(definition.join(";"), location.path, location.lineno)
 | 
			
		||||
  end
 | 
			
		||||
  alias :cattr_reader :mattr_reader
 | 
			
		||||
 | 
			
		||||
  # Defines a class attribute and creates a class and instance writer methods to
 | 
			
		||||
  # allow assignment to the attribute. All class and instance methods created
 | 
			
		||||
  # will be public, even if this method is called with a private or protected
 | 
			
		||||
  # access modifier.
 | 
			
		||||
  #
 | 
			
		||||
  #   module HairColors
 | 
			
		||||
  #     mattr_writer :hair_colors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   class Person
 | 
			
		||||
  #     include HairColors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   HairColors.hair_colors = [:brown, :black]
 | 
			
		||||
  #   Person.class_variable_get("@@hair_colors") # => [:brown, :black]
 | 
			
		||||
  #   Person.new.hair_colors = [:blonde, :red]
 | 
			
		||||
  #   HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red]
 | 
			
		||||
  #
 | 
			
		||||
  # To omit the instance writer method, pass
 | 
			
		||||
  # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
 | 
			
		||||
  #
 | 
			
		||||
  #   module HairColors
 | 
			
		||||
  #     mattr_writer :hair_colors, instance_writer: false
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   class Person
 | 
			
		||||
  #     include HairColors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Person.new.hair_colors = [:blonde, :red] # => NoMethodError
 | 
			
		||||
  #
 | 
			
		||||
  # You can set a default value for the attribute.
 | 
			
		||||
  #
 | 
			
		||||
  #   module HairColors
 | 
			
		||||
  #     mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red]
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   class Person
 | 
			
		||||
  #     include HairColors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
 | 
			
		||||
  def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil, location: nil)
 | 
			
		||||
    raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
 | 
			
		||||
    location ||= caller_locations(1, 1).first
 | 
			
		||||
 | 
			
		||||
    definition = []
 | 
			
		||||
    syms.each do |sym|
 | 
			
		||||
      raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
 | 
			
		||||
      definition << "def self.#{sym}=(val); @@#{sym} = val; end"
 | 
			
		||||
 | 
			
		||||
      if instance_writer && instance_accessor
 | 
			
		||||
        definition << "def #{sym}=(val); @@#{sym} = val; end"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sym_default_value = (block_given? && default.nil?) ? yield : default
 | 
			
		||||
      class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    module_eval(definition.join(";"), location.path, location.lineno)
 | 
			
		||||
  end
 | 
			
		||||
  alias :cattr_writer :mattr_writer
 | 
			
		||||
 | 
			
		||||
  # Defines both class and instance accessors for class attributes.
 | 
			
		||||
  # All class and instance methods created will be public, even if
 | 
			
		||||
  # this method is called with a private or protected access modifier.
 | 
			
		||||
  #
 | 
			
		||||
  #   module HairColors
 | 
			
		||||
  #     mattr_accessor :hair_colors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   class Person
 | 
			
		||||
  #     include HairColors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   HairColors.hair_colors = [:brown, :black, :blonde, :red]
 | 
			
		||||
  #   HairColors.hair_colors # => [:brown, :black, :blonde, :red]
 | 
			
		||||
  #   Person.new.hair_colors # => [:brown, :black, :blonde, :red]
 | 
			
		||||
  #
 | 
			
		||||
  # If a subclass changes the value then that would also change the value for
 | 
			
		||||
  # parent class. Similarly if parent class changes the value then that would
 | 
			
		||||
  # change the value of subclasses too.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Citizen < Person
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Citizen.new.hair_colors << :blue
 | 
			
		||||
  #   Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue]
 | 
			
		||||
  #
 | 
			
		||||
  # To omit the instance writer method, pass <tt>instance_writer: false</tt>.
 | 
			
		||||
  # To omit the instance reader method, pass <tt>instance_reader: false</tt>.
 | 
			
		||||
  #
 | 
			
		||||
  #   module HairColors
 | 
			
		||||
  #     mattr_accessor :hair_colors, instance_writer: false, instance_reader: false
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   class Person
 | 
			
		||||
  #     include HairColors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Person.new.hair_colors = [:brown]  # => NoMethodError
 | 
			
		||||
  #   Person.new.hair_colors             # => NoMethodError
 | 
			
		||||
  #
 | 
			
		||||
  # Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
 | 
			
		||||
  #
 | 
			
		||||
  #   module HairColors
 | 
			
		||||
  #     mattr_accessor :hair_colors, instance_accessor: false
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   class Person
 | 
			
		||||
  #     include HairColors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Person.new.hair_colors = [:brown]  # => NoMethodError
 | 
			
		||||
  #   Person.new.hair_colors             # => NoMethodError
 | 
			
		||||
  #
 | 
			
		||||
  # You can set a default value for the attribute.
 | 
			
		||||
  #
 | 
			
		||||
  #   module HairColors
 | 
			
		||||
  #     mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red]
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   class Person
 | 
			
		||||
  #     include HairColors
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
 | 
			
		||||
  def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
 | 
			
		||||
    location = caller_locations(1, 1).first
 | 
			
		||||
    mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, location: location, &blk)
 | 
			
		||||
    mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default, location: location)
 | 
			
		||||
  end
 | 
			
		||||
  alias :cattr_accessor :mattr_accessor
 | 
			
		||||
end
 | 
			
		||||
@ -1,148 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
# Extends the module object with class/module and instance accessors for
 | 
			
		||||
# class/module attributes, just like the native attr* accessors for instance
 | 
			
		||||
# attributes, but does so on a per-thread basis.
 | 
			
		||||
#
 | 
			
		||||
# So the values are scoped within the Thread.current space under the class name
 | 
			
		||||
# of the module.
 | 
			
		||||
class Module
 | 
			
		||||
  # Defines a per-thread class attribute and creates class and instance reader methods.
 | 
			
		||||
  # The underlying per-thread class variable is set to +nil+, if it is not previously defined.
 | 
			
		||||
  #
 | 
			
		||||
  #   module Current
 | 
			
		||||
  #     thread_mattr_reader :user
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Current.user # => nil
 | 
			
		||||
  #   Thread.current[:attr_Current_user] = "DHH"
 | 
			
		||||
  #   Current.user # => "DHH"
 | 
			
		||||
  #
 | 
			
		||||
  # The attribute name must be a valid method name in Ruby.
 | 
			
		||||
  #
 | 
			
		||||
  #   module Foo
 | 
			
		||||
  #     thread_mattr_reader :"1_Badname"
 | 
			
		||||
  #   end
 | 
			
		||||
  #   # => NameError: invalid attribute name: 1_Badname
 | 
			
		||||
  #
 | 
			
		||||
  # To omit the instance reader method, pass
 | 
			
		||||
  # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Current
 | 
			
		||||
  #     thread_mattr_reader :user, instance_reader: false
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Current.new.user # => NoMethodError
 | 
			
		||||
  def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil) # :nodoc:
 | 
			
		||||
    syms.each do |sym|
 | 
			
		||||
      raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
 | 
			
		||||
 | 
			
		||||
      # The following generated method concatenates `name` because we want it
 | 
			
		||||
      # to work with inheritance via polymorphism.
 | 
			
		||||
      class_eval(<<-EOS, __FILE__, __LINE__ + 1)
 | 
			
		||||
        def self.#{sym}
 | 
			
		||||
          Thread.current["attr_" + name + "_#{sym}"]
 | 
			
		||||
        end
 | 
			
		||||
      EOS
 | 
			
		||||
 | 
			
		||||
      if instance_reader && instance_accessor
 | 
			
		||||
        class_eval(<<-EOS, __FILE__, __LINE__ + 1)
 | 
			
		||||
          def #{sym}
 | 
			
		||||
            self.class.#{sym}
 | 
			
		||||
          end
 | 
			
		||||
        EOS
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      Thread.current["attr_" + name + "_#{sym}"] = default unless default.nil?
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
  alias :thread_cattr_reader :thread_mattr_reader
 | 
			
		||||
 | 
			
		||||
  # Defines a per-thread class attribute and creates a class and instance writer methods to
 | 
			
		||||
  # allow assignment to the attribute.
 | 
			
		||||
  #
 | 
			
		||||
  #   module Current
 | 
			
		||||
  #     thread_mattr_writer :user
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Current.user = "DHH"
 | 
			
		||||
  #   Thread.current[:attr_Current_user] # => "DHH"
 | 
			
		||||
  #
 | 
			
		||||
  # To omit the instance writer method, pass
 | 
			
		||||
  # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Current
 | 
			
		||||
  #     thread_mattr_writer :user, instance_writer: false
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Current.new.user = "DHH" # => NoMethodError
 | 
			
		||||
  def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil) # :nodoc:
 | 
			
		||||
    syms.each do |sym|
 | 
			
		||||
      raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
 | 
			
		||||
 | 
			
		||||
      # The following generated method concatenates `name` because we want it
 | 
			
		||||
      # to work with inheritance via polymorphism.
 | 
			
		||||
      class_eval(<<-EOS, __FILE__, __LINE__ + 1)
 | 
			
		||||
        def self.#{sym}=(obj)
 | 
			
		||||
          Thread.current["attr_" + name + "_#{sym}"] = obj
 | 
			
		||||
        end
 | 
			
		||||
      EOS
 | 
			
		||||
 | 
			
		||||
      if instance_writer && instance_accessor
 | 
			
		||||
        class_eval(<<-EOS, __FILE__, __LINE__ + 1)
 | 
			
		||||
          def #{sym}=(obj)
 | 
			
		||||
            self.class.#{sym} = obj
 | 
			
		||||
          end
 | 
			
		||||
        EOS
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      public_send("#{sym}=", default) unless default.nil?
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
  alias :thread_cattr_writer :thread_mattr_writer
 | 
			
		||||
 | 
			
		||||
  # Defines both class and instance accessors for class attributes.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Account
 | 
			
		||||
  #     thread_mattr_accessor :user
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Account.user = "DHH"
 | 
			
		||||
  #   Account.user     # => "DHH"
 | 
			
		||||
  #   Account.new.user # => "DHH"
 | 
			
		||||
  #
 | 
			
		||||
  # If a subclass changes the value, the parent class' value is not changed.
 | 
			
		||||
  # Similarly, if the parent class changes the value, the value of subclasses
 | 
			
		||||
  # is not changed.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Customer < Account
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Customer.user = "Rafael"
 | 
			
		||||
  #   Customer.user # => "Rafael"
 | 
			
		||||
  #   Account.user  # => "DHH"
 | 
			
		||||
  #
 | 
			
		||||
  # To omit the instance writer method, pass <tt>instance_writer: false</tt>.
 | 
			
		||||
  # To omit the instance reader method, pass <tt>instance_reader: false</tt>.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Current
 | 
			
		||||
  #     thread_mattr_accessor :user, instance_writer: false, instance_reader: false
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Current.new.user = "DHH"  # => NoMethodError
 | 
			
		||||
  #   Current.new.user          # => NoMethodError
 | 
			
		||||
  #
 | 
			
		||||
  # Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Current
 | 
			
		||||
  #     thread_mattr_accessor :user, instance_accessor: false
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Current.new.user = "DHH"  # => NoMethodError
 | 
			
		||||
  #   Current.new.user          # => NoMethodError
 | 
			
		||||
  def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil)
 | 
			
		||||
    thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default)
 | 
			
		||||
    thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor)
 | 
			
		||||
  end
 | 
			
		||||
  alias :thread_cattr_accessor :thread_mattr_accessor
 | 
			
		||||
end
 | 
			
		||||
@ -1,140 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "active_support/concern"
 | 
			
		||||
 | 
			
		||||
class Module
 | 
			
		||||
  # = Bite-sized separation of concerns
 | 
			
		||||
  #
 | 
			
		||||
  # We often find ourselves with a medium-sized chunk of behavior that we'd
 | 
			
		||||
  # like to extract, but only mix in to a single class.
 | 
			
		||||
  #
 | 
			
		||||
  # Extracting a plain old Ruby object to encapsulate it and collaborate or
 | 
			
		||||
  # delegate to the original object is often a good choice, but when there's
 | 
			
		||||
  # no additional state to encapsulate or we're making DSL-style declarations
 | 
			
		||||
  # about the parent class, introducing new collaborators can obfuscate rather
 | 
			
		||||
  # than simplify.
 | 
			
		||||
  #
 | 
			
		||||
  # The typical route is to just dump everything in a monolithic class, perhaps
 | 
			
		||||
  # with a comment, as a least-bad alternative. Using modules in separate files
 | 
			
		||||
  # means tedious sifting to get a big-picture view.
 | 
			
		||||
  #
 | 
			
		||||
  # = Dissatisfying ways to separate small concerns
 | 
			
		||||
  #
 | 
			
		||||
  # == Using comments:
 | 
			
		||||
  #
 | 
			
		||||
  #   class Todo < ApplicationRecord
 | 
			
		||||
  #     # Other todo implementation
 | 
			
		||||
  #     # ...
 | 
			
		||||
  #
 | 
			
		||||
  #     ## Event tracking
 | 
			
		||||
  #     has_many :events
 | 
			
		||||
  #
 | 
			
		||||
  #     before_create :track_creation
 | 
			
		||||
  #
 | 
			
		||||
  #     private
 | 
			
		||||
  #       def track_creation
 | 
			
		||||
  #         # ...
 | 
			
		||||
  #       end
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  # == With an inline module:
 | 
			
		||||
  #
 | 
			
		||||
  # Noisy syntax.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Todo < ApplicationRecord
 | 
			
		||||
  #     # Other todo implementation
 | 
			
		||||
  #     # ...
 | 
			
		||||
  #
 | 
			
		||||
  #     module EventTracking
 | 
			
		||||
  #       extend ActiveSupport::Concern
 | 
			
		||||
  #
 | 
			
		||||
  #       included do
 | 
			
		||||
  #         has_many :events
 | 
			
		||||
  #         before_create :track_creation
 | 
			
		||||
  #       end
 | 
			
		||||
  #
 | 
			
		||||
  #       private
 | 
			
		||||
  #         def track_creation
 | 
			
		||||
  #           # ...
 | 
			
		||||
  #         end
 | 
			
		||||
  #     end
 | 
			
		||||
  #     include EventTracking
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  # == Mix-in noise exiled to its own file:
 | 
			
		||||
  #
 | 
			
		||||
  # Once our chunk of behavior starts pushing the scroll-to-understand-it
 | 
			
		||||
  # boundary, we give in and move it to a separate file. At this size, the
 | 
			
		||||
  # increased overhead can be a reasonable tradeoff even if it reduces our
 | 
			
		||||
  # at-a-glance perception of how things work.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Todo < ApplicationRecord
 | 
			
		||||
  #     # Other todo implementation
 | 
			
		||||
  #     # ...
 | 
			
		||||
  #
 | 
			
		||||
  #     include TodoEventTracking
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  # = Introducing Module#concerning
 | 
			
		||||
  #
 | 
			
		||||
  # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
 | 
			
		||||
  # separate bite-sized concerns.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Todo < ApplicationRecord
 | 
			
		||||
  #     # Other todo implementation
 | 
			
		||||
  #     # ...
 | 
			
		||||
  #
 | 
			
		||||
  #     concerning :EventTracking do
 | 
			
		||||
  #       included do
 | 
			
		||||
  #         has_many :events
 | 
			
		||||
  #         before_create :track_creation
 | 
			
		||||
  #       end
 | 
			
		||||
  #
 | 
			
		||||
  #       private
 | 
			
		||||
  #         def track_creation
 | 
			
		||||
  #           # ...
 | 
			
		||||
  #         end
 | 
			
		||||
  #     end
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Todo.ancestors
 | 
			
		||||
  #   # => [Todo, Todo::EventTracking, ApplicationRecord, Object]
 | 
			
		||||
  #
 | 
			
		||||
  # This small step has some wonderful ripple effects. We can
 | 
			
		||||
  # * grok the behavior of our class in one glance,
 | 
			
		||||
  # * clean up monolithic junk-drawer classes by separating their concerns, and
 | 
			
		||||
  # * stop leaning on protected/private for crude "this is internal stuff" modularity.
 | 
			
		||||
  #
 | 
			
		||||
  # === Prepending concerning
 | 
			
		||||
  #
 | 
			
		||||
  # <tt>concerning</tt> supports a <tt>prepend: true</tt> argument which will <tt>prepend</tt> the
 | 
			
		||||
  # concern instead of using <tt>include</tt> for it.
 | 
			
		||||
  module Concerning
 | 
			
		||||
    # Define a new concern and mix it in.
 | 
			
		||||
    def concerning(topic, prepend: false, &block)
 | 
			
		||||
      method = prepend ? :prepend : :include
 | 
			
		||||
      __send__(method, concern(topic, &block))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # A low-cruft shortcut to define a concern.
 | 
			
		||||
    #
 | 
			
		||||
    #   concern :EventTracking do
 | 
			
		||||
    #     ...
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    # is equivalent to
 | 
			
		||||
    #
 | 
			
		||||
    #   module EventTracking
 | 
			
		||||
    #     extend ActiveSupport::Concern
 | 
			
		||||
    #
 | 
			
		||||
    #     ...
 | 
			
		||||
    #   end
 | 
			
		||||
    def concern(topic, &module_definition)
 | 
			
		||||
      const_set topic, Module.new {
 | 
			
		||||
        extend ::ActiveSupport::Concern
 | 
			
		||||
        module_eval(&module_definition)
 | 
			
		||||
      }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
  include Concerning
 | 
			
		||||
end
 | 
			
		||||
@ -1,330 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "set"
 | 
			
		||||
 | 
			
		||||
class Module
 | 
			
		||||
  # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
 | 
			
		||||
  # option is not used.
 | 
			
		||||
  class DelegationError < NoMethodError; end
 | 
			
		||||
 | 
			
		||||
  RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
 | 
			
		||||
  else elsif END end ensure false for if in module next nil not or redo rescue retry
 | 
			
		||||
  return self super then true undef unless until when while yield)
 | 
			
		||||
  DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
 | 
			
		||||
  DELEGATION_RESERVED_METHOD_NAMES = Set.new(
 | 
			
		||||
    RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
 | 
			
		||||
  ).freeze
 | 
			
		||||
 | 
			
		||||
  # Provides a +delegate+ class method to easily expose contained objects'
 | 
			
		||||
  # public methods as your own.
 | 
			
		||||
  #
 | 
			
		||||
  # ==== Options
 | 
			
		||||
  # * <tt>:to</tt> - Specifies the target object name as a symbol or string
 | 
			
		||||
  # * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
 | 
			
		||||
  # * <tt>:allow_nil</tt> - If set to true, prevents a +Module::DelegationError+
 | 
			
		||||
  #   from being raised
 | 
			
		||||
  # * <tt>:private</tt> - If set to true, changes method visibility to private
 | 
			
		||||
  #
 | 
			
		||||
  # The macro receives one or more method names (specified as symbols or
 | 
			
		||||
  # strings) and the name of the target object via the <tt>:to</tt> option
 | 
			
		||||
  # (also a symbol or string).
 | 
			
		||||
  #
 | 
			
		||||
  # Delegation is particularly useful with Active Record associations:
 | 
			
		||||
  #
 | 
			
		||||
  #   class Greeter < ActiveRecord::Base
 | 
			
		||||
  #     def hello
 | 
			
		||||
  #       'hello'
 | 
			
		||||
  #     end
 | 
			
		||||
  #
 | 
			
		||||
  #     def goodbye
 | 
			
		||||
  #       'goodbye'
 | 
			
		||||
  #     end
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   class Foo < ActiveRecord::Base
 | 
			
		||||
  #     belongs_to :greeter
 | 
			
		||||
  #     delegate :hello, to: :greeter
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Foo.new.hello   # => "hello"
 | 
			
		||||
  #   Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
 | 
			
		||||
  #
 | 
			
		||||
  # Multiple delegates to the same target are allowed:
 | 
			
		||||
  #
 | 
			
		||||
  #   class Foo < ActiveRecord::Base
 | 
			
		||||
  #     belongs_to :greeter
 | 
			
		||||
  #     delegate :hello, :goodbye, to: :greeter
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Foo.new.goodbye # => "goodbye"
 | 
			
		||||
  #
 | 
			
		||||
  # Methods can be delegated to instance variables, class variables, or constants
 | 
			
		||||
  # by providing them as a symbols:
 | 
			
		||||
  #
 | 
			
		||||
  #   class Foo
 | 
			
		||||
  #     CONSTANT_ARRAY = [0,1,2,3]
 | 
			
		||||
  #     @@class_array  = [4,5,6,7]
 | 
			
		||||
  #
 | 
			
		||||
  #     def initialize
 | 
			
		||||
  #       @instance_array = [8,9,10,11]
 | 
			
		||||
  #     end
 | 
			
		||||
  #     delegate :sum, to: :CONSTANT_ARRAY
 | 
			
		||||
  #     delegate :min, to: :@@class_array
 | 
			
		||||
  #     delegate :max, to: :@instance_array
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Foo.new.sum # => 6
 | 
			
		||||
  #   Foo.new.min # => 4
 | 
			
		||||
  #   Foo.new.max # => 11
 | 
			
		||||
  #
 | 
			
		||||
  # It's also possible to delegate a method to the class by using +:class+:
 | 
			
		||||
  #
 | 
			
		||||
  #   class Foo
 | 
			
		||||
  #     def self.hello
 | 
			
		||||
  #       "world"
 | 
			
		||||
  #     end
 | 
			
		||||
  #
 | 
			
		||||
  #     delegate :hello, to: :class
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Foo.new.hello # => "world"
 | 
			
		||||
  #
 | 
			
		||||
  # Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
 | 
			
		||||
  # is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
 | 
			
		||||
  # delegated to.
 | 
			
		||||
  #
 | 
			
		||||
  #   Person = Struct.new(:name, :address)
 | 
			
		||||
  #
 | 
			
		||||
  #   class Invoice < Struct.new(:client)
 | 
			
		||||
  #     delegate :name, :address, to: :client, prefix: true
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   john_doe = Person.new('John Doe', 'Vimmersvej 13')
 | 
			
		||||
  #   invoice = Invoice.new(john_doe)
 | 
			
		||||
  #   invoice.client_name    # => "John Doe"
 | 
			
		||||
  #   invoice.client_address # => "Vimmersvej 13"
 | 
			
		||||
  #
 | 
			
		||||
  # It is also possible to supply a custom prefix.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Invoice < Struct.new(:client)
 | 
			
		||||
  #     delegate :name, :address, to: :client, prefix: :customer
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   invoice = Invoice.new(john_doe)
 | 
			
		||||
  #   invoice.customer_name    # => 'John Doe'
 | 
			
		||||
  #   invoice.customer_address # => 'Vimmersvej 13'
 | 
			
		||||
  #
 | 
			
		||||
  # The delegated methods are public by default.
 | 
			
		||||
  # Pass <tt>private: true</tt> to change that.
 | 
			
		||||
  #
 | 
			
		||||
  #   class User < ActiveRecord::Base
 | 
			
		||||
  #     has_one :profile
 | 
			
		||||
  #     delegate :first_name, to: :profile
 | 
			
		||||
  #     delegate :date_of_birth, to: :profile, private: true
 | 
			
		||||
  #
 | 
			
		||||
  #     def age
 | 
			
		||||
  #       Date.today.year - date_of_birth.year
 | 
			
		||||
  #     end
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   User.new.first_name # => "Tomas"
 | 
			
		||||
  #   User.new.date_of_birth # => NoMethodError: private method `date_of_birth' called for #<User:0x00000008221340>
 | 
			
		||||
  #   User.new.age # => 2
 | 
			
		||||
  #
 | 
			
		||||
  # If the target is +nil+ and does not respond to the delegated method a
 | 
			
		||||
  # +Module::DelegationError+ is raised. If you wish to instead return +nil+,
 | 
			
		||||
  # use the <tt>:allow_nil</tt> option.
 | 
			
		||||
  #
 | 
			
		||||
  #   class User < ActiveRecord::Base
 | 
			
		||||
  #     has_one :profile
 | 
			
		||||
  #     delegate :age, to: :profile
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   User.new.age
 | 
			
		||||
  #   # => Module::DelegationError: User#age delegated to profile.age, but profile is nil
 | 
			
		||||
  #
 | 
			
		||||
  # But if not having a profile yet is fine and should not be an error
 | 
			
		||||
  # condition:
 | 
			
		||||
  #
 | 
			
		||||
  #   class User < ActiveRecord::Base
 | 
			
		||||
  #     has_one :profile
 | 
			
		||||
  #     delegate :age, to: :profile, allow_nil: true
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   User.new.age # nil
 | 
			
		||||
  #
 | 
			
		||||
  # Note that if the target is not +nil+ then the call is attempted regardless of the
 | 
			
		||||
  # <tt>:allow_nil</tt> option, and thus an exception is still raised if said object
 | 
			
		||||
  # does not respond to the method:
 | 
			
		||||
  #
 | 
			
		||||
  #   class Foo
 | 
			
		||||
  #     def initialize(bar)
 | 
			
		||||
  #       @bar = bar
 | 
			
		||||
  #     end
 | 
			
		||||
  #
 | 
			
		||||
  #     delegate :name, to: :@bar, allow_nil: true
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   Foo.new("Bar").name # raises NoMethodError: undefined method `name'
 | 
			
		||||
  #
 | 
			
		||||
  # The target method must be public, otherwise it will raise +NoMethodError+.
 | 
			
		||||
  def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil)
 | 
			
		||||
    unless to
 | 
			
		||||
      raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)."
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    if prefix == true && /^[^a-z_]/.match?(to)
 | 
			
		||||
      raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    method_prefix = \
 | 
			
		||||
      if prefix
 | 
			
		||||
        "#{prefix == true ? to : prefix}_"
 | 
			
		||||
      else
 | 
			
		||||
        ""
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    location = caller_locations(1, 1).first
 | 
			
		||||
    file, line = location.path, location.lineno
 | 
			
		||||
 | 
			
		||||
    to = to.to_s
 | 
			
		||||
    to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
 | 
			
		||||
 | 
			
		||||
    method_def = []
 | 
			
		||||
    method_names = []
 | 
			
		||||
 | 
			
		||||
    methods.map do |method|
 | 
			
		||||
      method_name = prefix ? "#{method_prefix}#{method}" : method
 | 
			
		||||
      method_names << method_name.to_sym
 | 
			
		||||
 | 
			
		||||
      # Attribute writer methods only accept one argument. Makes sure []=
 | 
			
		||||
      # methods still accept two arguments.
 | 
			
		||||
      definition = if /[^\]]=$/.match?(method)
 | 
			
		||||
        "arg"
 | 
			
		||||
      elsif RUBY_VERSION >= "2.7"
 | 
			
		||||
        "..."
 | 
			
		||||
      else
 | 
			
		||||
        "*args, &block"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # The following generated method calls the target exactly once, storing
 | 
			
		||||
      # the returned value in a dummy variable.
 | 
			
		||||
      #
 | 
			
		||||
      # Reason is twofold: On one hand doing less calls is in general better.
 | 
			
		||||
      # On the other hand it could be that the target has side-effects,
 | 
			
		||||
      # whereas conceptually, from the user point of view, the delegator should
 | 
			
		||||
      # be doing one call.
 | 
			
		||||
      if allow_nil
 | 
			
		||||
        method = method.to_s
 | 
			
		||||
 | 
			
		||||
        method_def <<
 | 
			
		||||
          "def #{method_name}(#{definition})" <<
 | 
			
		||||
          "  _ = #{to}" <<
 | 
			
		||||
          "  if !_.nil? || nil.respond_to?(:#{method})" <<
 | 
			
		||||
          "    _.#{method}(#{definition})" <<
 | 
			
		||||
          "  end" <<
 | 
			
		||||
          "end"
 | 
			
		||||
      else
 | 
			
		||||
        method = method.to_s
 | 
			
		||||
        method_name = method_name.to_s
 | 
			
		||||
 | 
			
		||||
        method_def <<
 | 
			
		||||
          "def #{method_name}(#{definition})" <<
 | 
			
		||||
          "  _ = #{to}" <<
 | 
			
		||||
          "  _.#{method}(#{definition})" <<
 | 
			
		||||
          "rescue NoMethodError => e" <<
 | 
			
		||||
          "  if _.nil? && e.name == :#{method}" <<
 | 
			
		||||
          %(   raise DelegationError, "#{self}##{method_name} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") <<
 | 
			
		||||
          "  else" <<
 | 
			
		||||
          "    raise" <<
 | 
			
		||||
          "  end" <<
 | 
			
		||||
          "end"
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    module_eval(method_def.join(";"), file, line)
 | 
			
		||||
    private(*method_names) if private
 | 
			
		||||
    method_names
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # When building decorators, a common pattern may emerge:
 | 
			
		||||
  #
 | 
			
		||||
  #   class Partition
 | 
			
		||||
  #     def initialize(event)
 | 
			
		||||
  #       @event = event
 | 
			
		||||
  #     end
 | 
			
		||||
  #
 | 
			
		||||
  #     def person
 | 
			
		||||
  #       detail.person || creator
 | 
			
		||||
  #     end
 | 
			
		||||
  #
 | 
			
		||||
  #     private
 | 
			
		||||
  #       def respond_to_missing?(name, include_private = false)
 | 
			
		||||
  #         @event.respond_to?(name, include_private)
 | 
			
		||||
  #       end
 | 
			
		||||
  #
 | 
			
		||||
  #       def method_missing(method, *args, &block)
 | 
			
		||||
  #         @event.send(method, *args, &block)
 | 
			
		||||
  #       end
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  # With <tt>Module#delegate_missing_to</tt>, the above is condensed to:
 | 
			
		||||
  #
 | 
			
		||||
  #   class Partition
 | 
			
		||||
  #     delegate_missing_to :@event
 | 
			
		||||
  #
 | 
			
		||||
  #     def initialize(event)
 | 
			
		||||
  #       @event = event
 | 
			
		||||
  #     end
 | 
			
		||||
  #
 | 
			
		||||
  #     def person
 | 
			
		||||
  #       detail.person || creator
 | 
			
		||||
  #     end
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  # The target can be anything callable within the object, e.g. instance
 | 
			
		||||
  # variables, methods, constants, etc.
 | 
			
		||||
  #
 | 
			
		||||
  # The delegated method must be public on the target, otherwise it will
 | 
			
		||||
  # raise +DelegationError+. If you wish to instead return +nil+,
 | 
			
		||||
  # use the <tt>:allow_nil</tt> option.
 | 
			
		||||
  #
 | 
			
		||||
  # The <tt>marshal_dump</tt> and <tt>_dump</tt> methods are exempt from
 | 
			
		||||
  # delegation due to possible interference when calling
 | 
			
		||||
  # <tt>Marshal.dump(object)</tt>, should the delegation target method
 | 
			
		||||
  # of <tt>object</tt> add or remove instance variables.
 | 
			
		||||
  def delegate_missing_to(target, allow_nil: nil)
 | 
			
		||||
    target = target.to_s
 | 
			
		||||
    target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
 | 
			
		||||
 | 
			
		||||
    module_eval <<-RUBY, __FILE__, __LINE__ + 1
 | 
			
		||||
      def respond_to_missing?(name, include_private = false)
 | 
			
		||||
        # It may look like an oversight, but we deliberately do not pass
 | 
			
		||||
        # +include_private+, because they do not get delegated.
 | 
			
		||||
 | 
			
		||||
        return false if name == :marshal_dump || name == :_dump
 | 
			
		||||
        #{target}.respond_to?(name) || super
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def method_missing(method, *args, &block)
 | 
			
		||||
        if #{target}.respond_to?(method)
 | 
			
		||||
          #{target}.public_send(method, *args, &block)
 | 
			
		||||
        else
 | 
			
		||||
          begin
 | 
			
		||||
            super
 | 
			
		||||
          rescue NoMethodError
 | 
			
		||||
            if #{target}.nil?
 | 
			
		||||
              if #{allow_nil == true}
 | 
			
		||||
                nil
 | 
			
		||||
              else
 | 
			
		||||
                raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
 | 
			
		||||
              end
 | 
			
		||||
            else
 | 
			
		||||
              raise
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
 | 
			
		||||
    RUBY
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Module
 | 
			
		||||
  #   deprecate :foo
 | 
			
		||||
  #   deprecate bar: 'message'
 | 
			
		||||
  #   deprecate :foo, :bar, baz: 'warning!', qux: 'gone!'
 | 
			
		||||
  #
 | 
			
		||||
  # You can also use custom deprecator instance:
 | 
			
		||||
  #
 | 
			
		||||
  #   deprecate :foo, deprecator: MyLib::Deprecator.new
 | 
			
		||||
  #   deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new
 | 
			
		||||
  #
 | 
			
		||||
  # \Custom deprecators must respond to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt>
 | 
			
		||||
  # method where you can implement your custom warning behavior.
 | 
			
		||||
  #
 | 
			
		||||
  #   class MyLib::Deprecator
 | 
			
		||||
  #     def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
 | 
			
		||||
  #       message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
 | 
			
		||||
  #       Kernel.warn message
 | 
			
		||||
  #     end
 | 
			
		||||
  #   end
 | 
			
		||||
  def deprecate(*method_names)
 | 
			
		||||
    ActiveSupport::Deprecation.deprecate_methods(self, *method_names)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,63 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "active_support/core_ext/string/filters"
 | 
			
		||||
require "active_support/inflector"
 | 
			
		||||
 | 
			
		||||
class Module
 | 
			
		||||
  # Returns the name of the module containing this one.
 | 
			
		||||
  #
 | 
			
		||||
  #   M::N.module_parent_name # => "M"
 | 
			
		||||
  def module_parent_name
 | 
			
		||||
    if defined?(@parent_name)
 | 
			
		||||
      @parent_name
 | 
			
		||||
    else
 | 
			
		||||
      parent_name = name =~ /::[^:]+\z/ ? -$` : nil
 | 
			
		||||
      @parent_name = parent_name unless frozen?
 | 
			
		||||
      parent_name
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Returns the module which contains this one according to its name.
 | 
			
		||||
  #
 | 
			
		||||
  #   module M
 | 
			
		||||
  #     module N
 | 
			
		||||
  #     end
 | 
			
		||||
  #   end
 | 
			
		||||
  #   X = M::N
 | 
			
		||||
  #
 | 
			
		||||
  #   M::N.module_parent # => M
 | 
			
		||||
  #   X.module_parent    # => M
 | 
			
		||||
  #
 | 
			
		||||
  # The parent of top-level and anonymous modules is Object.
 | 
			
		||||
  #
 | 
			
		||||
  #   M.module_parent          # => Object
 | 
			
		||||
  #   Module.new.module_parent # => Object
 | 
			
		||||
  def module_parent
 | 
			
		||||
    module_parent_name ? ActiveSupport::Inflector.constantize(module_parent_name) : Object
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Returns all the parents of this module according to its name, ordered from
 | 
			
		||||
  # nested outwards. The receiver is not contained within the result.
 | 
			
		||||
  #
 | 
			
		||||
  #   module M
 | 
			
		||||
  #     module N
 | 
			
		||||
  #     end
 | 
			
		||||
  #   end
 | 
			
		||||
  #   X = M::N
 | 
			
		||||
  #
 | 
			
		||||
  #   M.module_parents    # => [Object]
 | 
			
		||||
  #   M::N.module_parents # => [M, Object]
 | 
			
		||||
  #   X.module_parents    # => [M, Object]
 | 
			
		||||
  def module_parents
 | 
			
		||||
    parents = []
 | 
			
		||||
    if module_parent_name
 | 
			
		||||
      parts = module_parent_name.split("::")
 | 
			
		||||
      until parts.empty?
 | 
			
		||||
        parents << ActiveSupport::Inflector.constantize(parts * "::")
 | 
			
		||||
        parts.pop
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    parents << Object unless parents.include? Object
 | 
			
		||||
    parents
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,40 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Module
 | 
			
		||||
  # Marks the named method as intended to be redefined, if it exists.
 | 
			
		||||
  # Suppresses the Ruby method redefinition warning. Prefer
 | 
			
		||||
  # #redefine_method where possible.
 | 
			
		||||
  def silence_redefinition_of_method(method)
 | 
			
		||||
    if method_defined?(method) || private_method_defined?(method)
 | 
			
		||||
      # This suppresses the "method redefined" warning; the self-alias
 | 
			
		||||
      # looks odd, but means we don't need to generate a unique name
 | 
			
		||||
      alias_method method, method
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Replaces the existing method definition, if there is one, with the passed
 | 
			
		||||
  # block as its body.
 | 
			
		||||
  def redefine_method(method, &block)
 | 
			
		||||
    visibility = method_visibility(method)
 | 
			
		||||
    silence_redefinition_of_method(method)
 | 
			
		||||
    define_method(method, &block)
 | 
			
		||||
    send(visibility, method)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Replaces the existing singleton method definition, if there is one, with
 | 
			
		||||
  # the passed block as its body.
 | 
			
		||||
  def redefine_singleton_method(method, &block)
 | 
			
		||||
    singleton_class.redefine_method(method, &block)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def method_visibility(method) # :nodoc:
 | 
			
		||||
    case
 | 
			
		||||
    when private_method_defined?(method)
 | 
			
		||||
      :private
 | 
			
		||||
    when protected_method_defined?(method)
 | 
			
		||||
      :protected
 | 
			
		||||
    else
 | 
			
		||||
      :public
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,17 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "active_support/core_ext/module/redefine_method"
 | 
			
		||||
 | 
			
		||||
class Module
 | 
			
		||||
  # Removes the named method, if it exists.
 | 
			
		||||
  def remove_possible_method(method)
 | 
			
		||||
    if method_defined?(method) || private_method_defined?(method)
 | 
			
		||||
      undef_method(method)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Removes the named singleton method, if it exists.
 | 
			
		||||
  def remove_possible_singleton_method(method)
 | 
			
		||||
    singleton_class.remove_possible_method(method)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,66 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
class Numeric
 | 
			
		||||
  KILOBYTE = 1024
 | 
			
		||||
  MEGABYTE = KILOBYTE * 1024
 | 
			
		||||
  GIGABYTE = MEGABYTE * 1024
 | 
			
		||||
  TERABYTE = GIGABYTE * 1024
 | 
			
		||||
  PETABYTE = TERABYTE * 1024
 | 
			
		||||
  EXABYTE  = PETABYTE * 1024
 | 
			
		||||
 | 
			
		||||
  # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
 | 
			
		||||
  #
 | 
			
		||||
  #   2.bytes # => 2
 | 
			
		||||
  def bytes
 | 
			
		||||
    self
 | 
			
		||||
  end
 | 
			
		||||
  alias :byte :bytes
 | 
			
		||||
 | 
			
		||||
  # Returns the number of bytes equivalent to the kilobytes provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.kilobytes # => 2048
 | 
			
		||||
  def kilobytes
 | 
			
		||||
    self * KILOBYTE
 | 
			
		||||
  end
 | 
			
		||||
  alias :kilobyte :kilobytes
 | 
			
		||||
 | 
			
		||||
  # Returns the number of bytes equivalent to the megabytes provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.megabytes # => 2_097_152
 | 
			
		||||
  def megabytes
 | 
			
		||||
    self * MEGABYTE
 | 
			
		||||
  end
 | 
			
		||||
  alias :megabyte :megabytes
 | 
			
		||||
 | 
			
		||||
  # Returns the number of bytes equivalent to the gigabytes provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.gigabytes # => 2_147_483_648
 | 
			
		||||
  def gigabytes
 | 
			
		||||
    self * GIGABYTE
 | 
			
		||||
  end
 | 
			
		||||
  alias :gigabyte :gigabytes
 | 
			
		||||
 | 
			
		||||
  # Returns the number of bytes equivalent to the terabytes provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.terabytes # => 2_199_023_255_552
 | 
			
		||||
  def terabytes
 | 
			
		||||
    self * TERABYTE
 | 
			
		||||
  end
 | 
			
		||||
  alias :terabyte :terabytes
 | 
			
		||||
 | 
			
		||||
  # Returns the number of bytes equivalent to the petabytes provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.petabytes # => 2_251_799_813_685_248
 | 
			
		||||
  def petabytes
 | 
			
		||||
    self * PETABYTE
 | 
			
		||||
  end
 | 
			
		||||
  alias :petabyte :petabytes
 | 
			
		||||
 | 
			
		||||
  # Returns the number of bytes equivalent to the exabytes provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.exabytes # => 2_305_843_009_213_693_952
 | 
			
		||||
  def exabytes
 | 
			
		||||
    self * EXABYTE
 | 
			
		||||
  end
 | 
			
		||||
  alias :exabyte :exabytes
 | 
			
		||||
end
 | 
			
		||||
@ -1,140 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "active_support/core_ext/big_decimal/conversions"
 | 
			
		||||
require "active_support/number_helper"
 | 
			
		||||
 | 
			
		||||
module ActiveSupport
 | 
			
		||||
  module NumericWithFormat
 | 
			
		||||
    # Provides options for converting numbers into formatted strings.
 | 
			
		||||
    # Options are provided for phone numbers, currency, percentage,
 | 
			
		||||
    # precision, positional notation, file size and pretty printing.
 | 
			
		||||
    #
 | 
			
		||||
    # ==== Options
 | 
			
		||||
    #
 | 
			
		||||
    # For details on which formats use which options, see ActiveSupport::NumberHelper
 | 
			
		||||
    #
 | 
			
		||||
    # ==== Examples
 | 
			
		||||
    #
 | 
			
		||||
    #  Phone Numbers:
 | 
			
		||||
    #  5551234.to_s(:phone)                                     # => "555-1234"
 | 
			
		||||
    #  1235551234.to_s(:phone)                                  # => "123-555-1234"
 | 
			
		||||
    #  1235551234.to_s(:phone, area_code: true)                 # => "(123) 555-1234"
 | 
			
		||||
    #  1235551234.to_s(:phone, delimiter: ' ')                  # => "123 555 1234"
 | 
			
		||||
    #  1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
 | 
			
		||||
    #  1235551234.to_s(:phone, country_code: 1)                 # => "+1-123-555-1234"
 | 
			
		||||
    #  1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
 | 
			
		||||
    #  # => "+1.123.555.1234 x 1343"
 | 
			
		||||
    #
 | 
			
		||||
    #  Currency:
 | 
			
		||||
    #  1234567890.50.to_s(:currency)                     # => "$1,234,567,890.50"
 | 
			
		||||
    #  1234567890.506.to_s(:currency)                    # => "$1,234,567,890.51"
 | 
			
		||||
    #  1234567890.506.to_s(:currency, precision: 3)      # => "$1,234,567,890.506"
 | 
			
		||||
    #  1234567890.506.to_s(:currency, round_mode: :down) # => "$1,234,567,890.50"
 | 
			
		||||
    #  1234567890.506.to_s(:currency, locale: :fr)       # => "1 234 567 890,51 €"
 | 
			
		||||
    #  -1234567890.50.to_s(:currency, negative_format: '(%u%n)')
 | 
			
		||||
    #  # => "($1,234,567,890.50)"
 | 
			
		||||
    #  1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '')
 | 
			
		||||
    #  # => "£1234567890,50"
 | 
			
		||||
    #  1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '', format: '%n %u')
 | 
			
		||||
    #  # => "1234567890,50 £"
 | 
			
		||||
    #
 | 
			
		||||
    #  Percentage:
 | 
			
		||||
    #  100.to_s(:percentage)                                  # => "100.000%"
 | 
			
		||||
    #  100.to_s(:percentage, precision: 0)                    # => "100%"
 | 
			
		||||
    #  1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%"
 | 
			
		||||
    #  302.24398923423.to_s(:percentage, precision: 5)        # => "302.24399%"
 | 
			
		||||
    #  302.24398923423.to_s(:percentage, round_mode: :down)   # => "302.243%"
 | 
			
		||||
    #  1000.to_s(:percentage, locale: :fr)                    # => "1 000,000%"
 | 
			
		||||
    #  100.to_s(:percentage, format: '%n  %')                 # => "100.000  %"
 | 
			
		||||
    #
 | 
			
		||||
    #  Delimited:
 | 
			
		||||
    #  12345678.to_s(:delimited)                     # => "12,345,678"
 | 
			
		||||
    #  12345678.05.to_s(:delimited)                  # => "12,345,678.05"
 | 
			
		||||
    #  12345678.to_s(:delimited, delimiter: '.')     # => "12.345.678"
 | 
			
		||||
    #  12345678.to_s(:delimited, delimiter: ',')     # => "12,345,678"
 | 
			
		||||
    #  12345678.05.to_s(:delimited, separator: ' ')  # => "12,345,678 05"
 | 
			
		||||
    #  12345678.05.to_s(:delimited, locale: :fr)     # => "12 345 678,05"
 | 
			
		||||
    #  98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
 | 
			
		||||
    #  # => "98 765 432,98"
 | 
			
		||||
    #
 | 
			
		||||
    #  Rounded:
 | 
			
		||||
    #  111.2345.to_s(:rounded)                                      # => "111.235"
 | 
			
		||||
    #  111.2345.to_s(:rounded, precision: 2)                        # => "111.23"
 | 
			
		||||
    #  111.2345.to_s(:rounded, precision: 2, round_mode: :up)       # => "111.24"
 | 
			
		||||
    #  13.to_s(:rounded, precision: 5)                              # => "13.00000"
 | 
			
		||||
    #  389.32314.to_s(:rounded, precision: 0)                       # => "389"
 | 
			
		||||
    #  111.2345.to_s(:rounded, significant: true)                   # => "111"
 | 
			
		||||
    #  111.2345.to_s(:rounded, precision: 1, significant: true)     # => "100"
 | 
			
		||||
    #  13.to_s(:rounded, precision: 5, significant: true)           # => "13.000"
 | 
			
		||||
    #  111.234.to_s(:rounded, locale: :fr)                          # => "111,234"
 | 
			
		||||
    #  13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
 | 
			
		||||
    #  # => "13"
 | 
			
		||||
    #  389.32314.to_s(:rounded, precision: 4, significant: true)    # => "389.3"
 | 
			
		||||
    #  1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
 | 
			
		||||
    #  # => "1.111,23"
 | 
			
		||||
    #
 | 
			
		||||
    #  Human-friendly size in Bytes:
 | 
			
		||||
    #  123.to_s(:human_size)                                    # => "123 Bytes"
 | 
			
		||||
    #  1234.to_s(:human_size)                                   # => "1.21 KB"
 | 
			
		||||
    #  12345.to_s(:human_size)                                  # => "12.1 KB"
 | 
			
		||||
    #  1234567.to_s(:human_size)                                # => "1.18 MB"
 | 
			
		||||
    #  1234567890.to_s(:human_size)                             # => "1.15 GB"
 | 
			
		||||
    #  1234567890123.to_s(:human_size)                          # => "1.12 TB"
 | 
			
		||||
    #  1234567890123456.to_s(:human_size)                       # => "1.1 PB"
 | 
			
		||||
    #  1234567890123456789.to_s(:human_size)                    # => "1.07 EB"
 | 
			
		||||
    #  1234567.to_s(:human_size, precision: 2)                  # => "1.2 MB"
 | 
			
		||||
    #  1234567.to_s(:human_size, precision: 2, round_mode: :up) # => "1.3 MB"
 | 
			
		||||
    #  483989.to_s(:human_size, precision: 2)                   # => "470 KB"
 | 
			
		||||
    #  1234567.to_s(:human_size, precision: 2, separator: ',')  # => "1,2 MB"
 | 
			
		||||
    #  1234567890123.to_s(:human_size, precision: 5)            # => "1.1228 TB"
 | 
			
		||||
    #  524288000.to_s(:human_size, precision: 5)                # => "500 MB"
 | 
			
		||||
    #
 | 
			
		||||
    #  Human-friendly format:
 | 
			
		||||
    #  123.to_s(:human)                                       # => "123"
 | 
			
		||||
    #  1234.to_s(:human)                                      # => "1.23 Thousand"
 | 
			
		||||
    #  12345.to_s(:human)                                     # => "12.3 Thousand"
 | 
			
		||||
    #  1234567.to_s(:human)                                   # => "1.23 Million"
 | 
			
		||||
    #  1234567890.to_s(:human)                                # => "1.23 Billion"
 | 
			
		||||
    #  1234567890123.to_s(:human)                             # => "1.23 Trillion"
 | 
			
		||||
    #  1234567890123456.to_s(:human)                          # => "1.23 Quadrillion"
 | 
			
		||||
    #  1234567890123456789.to_s(:human)                       # => "1230 Quadrillion"
 | 
			
		||||
    #  489939.to_s(:human, precision: 2)                      # => "490 Thousand"
 | 
			
		||||
    #  489939.to_s(:human, precision: 2, round_mode: :down)   # => "480 Thousand"
 | 
			
		||||
    #  489939.to_s(:human, precision: 4)                      # => "489.9 Thousand"
 | 
			
		||||
    #  1234567.to_s(:human, precision: 4,
 | 
			
		||||
    #                   significant: false)                   # => "1.2346 Million"
 | 
			
		||||
    #  1234567.to_s(:human, precision: 1,
 | 
			
		||||
    #                   separator: ',',
 | 
			
		||||
    #                   significant: false)                   # => "1,2 Million"
 | 
			
		||||
    def to_s(format = nil, options = nil)
 | 
			
		||||
      case format
 | 
			
		||||
      when nil
 | 
			
		||||
        super()
 | 
			
		||||
      when Integer, String
 | 
			
		||||
        super(format)
 | 
			
		||||
      when :phone
 | 
			
		||||
        ActiveSupport::NumberHelper.number_to_phone(self, options || {})
 | 
			
		||||
      when :currency
 | 
			
		||||
        ActiveSupport::NumberHelper.number_to_currency(self, options || {})
 | 
			
		||||
      when :percentage
 | 
			
		||||
        ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
 | 
			
		||||
      when :delimited
 | 
			
		||||
        ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
 | 
			
		||||
      when :rounded
 | 
			
		||||
        ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
 | 
			
		||||
      when :human
 | 
			
		||||
        ActiveSupport::NumberHelper.number_to_human(self, options || {})
 | 
			
		||||
      when :human_size
 | 
			
		||||
        ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
 | 
			
		||||
      when Symbol
 | 
			
		||||
        super()
 | 
			
		||||
      else
 | 
			
		||||
        super(format)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
Integer.prepend ActiveSupport::NumericWithFormat
 | 
			
		||||
Float.prepend ActiveSupport::NumericWithFormat
 | 
			
		||||
BigDecimal.prepend ActiveSupport::NumericWithFormat
 | 
			
		||||
@ -1,66 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "active_support/duration"
 | 
			
		||||
require "active_support/core_ext/time/calculations"
 | 
			
		||||
require "active_support/core_ext/time/acts_like"
 | 
			
		||||
require "active_support/core_ext/date/calculations"
 | 
			
		||||
require "active_support/core_ext/date/acts_like"
 | 
			
		||||
 | 
			
		||||
class Numeric
 | 
			
		||||
  # Returns a Duration instance matching the number of seconds provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.seconds # => 2 seconds
 | 
			
		||||
  def seconds
 | 
			
		||||
    ActiveSupport::Duration.seconds(self)
 | 
			
		||||
  end
 | 
			
		||||
  alias :second :seconds
 | 
			
		||||
 | 
			
		||||
  # Returns a Duration instance matching the number of minutes provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.minutes # => 2 minutes
 | 
			
		||||
  def minutes
 | 
			
		||||
    ActiveSupport::Duration.minutes(self)
 | 
			
		||||
  end
 | 
			
		||||
  alias :minute :minutes
 | 
			
		||||
 | 
			
		||||
  # Returns a Duration instance matching the number of hours provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.hours # => 2 hours
 | 
			
		||||
  def hours
 | 
			
		||||
    ActiveSupport::Duration.hours(self)
 | 
			
		||||
  end
 | 
			
		||||
  alias :hour :hours
 | 
			
		||||
 | 
			
		||||
  # Returns a Duration instance matching the number of days provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.days # => 2 days
 | 
			
		||||
  def days
 | 
			
		||||
    ActiveSupport::Duration.days(self)
 | 
			
		||||
  end
 | 
			
		||||
  alias :day :days
 | 
			
		||||
 | 
			
		||||
  # Returns a Duration instance matching the number of weeks provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.weeks # => 2 weeks
 | 
			
		||||
  def weeks
 | 
			
		||||
    ActiveSupport::Duration.weeks(self)
 | 
			
		||||
  end
 | 
			
		||||
  alias :week :weeks
 | 
			
		||||
 | 
			
		||||
  # Returns a Duration instance matching the number of fortnights provided.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.fortnights # => 4 weeks
 | 
			
		||||
  def fortnights
 | 
			
		||||
    ActiveSupport::Duration.weeks(self * 2)
 | 
			
		||||
  end
 | 
			
		||||
  alias :fortnight :fortnights
 | 
			
		||||
 | 
			
		||||
  # Returns the number of milliseconds equivalent to the seconds provided.
 | 
			
		||||
  # Used with the standard time durations.
 | 
			
		||||
  #
 | 
			
		||||
  #   2.in_milliseconds # => 2000
 | 
			
		||||
  #   1.hour.in_milliseconds # => 3600000
 | 
			
		||||
  def in_milliseconds
 | 
			
		||||
    self * 1000
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,293 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "active_support/inflector/methods"
 | 
			
		||||
require "active_support/inflector/transliterate"
 | 
			
		||||
 | 
			
		||||
# String inflections define new methods on the String class to transform names for different purposes.
 | 
			
		||||
# For instance, you can figure out the name of a table from the name of a class.
 | 
			
		||||
#
 | 
			
		||||
#   'ScaleScore'.tableize # => "scale_scores"
 | 
			
		||||
#
 | 
			
		||||
class String
 | 
			
		||||
  # Returns the plural form of the word in the string.
 | 
			
		||||
  #
 | 
			
		||||
  # If the optional parameter +count+ is specified,
 | 
			
		||||
  # the singular form will be returned if <tt>count == 1</tt>.
 | 
			
		||||
  # For any other value of +count+ the plural will be returned.
 | 
			
		||||
  #
 | 
			
		||||
  # If the optional parameter +locale+ is specified,
 | 
			
		||||
  # the word will be pluralized as a word of that language.
 | 
			
		||||
  # By default, this parameter is set to <tt>:en</tt>.
 | 
			
		||||
  # You must define your own inflection rules for languages other than English.
 | 
			
		||||
  #
 | 
			
		||||
  #   'post'.pluralize             # => "posts"
 | 
			
		||||
  #   'octopus'.pluralize          # => "octopi"
 | 
			
		||||
  #   'sheep'.pluralize            # => "sheep"
 | 
			
		||||
  #   'words'.pluralize            # => "words"
 | 
			
		||||
  #   'the blue mailman'.pluralize # => "the blue mailmen"
 | 
			
		||||
  #   'CamelOctopus'.pluralize     # => "CamelOctopi"
 | 
			
		||||
  #   'apple'.pluralize(1)         # => "apple"
 | 
			
		||||
  #   'apple'.pluralize(2)         # => "apples"
 | 
			
		||||
  #   'ley'.pluralize(:es)         # => "leyes"
 | 
			
		||||
  #   'ley'.pluralize(1, :es)      # => "ley"
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.pluralize.
 | 
			
		||||
  def pluralize(count = nil, locale = :en)
 | 
			
		||||
    locale = count if count.is_a?(Symbol)
 | 
			
		||||
    if count == 1
 | 
			
		||||
      dup
 | 
			
		||||
    else
 | 
			
		||||
      ActiveSupport::Inflector.pluralize(self, locale)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # The reverse of +pluralize+, returns the singular form of a word in a string.
 | 
			
		||||
  #
 | 
			
		||||
  # If the optional parameter +locale+ is specified,
 | 
			
		||||
  # the word will be singularized as a word of that language.
 | 
			
		||||
  # By default, this parameter is set to <tt>:en</tt>.
 | 
			
		||||
  # You must define your own inflection rules for languages other than English.
 | 
			
		||||
  #
 | 
			
		||||
  #   'posts'.singularize            # => "post"
 | 
			
		||||
  #   'octopi'.singularize           # => "octopus"
 | 
			
		||||
  #   'sheep'.singularize            # => "sheep"
 | 
			
		||||
  #   'word'.singularize             # => "word"
 | 
			
		||||
  #   'the blue mailmen'.singularize # => "the blue mailman"
 | 
			
		||||
  #   'CamelOctopi'.singularize      # => "CamelOctopus"
 | 
			
		||||
  #   'leyes'.singularize(:es)       # => "ley"
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.singularize.
 | 
			
		||||
  def singularize(locale = :en)
 | 
			
		||||
    ActiveSupport::Inflector.singularize(self, locale)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # +constantize+ tries to find a declared constant with the name specified
 | 
			
		||||
  # in the string. It raises a NameError when the name is not in CamelCase
 | 
			
		||||
  # or is not initialized.
 | 
			
		||||
  #
 | 
			
		||||
  #   'Module'.constantize  # => Module
 | 
			
		||||
  #   'Class'.constantize   # => Class
 | 
			
		||||
  #   'blargle'.constantize # => NameError: wrong constant name blargle
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.constantize.
 | 
			
		||||
  def constantize
 | 
			
		||||
    ActiveSupport::Inflector.constantize(self)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # +safe_constantize+ tries to find a declared constant with the name specified
 | 
			
		||||
  # in the string. It returns +nil+ when the name is not in CamelCase
 | 
			
		||||
  # or is not initialized.
 | 
			
		||||
  #
 | 
			
		||||
  #   'Module'.safe_constantize  # => Module
 | 
			
		||||
  #   'Class'.safe_constantize   # => Class
 | 
			
		||||
  #   'blargle'.safe_constantize # => nil
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.safe_constantize.
 | 
			
		||||
  def safe_constantize
 | 
			
		||||
    ActiveSupport::Inflector.safe_constantize(self)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize
 | 
			
		||||
  # is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
 | 
			
		||||
  #
 | 
			
		||||
  # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
 | 
			
		||||
  #
 | 
			
		||||
  #   'active_record'.camelize                # => "ActiveRecord"
 | 
			
		||||
  #   'active_record'.camelize(:lower)        # => "activeRecord"
 | 
			
		||||
  #   'active_record/errors'.camelize         # => "ActiveRecord::Errors"
 | 
			
		||||
  #   'active_record/errors'.camelize(:lower) # => "activeRecord::Errors"
 | 
			
		||||
  #
 | 
			
		||||
  # +camelize+ is also aliased as +camelcase+.
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.camelize.
 | 
			
		||||
  def camelize(first_letter = :upper)
 | 
			
		||||
    case first_letter
 | 
			
		||||
    when :upper
 | 
			
		||||
      ActiveSupport::Inflector.camelize(self, true)
 | 
			
		||||
    when :lower
 | 
			
		||||
      ActiveSupport::Inflector.camelize(self, false)
 | 
			
		||||
    else
 | 
			
		||||
      raise ArgumentError, "Invalid option, use either :upper or :lower."
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
  alias_method :camelcase, :camelize
 | 
			
		||||
 | 
			
		||||
  # Capitalizes all the words and replaces some characters in the string to create
 | 
			
		||||
  # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
 | 
			
		||||
  # used in the Rails internals.
 | 
			
		||||
  #
 | 
			
		||||
  # The trailing '_id','Id'.. can be kept and capitalized by setting the
 | 
			
		||||
  # optional parameter +keep_id_suffix+ to true.
 | 
			
		||||
  # By default, this parameter is false.
 | 
			
		||||
  #
 | 
			
		||||
  #   'man from the boondocks'.titleize                       # => "Man From The Boondocks"
 | 
			
		||||
  #   'x-men: the last stand'.titleize                        # => "X Men: The Last Stand"
 | 
			
		||||
  #   'string_ending_with_id'.titleize(keep_id_suffix: true)  # => "String Ending With Id"
 | 
			
		||||
  #
 | 
			
		||||
  # +titleize+ is also aliased as +titlecase+.
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.titleize.
 | 
			
		||||
  def titleize(keep_id_suffix: false)
 | 
			
		||||
    ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix)
 | 
			
		||||
  end
 | 
			
		||||
  alias_method :titlecase, :titleize
 | 
			
		||||
 | 
			
		||||
  # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
 | 
			
		||||
  #
 | 
			
		||||
  # +underscore+ will also change '::' to '/' to convert namespaces to paths.
 | 
			
		||||
  #
 | 
			
		||||
  #   'ActiveModel'.underscore         # => "active_model"
 | 
			
		||||
  #   'ActiveModel::Errors'.underscore # => "active_model/errors"
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.underscore.
 | 
			
		||||
  def underscore
 | 
			
		||||
    ActiveSupport::Inflector.underscore(self)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Replaces underscores with dashes in the string.
 | 
			
		||||
  #
 | 
			
		||||
  #   'puni_puni'.dasherize # => "puni-puni"
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.dasherize.
 | 
			
		||||
  def dasherize
 | 
			
		||||
    ActiveSupport::Inflector.dasherize(self)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Removes the module part from the constant expression in the string.
 | 
			
		||||
  #
 | 
			
		||||
  #   'ActiveSupport::Inflector::Inflections'.demodulize # => "Inflections"
 | 
			
		||||
  #   'Inflections'.demodulize                           # => "Inflections"
 | 
			
		||||
  #   '::Inflections'.demodulize                         # => "Inflections"
 | 
			
		||||
  #   ''.demodulize                                      # => ''
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.demodulize.
 | 
			
		||||
  #
 | 
			
		||||
  # See also +deconstantize+.
 | 
			
		||||
  def demodulize
 | 
			
		||||
    ActiveSupport::Inflector.demodulize(self)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Removes the rightmost segment from the constant expression in the string.
 | 
			
		||||
  #
 | 
			
		||||
  #   'Net::HTTP'.deconstantize   # => "Net"
 | 
			
		||||
  #   '::Net::HTTP'.deconstantize # => "::Net"
 | 
			
		||||
  #   'String'.deconstantize      # => ""
 | 
			
		||||
  #   '::String'.deconstantize    # => ""
 | 
			
		||||
  #   ''.deconstantize            # => ""
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.deconstantize.
 | 
			
		||||
  #
 | 
			
		||||
  # See also +demodulize+.
 | 
			
		||||
  def deconstantize
 | 
			
		||||
    ActiveSupport::Inflector.deconstantize(self)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
 | 
			
		||||
  #
 | 
			
		||||
  # If the optional parameter +locale+ is specified,
 | 
			
		||||
  # the word will be parameterized as a word of that language.
 | 
			
		||||
  # By default, this parameter is set to <tt>nil</tt> and it will use
 | 
			
		||||
  # the configured <tt>I18n.locale</tt>.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Person
 | 
			
		||||
  #     def to_param
 | 
			
		||||
  #       "#{id}-#{name.parameterize}"
 | 
			
		||||
  #     end
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   @person = Person.find(1)
 | 
			
		||||
  #   # => #<Person id: 1, name: "Donald E. Knuth">
 | 
			
		||||
  #
 | 
			
		||||
  #   <%= link_to(@person.name, person_path) %>
 | 
			
		||||
  #   # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
 | 
			
		||||
  #
 | 
			
		||||
  # To preserve the case of the characters in a string, use the +preserve_case+ argument.
 | 
			
		||||
  #
 | 
			
		||||
  #   class Person
 | 
			
		||||
  #     def to_param
 | 
			
		||||
  #       "#{id}-#{name.parameterize(preserve_case: true)}"
 | 
			
		||||
  #     end
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   @person = Person.find(1)
 | 
			
		||||
  #   # => #<Person id: 1, name: "Donald E. Knuth">
 | 
			
		||||
  #
 | 
			
		||||
  #   <%= link_to(@person.name, person_path) %>
 | 
			
		||||
  #   # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a>
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.parameterize.
 | 
			
		||||
  def parameterize(separator: "-", preserve_case: false, locale: nil)
 | 
			
		||||
    ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case, locale: locale)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Creates the name of a table like Rails does for models to table names. This method
 | 
			
		||||
  # uses the +pluralize+ method on the last word in the string.
 | 
			
		||||
  #
 | 
			
		||||
  #   'RawScaledScorer'.tableize # => "raw_scaled_scorers"
 | 
			
		||||
  #   'ham_and_egg'.tableize     # => "ham_and_eggs"
 | 
			
		||||
  #   'fancyCategory'.tableize   # => "fancy_categories"
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.tableize.
 | 
			
		||||
  def tableize
 | 
			
		||||
    ActiveSupport::Inflector.tableize(self)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Creates a class name from a plural table name like Rails does for table names to models.
 | 
			
		||||
  # Note that this returns a string and not a class. (To convert to an actual class
 | 
			
		||||
  # follow +classify+ with +constantize+.)
 | 
			
		||||
  #
 | 
			
		||||
  #   'ham_and_eggs'.classify # => "HamAndEgg"
 | 
			
		||||
  #   'posts'.classify        # => "Post"
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.classify.
 | 
			
		||||
  def classify
 | 
			
		||||
    ActiveSupport::Inflector.classify(self)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Capitalizes the first word, turns underscores into spaces, and (by default)strips a
 | 
			
		||||
  # trailing '_id' if present.
 | 
			
		||||
  # Like +titleize+, this is meant for creating pretty output.
 | 
			
		||||
  #
 | 
			
		||||
  # The capitalization of the first word can be turned off by setting the
 | 
			
		||||
  # optional parameter +capitalize+ to false.
 | 
			
		||||
  # By default, this parameter is true.
 | 
			
		||||
  #
 | 
			
		||||
  # The trailing '_id' can be kept and capitalized by setting the
 | 
			
		||||
  # optional parameter +keep_id_suffix+ to true.
 | 
			
		||||
  # By default, this parameter is false.
 | 
			
		||||
  #
 | 
			
		||||
  #   'employee_salary'.humanize                    # => "Employee salary"
 | 
			
		||||
  #   'author_id'.humanize                          # => "Author"
 | 
			
		||||
  #   'author_id'.humanize(capitalize: false)       # => "author"
 | 
			
		||||
  #   '_id'.humanize                                # => "Id"
 | 
			
		||||
  #   'author_id'.humanize(keep_id_suffix: true)    # => "Author Id"
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.humanize.
 | 
			
		||||
  def humanize(capitalize: true, keep_id_suffix: false)
 | 
			
		||||
    ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Converts just the first character to uppercase.
 | 
			
		||||
  #
 | 
			
		||||
  #   'what a Lovely Day'.upcase_first # => "What a Lovely Day"
 | 
			
		||||
  #   'w'.upcase_first                 # => "W"
 | 
			
		||||
  #   ''.upcase_first                  # => ""
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.upcase_first.
 | 
			
		||||
  def upcase_first
 | 
			
		||||
    ActiveSupport::Inflector.upcase_first(self)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Creates a foreign key name from a class name.
 | 
			
		||||
  # +separate_class_name_and_id_with_underscore+ sets whether
 | 
			
		||||
  # the method should put '_' between the name and 'id'.
 | 
			
		||||
  #
 | 
			
		||||
  #   'Message'.foreign_key        # => "message_id"
 | 
			
		||||
  #   'Message'.foreign_key(false) # => "messageid"
 | 
			
		||||
  #   'Admin::Post'.foreign_key    # => "post_id"
 | 
			
		||||
  #
 | 
			
		||||
  # See ActiveSupport::Inflector.foreign_key.
 | 
			
		||||
  def foreign_key(separate_class_name_and_id_with_underscore = true)
 | 
			
		||||
    ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,58 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "active_support/multibyte"
 | 
			
		||||
 | 
			
		||||
class String
 | 
			
		||||
  # == Multibyte proxy
 | 
			
		||||
  #
 | 
			
		||||
  # +mb_chars+ is a multibyte safe proxy for string methods.
 | 
			
		||||
  #
 | 
			
		||||
  # It creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
 | 
			
		||||
  # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
 | 
			
		||||
  # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
 | 
			
		||||
  #
 | 
			
		||||
  #   >> "lj".mb_chars.upcase.to_s
 | 
			
		||||
  #   => "LJ"
 | 
			
		||||
  #
 | 
			
		||||
  # NOTE: Ruby 2.4 and later support native Unicode case mappings:
 | 
			
		||||
  #
 | 
			
		||||
  #   >> "lj".upcase
 | 
			
		||||
  #   => "LJ"
 | 
			
		||||
  #
 | 
			
		||||
  # == Method chaining
 | 
			
		||||
  #
 | 
			
		||||
  # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
 | 
			
		||||
  # method chaining on the result of any of these methods.
 | 
			
		||||
  #
 | 
			
		||||
  #   name.mb_chars.reverse.length # => 12
 | 
			
		||||
  #
 | 
			
		||||
  # == Interoperability and configuration
 | 
			
		||||
  #
 | 
			
		||||
  # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
 | 
			
		||||
  # String and Char work like expected. The bang! methods change the internal string representation in the Chars
 | 
			
		||||
  # object. Interoperability problems can be resolved easily with a +to_s+ call.
 | 
			
		||||
  #
 | 
			
		||||
  # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
 | 
			
		||||
  # information about how to change the default Multibyte behavior see ActiveSupport::Multibyte.
 | 
			
		||||
  def mb_chars
 | 
			
		||||
    ActiveSupport::Multibyte.proxy_class.new(self)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # Returns +true+ if string has utf_8 encoding.
 | 
			
		||||
  #
 | 
			
		||||
  #   utf_8_str = "some string".encode "UTF-8"
 | 
			
		||||
  #   iso_str = "some string".encode "ISO-8859-1"
 | 
			
		||||
  #
 | 
			
		||||
  #   utf_8_str.is_utf8? # => true
 | 
			
		||||
  #   iso_str.is_utf8?   # => false
 | 
			
		||||
  def is_utf8?
 | 
			
		||||
    case encoding
 | 
			
		||||
    when Encoding::UTF_8, Encoding::US_ASCII
 | 
			
		||||
      valid_encoding?
 | 
			
		||||
    when Encoding::ASCII_8BIT
 | 
			
		||||
      dup.force_encoding(Encoding::UTF_8).valid_encoding?
 | 
			
		||||
    else
 | 
			
		||||
      false
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,16 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "active_support/core_ext/hash/deep_merge"
 | 
			
		||||
require "active_support/core_ext/hash/except"
 | 
			
		||||
require "active_support/core_ext/hash/slice"
 | 
			
		||||
begin
 | 
			
		||||
  require "i18n"
 | 
			
		||||
rescue LoadError => e
 | 
			
		||||
  $stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install"
 | 
			
		||||
  raise e
 | 
			
		||||
end
 | 
			
		||||
require "active_support/lazy_load_hooks"
 | 
			
		||||
 | 
			
		||||
ActiveSupport.run_load_hooks(:i18n)
 | 
			
		||||
I18n.load_path << File.expand_path("locale/en.yml", __dir__)
 | 
			
		||||
I18n.load_path << File.expand_path("locale/en.rb", __dir__)
 | 
			
		||||
@ -1,72 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "active_support/inflector/inflections"
 | 
			
		||||
 | 
			
		||||
#--
 | 
			
		||||
# Defines the standard inflection rules. These are the starting point for
 | 
			
		||||
# new projects and are not considered complete. The current set of inflection
 | 
			
		||||
# rules is frozen. This means, we do not change them to become more complete.
 | 
			
		||||
# This is a safety measure to keep existing applications from breaking.
 | 
			
		||||
#++
 | 
			
		||||
module ActiveSupport
 | 
			
		||||
  Inflector.inflections(:en) do |inflect|
 | 
			
		||||
    inflect.plural(/$/, "s")
 | 
			
		||||
    inflect.plural(/s$/i, "s")
 | 
			
		||||
    inflect.plural(/^(ax|test)is$/i, '\1es')
 | 
			
		||||
    inflect.plural(/(octop|vir)us$/i, '\1i')
 | 
			
		||||
    inflect.plural(/(octop|vir)i$/i, '\1i')
 | 
			
		||||
    inflect.plural(/(alias|status)$/i, '\1es')
 | 
			
		||||
    inflect.plural(/(bu)s$/i, '\1ses')
 | 
			
		||||
    inflect.plural(/(buffal|tomat)o$/i, '\1oes')
 | 
			
		||||
    inflect.plural(/([ti])um$/i, '\1a')
 | 
			
		||||
    inflect.plural(/([ti])a$/i, '\1a')
 | 
			
		||||
    inflect.plural(/sis$/i, "ses")
 | 
			
		||||
    inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
 | 
			
		||||
    inflect.plural(/(hive)$/i, '\1s')
 | 
			
		||||
    inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
 | 
			
		||||
    inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
 | 
			
		||||
    inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
 | 
			
		||||
    inflect.plural(/^(m|l)ouse$/i, '\1ice')
 | 
			
		||||
    inflect.plural(/^(m|l)ice$/i, '\1ice')
 | 
			
		||||
    inflect.plural(/^(ox)$/i, '\1en')
 | 
			
		||||
    inflect.plural(/^(oxen)$/i, '\1')
 | 
			
		||||
    inflect.plural(/(quiz)$/i, '\1zes')
 | 
			
		||||
 | 
			
		||||
    inflect.singular(/s$/i, "")
 | 
			
		||||
    inflect.singular(/(ss)$/i, '\1')
 | 
			
		||||
    inflect.singular(/(n)ews$/i, '\1ews')
 | 
			
		||||
    inflect.singular(/([ti])a$/i, '\1um')
 | 
			
		||||
    inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
 | 
			
		||||
    inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
 | 
			
		||||
    inflect.singular(/([^f])ves$/i, '\1fe')
 | 
			
		||||
    inflect.singular(/(hive)s$/i, '\1')
 | 
			
		||||
    inflect.singular(/(tive)s$/i, '\1')
 | 
			
		||||
    inflect.singular(/([lr])ves$/i, '\1f')
 | 
			
		||||
    inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
 | 
			
		||||
    inflect.singular(/(s)eries$/i, '\1eries')
 | 
			
		||||
    inflect.singular(/(m)ovies$/i, '\1ovie')
 | 
			
		||||
    inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
 | 
			
		||||
    inflect.singular(/^(m|l)ice$/i, '\1ouse')
 | 
			
		||||
    inflect.singular(/(bus)(es)?$/i, '\1')
 | 
			
		||||
    inflect.singular(/(o)es$/i, '\1')
 | 
			
		||||
    inflect.singular(/(shoe)s$/i, '\1')
 | 
			
		||||
    inflect.singular(/(cris|test)(is|es)$/i, '\1is')
 | 
			
		||||
    inflect.singular(/^(a)x[ie]s$/i, '\1xis')
 | 
			
		||||
    inflect.singular(/(octop|vir)(us|i)$/i, '\1us')
 | 
			
		||||
    inflect.singular(/(alias|status)(es)?$/i, '\1')
 | 
			
		||||
    inflect.singular(/^(ox)en/i, '\1')
 | 
			
		||||
    inflect.singular(/(vert|ind)ices$/i, '\1ex')
 | 
			
		||||
    inflect.singular(/(matr)ices$/i, '\1ix')
 | 
			
		||||
    inflect.singular(/(quiz)zes$/i, '\1')
 | 
			
		||||
    inflect.singular(/(database)s$/i, '\1')
 | 
			
		||||
 | 
			
		||||
    inflect.irregular("person", "people")
 | 
			
		||||
    inflect.irregular("man", "men")
 | 
			
		||||
    inflect.irregular("child", "children")
 | 
			
		||||
    inflect.irregular("sex", "sexes")
 | 
			
		||||
    inflect.irregular("move", "moves")
 | 
			
		||||
    inflect.irregular("zombie", "zombies")
 | 
			
		||||
 | 
			
		||||
    inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
# in case active_support/inflector is required without the rest of active_support
 | 
			
		||||
require "active_support/inflector/inflections"
 | 
			
		||||
require "active_support/inflector/transliterate"
 | 
			
		||||
require "active_support/inflector/methods"
 | 
			
		||||
 | 
			
		||||
require "active_support/inflections"
 | 
			
		||||
require "active_support/core_ext/string/inflections"
 | 
			
		||||
@ -1,255 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "concurrent/map"
 | 
			
		||||
require "active_support/i18n"
 | 
			
		||||
 | 
			
		||||
module ActiveSupport
 | 
			
		||||
  module Inflector
 | 
			
		||||
    extend self
 | 
			
		||||
 | 
			
		||||
    # A singleton instance of this class is yielded by Inflector.inflections,
 | 
			
		||||
    # which can then be used to specify additional inflection rules. If passed
 | 
			
		||||
    # an optional locale, rules for other languages can be specified. The
 | 
			
		||||
    # default locale is <tt>:en</tt>. Only rules for English are provided.
 | 
			
		||||
    #
 | 
			
		||||
    #   ActiveSupport::Inflector.inflections(:en) do |inflect|
 | 
			
		||||
    #     inflect.plural /^(ox)$/i, '\1\2en'
 | 
			
		||||
    #     inflect.singular /^(ox)en/i, '\1'
 | 
			
		||||
    #
 | 
			
		||||
    #     inflect.irregular 'octopus', 'octopi'
 | 
			
		||||
    #
 | 
			
		||||
    #     inflect.uncountable 'equipment'
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    # New rules are added at the top. So in the example above, the irregular
 | 
			
		||||
    # rule for octopus will now be the first of the pluralization and
 | 
			
		||||
    # singularization rules that is runs. This guarantees that your rules run
 | 
			
		||||
    # before any of the rules that may already have been loaded.
 | 
			
		||||
    class Inflections
 | 
			
		||||
      @__instance__ = Concurrent::Map.new
 | 
			
		||||
 | 
			
		||||
      class Uncountables < Array
 | 
			
		||||
        def initialize
 | 
			
		||||
          @regex_array = []
 | 
			
		||||
          super
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def delete(entry)
 | 
			
		||||
          super entry
 | 
			
		||||
          @regex_array.delete(to_regex(entry))
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def <<(*word)
 | 
			
		||||
          add(word)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def add(words)
 | 
			
		||||
          words = words.flatten.map(&:downcase)
 | 
			
		||||
          concat(words)
 | 
			
		||||
          @regex_array += words.map { |word| to_regex(word) }
 | 
			
		||||
          self
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def uncountable?(str)
 | 
			
		||||
          @regex_array.any? { |regex| regex.match? str }
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        private
 | 
			
		||||
          def to_regex(string)
 | 
			
		||||
            /\b#{::Regexp.escape(string)}\Z/i
 | 
			
		||||
          end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def self.instance(locale = :en)
 | 
			
		||||
        @__instance__[locale] ||= new
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms
 | 
			
		||||
 | 
			
		||||
      attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc:
 | 
			
		||||
 | 
			
		||||
      def initialize
 | 
			
		||||
        @plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {}
 | 
			
		||||
        define_acronym_regex_patterns
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Private, for the test suite.
 | 
			
		||||
      def initialize_dup(orig) # :nodoc:
 | 
			
		||||
        %w(plurals singulars uncountables humans acronyms).each do |scope|
 | 
			
		||||
          instance_variable_set("@#{scope}", orig.public_send(scope).dup)
 | 
			
		||||
        end
 | 
			
		||||
        define_acronym_regex_patterns
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Specifies a new acronym. An acronym must be specified as it will appear
 | 
			
		||||
      # in a camelized string. An underscore string that contains the acronym
 | 
			
		||||
      # will retain the acronym when passed to +camelize+, +humanize+, or
 | 
			
		||||
      # +titleize+. A camelized string that contains the acronym will maintain
 | 
			
		||||
      # the acronym when titleized or humanized, and will convert the acronym
 | 
			
		||||
      # into a non-delimited single lowercase word when passed to +underscore+.
 | 
			
		||||
      #
 | 
			
		||||
      #   acronym 'HTML'
 | 
			
		||||
      #   titleize 'html'     # => 'HTML'
 | 
			
		||||
      #   camelize 'html'     # => 'HTML'
 | 
			
		||||
      #   underscore 'MyHTML' # => 'my_html'
 | 
			
		||||
      #
 | 
			
		||||
      # The acronym, however, must occur as a delimited unit and not be part of
 | 
			
		||||
      # another word for conversions to recognize it:
 | 
			
		||||
      #
 | 
			
		||||
      #   acronym 'HTTP'
 | 
			
		||||
      #   camelize 'my_http_delimited' # => 'MyHTTPDelimited'
 | 
			
		||||
      #   camelize 'https'             # => 'Https', not 'HTTPs'
 | 
			
		||||
      #   underscore 'HTTPS'           # => 'http_s', not 'https'
 | 
			
		||||
      #
 | 
			
		||||
      #   acronym 'HTTPS'
 | 
			
		||||
      #   camelize 'https'   # => 'HTTPS'
 | 
			
		||||
      #   underscore 'HTTPS' # => 'https'
 | 
			
		||||
      #
 | 
			
		||||
      # Note: Acronyms that are passed to +pluralize+ will no longer be
 | 
			
		||||
      # recognized, since the acronym will not occur as a delimited unit in the
 | 
			
		||||
      # pluralized result. To work around this, you must specify the pluralized
 | 
			
		||||
      # form as an acronym as well:
 | 
			
		||||
      #
 | 
			
		||||
      #    acronym 'API'
 | 
			
		||||
      #    camelize(pluralize('api')) # => 'Apis'
 | 
			
		||||
      #
 | 
			
		||||
      #    acronym 'APIs'
 | 
			
		||||
      #    camelize(pluralize('api')) # => 'APIs'
 | 
			
		||||
      #
 | 
			
		||||
      # +acronym+ may be used to specify any word that contains an acronym or
 | 
			
		||||
      # otherwise needs to maintain a non-standard capitalization. The only
 | 
			
		||||
      # restriction is that the word must begin with a capital letter.
 | 
			
		||||
      #
 | 
			
		||||
      #   acronym 'RESTful'
 | 
			
		||||
      #   underscore 'RESTful'           # => 'restful'
 | 
			
		||||
      #   underscore 'RESTfulController' # => 'restful_controller'
 | 
			
		||||
      #   titleize 'RESTfulController'   # => 'RESTful Controller'
 | 
			
		||||
      #   camelize 'restful'             # => 'RESTful'
 | 
			
		||||
      #   camelize 'restful_controller'  # => 'RESTfulController'
 | 
			
		||||
      #
 | 
			
		||||
      #   acronym 'McDonald'
 | 
			
		||||
      #   underscore 'McDonald' # => 'mcdonald'
 | 
			
		||||
      #   camelize 'mcdonald'   # => 'McDonald'
 | 
			
		||||
      def acronym(word)
 | 
			
		||||
        @acronyms[word.downcase] = word
 | 
			
		||||
        define_acronym_regex_patterns
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Specifies a new pluralization rule and its replacement. The rule can
 | 
			
		||||
      # either be a string or a regular expression. The replacement should
 | 
			
		||||
      # always be a string that may include references to the matched data from
 | 
			
		||||
      # the rule.
 | 
			
		||||
      def plural(rule, replacement)
 | 
			
		||||
        @uncountables.delete(rule) if rule.is_a?(String)
 | 
			
		||||
        @uncountables.delete(replacement)
 | 
			
		||||
        @plurals.prepend([rule, replacement])
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Specifies a new singularization rule and its replacement. The rule can
 | 
			
		||||
      # either be a string or a regular expression. The replacement should
 | 
			
		||||
      # always be a string that may include references to the matched data from
 | 
			
		||||
      # the rule.
 | 
			
		||||
      def singular(rule, replacement)
 | 
			
		||||
        @uncountables.delete(rule) if rule.is_a?(String)
 | 
			
		||||
        @uncountables.delete(replacement)
 | 
			
		||||
        @singulars.prepend([rule, replacement])
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Specifies a new irregular that applies to both pluralization and
 | 
			
		||||
      # singularization at the same time. This can only be used for strings, not
 | 
			
		||||
      # regular expressions. You simply pass the irregular in singular and
 | 
			
		||||
      # plural form.
 | 
			
		||||
      #
 | 
			
		||||
      #   irregular 'octopus', 'octopi'
 | 
			
		||||
      #   irregular 'person', 'people'
 | 
			
		||||
      def irregular(singular, plural)
 | 
			
		||||
        @uncountables.delete(singular)
 | 
			
		||||
        @uncountables.delete(plural)
 | 
			
		||||
 | 
			
		||||
        s0 = singular[0]
 | 
			
		||||
        srest = singular[1..-1]
 | 
			
		||||
 | 
			
		||||
        p0 = plural[0]
 | 
			
		||||
        prest = plural[1..-1]
 | 
			
		||||
 | 
			
		||||
        if s0.upcase == p0.upcase
 | 
			
		||||
          plural(/(#{s0})#{srest}$/i, '\1' + prest)
 | 
			
		||||
          plural(/(#{p0})#{prest}$/i, '\1' + prest)
 | 
			
		||||
 | 
			
		||||
          singular(/(#{s0})#{srest}$/i, '\1' + srest)
 | 
			
		||||
          singular(/(#{p0})#{prest}$/i, '\1' + srest)
 | 
			
		||||
        else
 | 
			
		||||
          plural(/#{s0.upcase}(?i)#{srest}$/,   p0.upcase   + prest)
 | 
			
		||||
          plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest)
 | 
			
		||||
          plural(/#{p0.upcase}(?i)#{prest}$/,   p0.upcase   + prest)
 | 
			
		||||
          plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest)
 | 
			
		||||
 | 
			
		||||
          singular(/#{s0.upcase}(?i)#{srest}$/,   s0.upcase   + srest)
 | 
			
		||||
          singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest)
 | 
			
		||||
          singular(/#{p0.upcase}(?i)#{prest}$/,   s0.upcase   + srest)
 | 
			
		||||
          singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Specifies words that are uncountable and should not be inflected.
 | 
			
		||||
      #
 | 
			
		||||
      #   uncountable 'money'
 | 
			
		||||
      #   uncountable 'money', 'information'
 | 
			
		||||
      #   uncountable %w( money information rice )
 | 
			
		||||
      def uncountable(*words)
 | 
			
		||||
        @uncountables.add(words)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Specifies a humanized form of a string by a regular expression rule or
 | 
			
		||||
      # by a string mapping. When using a regular expression based replacement,
 | 
			
		||||
      # the normal humanize formatting is called after the replacement. When a
 | 
			
		||||
      # string is used, the human form should be specified as desired (example:
 | 
			
		||||
      # 'The name', not 'the_name').
 | 
			
		||||
      #
 | 
			
		||||
      #   human /_cnt$/i, '\1_count'
 | 
			
		||||
      #   human 'legacy_col_person_name', 'Name'
 | 
			
		||||
      def human(rule, replacement)
 | 
			
		||||
        @humans.prepend([rule, replacement])
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Clears the loaded inflections within a given scope (default is
 | 
			
		||||
      # <tt>:all</tt>). Give the scope as a symbol of the inflection type, the
 | 
			
		||||
      # options are: <tt>:plurals</tt>, <tt>:singulars</tt>, <tt>:uncountables</tt>,
 | 
			
		||||
      # <tt>:humans</tt>.
 | 
			
		||||
      #
 | 
			
		||||
      #   clear :all
 | 
			
		||||
      #   clear :plurals
 | 
			
		||||
      def clear(scope = :all)
 | 
			
		||||
        case scope
 | 
			
		||||
        when :all
 | 
			
		||||
          @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
 | 
			
		||||
        else
 | 
			
		||||
          instance_variable_set "@#{scope}", []
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      private
 | 
			
		||||
        def define_acronym_regex_patterns
 | 
			
		||||
          @acronym_regex             = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/
 | 
			
		||||
          @acronyms_camelize_regex   = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/
 | 
			
		||||
          @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/
 | 
			
		||||
        end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Yields a singleton instance of Inflector::Inflections so you can specify
 | 
			
		||||
    # additional inflector rules. If passed an optional locale, rules for other
 | 
			
		||||
    # languages can be specified. If not specified, defaults to <tt>:en</tt>.
 | 
			
		||||
    # Only rules for English are provided.
 | 
			
		||||
    #
 | 
			
		||||
    #   ActiveSupport::Inflector.inflections(:en) do |inflect|
 | 
			
		||||
    #     inflect.uncountable 'rails'
 | 
			
		||||
    #   end
 | 
			
		||||
    def inflections(locale = :en)
 | 
			
		||||
      if block_given?
 | 
			
		||||
        yield Inflections.instance(locale)
 | 
			
		||||
      else
 | 
			
		||||
        Inflections.instance(locale)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,400 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "active_support/inflections"
 | 
			
		||||
require "active_support/core_ext/object/blank"
 | 
			
		||||
 | 
			
		||||
module ActiveSupport
 | 
			
		||||
  # The Inflector transforms words from singular to plural, class names to table
 | 
			
		||||
  # names, modularized class names to ones without, and class names to foreign
 | 
			
		||||
  # keys. The default inflections for pluralization, singularization, and
 | 
			
		||||
  # uncountable words are kept in inflections.rb.
 | 
			
		||||
  #
 | 
			
		||||
  # The Rails core team has stated patches for the inflections library will not
 | 
			
		||||
  # be accepted in order to avoid breaking legacy applications which may be
 | 
			
		||||
  # relying on errant inflections. If you discover an incorrect inflection and
 | 
			
		||||
  # require it for your application or wish to define rules for languages other
 | 
			
		||||
  # than English, please correct or add them yourself (explained below).
 | 
			
		||||
  module Inflector
 | 
			
		||||
    extend self
 | 
			
		||||
 | 
			
		||||
    # Returns the plural form of the word in the string.
 | 
			
		||||
    #
 | 
			
		||||
    # If passed an optional +locale+ parameter, the word will be
 | 
			
		||||
    # pluralized using rules defined for that language. By default,
 | 
			
		||||
    # this parameter is set to <tt>:en</tt>.
 | 
			
		||||
    #
 | 
			
		||||
    #   pluralize('post')             # => "posts"
 | 
			
		||||
    #   pluralize('octopus')          # => "octopi"
 | 
			
		||||
    #   pluralize('sheep')            # => "sheep"
 | 
			
		||||
    #   pluralize('words')            # => "words"
 | 
			
		||||
    #   pluralize('CamelOctopus')     # => "CamelOctopi"
 | 
			
		||||
    #   pluralize('ley', :es)         # => "leyes"
 | 
			
		||||
    def pluralize(word, locale = :en)
 | 
			
		||||
      apply_inflections(word, inflections(locale).plurals, locale)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # The reverse of #pluralize, returns the singular form of a word in a
 | 
			
		||||
    # string.
 | 
			
		||||
    #
 | 
			
		||||
    # If passed an optional +locale+ parameter, the word will be
 | 
			
		||||
    # singularized using rules defined for that language. By default,
 | 
			
		||||
    # this parameter is set to <tt>:en</tt>.
 | 
			
		||||
    #
 | 
			
		||||
    #   singularize('posts')            # => "post"
 | 
			
		||||
    #   singularize('octopi')           # => "octopus"
 | 
			
		||||
    #   singularize('sheep')            # => "sheep"
 | 
			
		||||
    #   singularize('word')             # => "word"
 | 
			
		||||
    #   singularize('CamelOctopi')      # => "CamelOctopus"
 | 
			
		||||
    #   singularize('leyes', :es)       # => "ley"
 | 
			
		||||
    def singularize(word, locale = :en)
 | 
			
		||||
      apply_inflections(word, inflections(locale).singulars, locale)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Converts strings to UpperCamelCase.
 | 
			
		||||
    # If the +uppercase_first_letter+ parameter is set to false, then produces
 | 
			
		||||
    # lowerCamelCase.
 | 
			
		||||
    #
 | 
			
		||||
    # Also converts '/' to '::' which is useful for converting
 | 
			
		||||
    # paths to namespaces.
 | 
			
		||||
    #
 | 
			
		||||
    #   camelize('active_model')                # => "ActiveModel"
 | 
			
		||||
    #   camelize('active_model', false)         # => "activeModel"
 | 
			
		||||
    #   camelize('active_model/errors')         # => "ActiveModel::Errors"
 | 
			
		||||
    #   camelize('active_model/errors', false)  # => "activeModel::Errors"
 | 
			
		||||
    #
 | 
			
		||||
    # As a rule of thumb you can think of +camelize+ as the inverse of
 | 
			
		||||
    # #underscore, though there are cases where that does not hold:
 | 
			
		||||
    #
 | 
			
		||||
    #   camelize(underscore('SSLError'))        # => "SslError"
 | 
			
		||||
    def camelize(term, uppercase_first_letter = true)
 | 
			
		||||
      string = term.to_s
 | 
			
		||||
      if uppercase_first_letter
 | 
			
		||||
        string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
 | 
			
		||||
      else
 | 
			
		||||
        string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase }
 | 
			
		||||
      end
 | 
			
		||||
      string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
 | 
			
		||||
      string.gsub!("/", "::")
 | 
			
		||||
      string
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Makes an underscored, lowercase form from the expression in the string.
 | 
			
		||||
    #
 | 
			
		||||
    # Changes '::' to '/' to convert namespaces to paths.
 | 
			
		||||
    #
 | 
			
		||||
    #   underscore('ActiveModel')         # => "active_model"
 | 
			
		||||
    #   underscore('ActiveModel::Errors') # => "active_model/errors"
 | 
			
		||||
    #
 | 
			
		||||
    # As a rule of thumb you can think of +underscore+ as the inverse of
 | 
			
		||||
    # #camelize, though there are cases where that does not hold:
 | 
			
		||||
    #
 | 
			
		||||
    #   camelize(underscore('SSLError'))  # => "SslError"
 | 
			
		||||
    def underscore(camel_cased_word)
 | 
			
		||||
      return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
 | 
			
		||||
      word = camel_cased_word.to_s.gsub("::", "/")
 | 
			
		||||
      word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" }
 | 
			
		||||
      word.gsub!(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" }
 | 
			
		||||
      word.tr!("-", "_")
 | 
			
		||||
      word.downcase!
 | 
			
		||||
      word
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Tweaks an attribute name for display to end users.
 | 
			
		||||
    #
 | 
			
		||||
    # Specifically, performs these transformations:
 | 
			
		||||
    #
 | 
			
		||||
    # * Applies human inflection rules to the argument.
 | 
			
		||||
    # * Deletes leading underscores, if any.
 | 
			
		||||
    # * Removes a "_id" suffix if present.
 | 
			
		||||
    # * Replaces underscores with spaces, if any.
 | 
			
		||||
    # * Downcases all words except acronyms.
 | 
			
		||||
    # * Capitalizes the first word.
 | 
			
		||||
    # The capitalization of the first word can be turned off by setting the
 | 
			
		||||
    # +:capitalize+ option to false (default is true).
 | 
			
		||||
    #
 | 
			
		||||
    # The trailing '_id' can be kept and capitalized by setting the
 | 
			
		||||
    # optional parameter +keep_id_suffix+ to true (default is false).
 | 
			
		||||
    #
 | 
			
		||||
    #   humanize('employee_salary')                  # => "Employee salary"
 | 
			
		||||
    #   humanize('author_id')                        # => "Author"
 | 
			
		||||
    #   humanize('author_id', capitalize: false)     # => "author"
 | 
			
		||||
    #   humanize('_id')                              # => "Id"
 | 
			
		||||
    #   humanize('author_id', keep_id_suffix: true)  # => "Author Id"
 | 
			
		||||
    #
 | 
			
		||||
    # If "SSL" was defined to be an acronym:
 | 
			
		||||
    #
 | 
			
		||||
    #   humanize('ssl_error') # => "SSL error"
 | 
			
		||||
    #
 | 
			
		||||
    def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
 | 
			
		||||
      result = lower_case_and_underscored_word.to_s.dup
 | 
			
		||||
 | 
			
		||||
      inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
 | 
			
		||||
 | 
			
		||||
      result.sub!(/\A_+/, "")
 | 
			
		||||
      unless keep_id_suffix
 | 
			
		||||
        result.delete_suffix!("_id")
 | 
			
		||||
      end
 | 
			
		||||
      result.tr!("_", " ")
 | 
			
		||||
 | 
			
		||||
      result.gsub!(/([a-z\d]*)/i) do |match|
 | 
			
		||||
        "#{inflections.acronyms[match.downcase] || match.downcase}"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      if capitalize
 | 
			
		||||
        result.sub!(/\A\w/) { |match| match.upcase }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      result
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Converts just the first character to uppercase.
 | 
			
		||||
    #
 | 
			
		||||
    #   upcase_first('what a Lovely Day') # => "What a Lovely Day"
 | 
			
		||||
    #   upcase_first('w')                 # => "W"
 | 
			
		||||
    #   upcase_first('')                  # => ""
 | 
			
		||||
    def upcase_first(string)
 | 
			
		||||
      string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ""
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Capitalizes all the words and replaces some characters in the string to
 | 
			
		||||
    # create a nicer looking title. +titleize+ is meant for creating pretty
 | 
			
		||||
    # output. It is not used in the Rails internals.
 | 
			
		||||
    #
 | 
			
		||||
    # The trailing '_id','Id'.. can be kept and capitalized by setting the
 | 
			
		||||
    # optional parameter +keep_id_suffix+ to true.
 | 
			
		||||
    # By default, this parameter is false.
 | 
			
		||||
    #
 | 
			
		||||
    # +titleize+ is also aliased as +titlecase+.
 | 
			
		||||
    #
 | 
			
		||||
    #   titleize('man from the boondocks')                       # => "Man From The Boondocks"
 | 
			
		||||
    #   titleize('x-men: the last stand')                        # => "X Men: The Last Stand"
 | 
			
		||||
    #   titleize('TheManWithoutAPast')                           # => "The Man Without A Past"
 | 
			
		||||
    #   titleize('raiders_of_the_lost_ark')                      # => "Raiders Of The Lost Ark"
 | 
			
		||||
    #   titleize('string_ending_with_id', keep_id_suffix: true)  # => "String Ending With Id"
 | 
			
		||||
    def titleize(word, keep_id_suffix: false)
 | 
			
		||||
      humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`()])[a-z]/) do |match|
 | 
			
		||||
        match.capitalize
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Creates the name of a table like Rails does for models to table names.
 | 
			
		||||
    # This method uses the #pluralize method on the last word in the string.
 | 
			
		||||
    #
 | 
			
		||||
    #   tableize('RawScaledScorer') # => "raw_scaled_scorers"
 | 
			
		||||
    #   tableize('ham_and_egg')     # => "ham_and_eggs"
 | 
			
		||||
    #   tableize('fancyCategory')   # => "fancy_categories"
 | 
			
		||||
    def tableize(class_name)
 | 
			
		||||
      pluralize(underscore(class_name))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Creates a class name from a plural table name like Rails does for table
 | 
			
		||||
    # names to models. Note that this returns a string and not a Class (To
 | 
			
		||||
    # convert to an actual class follow +classify+ with #constantize).
 | 
			
		||||
    #
 | 
			
		||||
    #   classify('ham_and_eggs') # => "HamAndEgg"
 | 
			
		||||
    #   classify('posts')        # => "Post"
 | 
			
		||||
    #
 | 
			
		||||
    # Singular names are not handled correctly:
 | 
			
		||||
    #
 | 
			
		||||
    #   classify('calculus')     # => "Calculu"
 | 
			
		||||
    def classify(table_name)
 | 
			
		||||
      # strip out any leading schema name
 | 
			
		||||
      camelize(singularize(table_name.to_s.sub(/.*\./, "")))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Replaces underscores with dashes in the string.
 | 
			
		||||
    #
 | 
			
		||||
    #   dasherize('puni_puni') # => "puni-puni"
 | 
			
		||||
    def dasherize(underscored_word)
 | 
			
		||||
      underscored_word.tr("_", "-")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Removes the module part from the expression in the string.
 | 
			
		||||
    #
 | 
			
		||||
    #   demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections"
 | 
			
		||||
    #   demodulize('Inflections')                           # => "Inflections"
 | 
			
		||||
    #   demodulize('::Inflections')                         # => "Inflections"
 | 
			
		||||
    #   demodulize('')                                      # => ""
 | 
			
		||||
    #
 | 
			
		||||
    # See also #deconstantize.
 | 
			
		||||
    def demodulize(path)
 | 
			
		||||
      path = path.to_s
 | 
			
		||||
      if i = path.rindex("::")
 | 
			
		||||
        path[(i + 2)..-1]
 | 
			
		||||
      else
 | 
			
		||||
        path
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Removes the rightmost segment from the constant expression in the string.
 | 
			
		||||
    #
 | 
			
		||||
    #   deconstantize('Net::HTTP')   # => "Net"
 | 
			
		||||
    #   deconstantize('::Net::HTTP') # => "::Net"
 | 
			
		||||
    #   deconstantize('String')      # => ""
 | 
			
		||||
    #   deconstantize('::String')    # => ""
 | 
			
		||||
    #   deconstantize('')            # => ""
 | 
			
		||||
    #
 | 
			
		||||
    # See also #demodulize.
 | 
			
		||||
    def deconstantize(path)
 | 
			
		||||
      path.to_s[0, path.rindex("::") || 0] # implementation based on the one in facets' Module#spacename
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Creates a foreign key name from a class name.
 | 
			
		||||
    # +separate_class_name_and_id_with_underscore+ sets whether
 | 
			
		||||
    # the method should put '_' between the name and 'id'.
 | 
			
		||||
    #
 | 
			
		||||
    #   foreign_key('Message')        # => "message_id"
 | 
			
		||||
    #   foreign_key('Message', false) # => "messageid"
 | 
			
		||||
    #   foreign_key('Admin::Post')    # => "post_id"
 | 
			
		||||
    def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
 | 
			
		||||
      underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Tries to find a constant with the name specified in the argument string.
 | 
			
		||||
    #
 | 
			
		||||
    #   constantize('Module')   # => Module
 | 
			
		||||
    #   constantize('Foo::Bar') # => Foo::Bar
 | 
			
		||||
    #
 | 
			
		||||
    # The name is assumed to be the one of a top-level constant, no matter
 | 
			
		||||
    # whether it starts with "::" or not. No lexical context is taken into
 | 
			
		||||
    # account:
 | 
			
		||||
    #
 | 
			
		||||
    #   C = 'outside'
 | 
			
		||||
    #   module M
 | 
			
		||||
    #     C = 'inside'
 | 
			
		||||
    #     C                # => 'inside'
 | 
			
		||||
    #     constantize('C') # => 'outside', same as ::C
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    # NameError is raised when the name is not in CamelCase or the constant is
 | 
			
		||||
    # unknown.
 | 
			
		||||
    def constantize(camel_cased_word)
 | 
			
		||||
      if camel_cased_word.blank? || !camel_cased_word.include?("::")
 | 
			
		||||
        Object.const_get(camel_cased_word)
 | 
			
		||||
      else
 | 
			
		||||
        names = camel_cased_word.split("::")
 | 
			
		||||
 | 
			
		||||
        # Trigger a built-in NameError exception including the ill-formed constant in the message.
 | 
			
		||||
        Object.const_get(camel_cased_word) if names.empty?
 | 
			
		||||
 | 
			
		||||
        # Remove the first blank element in case of '::ClassName' notation.
 | 
			
		||||
        names.shift if names.size > 1 && names.first.empty?
 | 
			
		||||
 | 
			
		||||
        names.inject(Object) do |constant, name|
 | 
			
		||||
          if constant == Object
 | 
			
		||||
            constant.const_get(name)
 | 
			
		||||
          else
 | 
			
		||||
            candidate = constant.const_get(name)
 | 
			
		||||
            next candidate if constant.const_defined?(name, false)
 | 
			
		||||
            next candidate unless Object.const_defined?(name)
 | 
			
		||||
 | 
			
		||||
            # Go down the ancestors to check if it is owned directly. The check
 | 
			
		||||
            # stops when we reach Object or the end of ancestors tree.
 | 
			
		||||
            constant = constant.ancestors.inject(constant) do |const, ancestor|
 | 
			
		||||
              break const    if ancestor == Object
 | 
			
		||||
              break ancestor if ancestor.const_defined?(name, false)
 | 
			
		||||
              const
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
            # owner is in Object, so raise
 | 
			
		||||
            constant.const_get(name, false)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Tries to find a constant with the name specified in the argument string.
 | 
			
		||||
    #
 | 
			
		||||
    #   safe_constantize('Module')   # => Module
 | 
			
		||||
    #   safe_constantize('Foo::Bar') # => Foo::Bar
 | 
			
		||||
    #
 | 
			
		||||
    # The name is assumed to be the one of a top-level constant, no matter
 | 
			
		||||
    # whether it starts with "::" or not. No lexical context is taken into
 | 
			
		||||
    # account:
 | 
			
		||||
    #
 | 
			
		||||
    #   C = 'outside'
 | 
			
		||||
    #   module M
 | 
			
		||||
    #     C = 'inside'
 | 
			
		||||
    #     C                     # => 'inside'
 | 
			
		||||
    #     safe_constantize('C') # => 'outside', same as ::C
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    # +nil+ is returned when the name is not in CamelCase or the constant (or
 | 
			
		||||
    # part of it) is unknown.
 | 
			
		||||
    #
 | 
			
		||||
    #   safe_constantize('blargle')                  # => nil
 | 
			
		||||
    #   safe_constantize('UnknownModule')            # => nil
 | 
			
		||||
    #   safe_constantize('UnknownModule::Foo::Bar')  # => nil
 | 
			
		||||
    def safe_constantize(camel_cased_word)
 | 
			
		||||
      constantize(camel_cased_word)
 | 
			
		||||
    rescue NameError => e
 | 
			
		||||
      raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
 | 
			
		||||
        e.name.to_s == camel_cased_word.to_s)
 | 
			
		||||
    rescue LoadError => e
 | 
			
		||||
      message = e.respond_to?(:original_message) ? e.original_message : e.message
 | 
			
		||||
      raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(message)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Returns the suffix that should be added to a number to denote the position
 | 
			
		||||
    # in an ordered sequence such as 1st, 2nd, 3rd, 4th.
 | 
			
		||||
    #
 | 
			
		||||
    #   ordinal(1)     # => "st"
 | 
			
		||||
    #   ordinal(2)     # => "nd"
 | 
			
		||||
    #   ordinal(1002)  # => "nd"
 | 
			
		||||
    #   ordinal(1003)  # => "rd"
 | 
			
		||||
    #   ordinal(-11)   # => "th"
 | 
			
		||||
    #   ordinal(-1021) # => "st"
 | 
			
		||||
    def ordinal(number)
 | 
			
		||||
      I18n.translate("number.nth.ordinals", number: number)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Turns a number into an ordinal string used to denote the position in an
 | 
			
		||||
    # ordered sequence such as 1st, 2nd, 3rd, 4th.
 | 
			
		||||
    #
 | 
			
		||||
    #   ordinalize(1)     # => "1st"
 | 
			
		||||
    #   ordinalize(2)     # => "2nd"
 | 
			
		||||
    #   ordinalize(1002)  # => "1002nd"
 | 
			
		||||
    #   ordinalize(1003)  # => "1003rd"
 | 
			
		||||
    #   ordinalize(-11)   # => "-11th"
 | 
			
		||||
    #   ordinalize(-1021) # => "-1021st"
 | 
			
		||||
    def ordinalize(number)
 | 
			
		||||
      I18n.translate("number.nth.ordinalized", number: number)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
      # Mounts a regular expression, returned as a string to ease interpolation,
 | 
			
		||||
      # that will match part by part the given constant.
 | 
			
		||||
      #
 | 
			
		||||
      #   const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
 | 
			
		||||
      #   const_regexp("::")            # => "::"
 | 
			
		||||
      def const_regexp(camel_cased_word)
 | 
			
		||||
        parts = camel_cased_word.split("::")
 | 
			
		||||
 | 
			
		||||
        return Regexp.escape(camel_cased_word) if parts.blank?
 | 
			
		||||
 | 
			
		||||
        last = parts.pop
 | 
			
		||||
 | 
			
		||||
        parts.reverse!.inject(last) do |acc, part|
 | 
			
		||||
          part.empty? ? acc : "#{part}(::#{acc})?"
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Applies inflection rules for +singularize+ and +pluralize+.
 | 
			
		||||
      #
 | 
			
		||||
      # If passed an optional +locale+ parameter, the uncountables will be
 | 
			
		||||
      # found for that locale.
 | 
			
		||||
      #
 | 
			
		||||
      #  apply_inflections('post', inflections.plurals, :en)    # => "posts"
 | 
			
		||||
      #  apply_inflections('posts', inflections.singulars, :en) # => "post"
 | 
			
		||||
      def apply_inflections(word, rules, locale = :en)
 | 
			
		||||
        result = word.to_s.dup
 | 
			
		||||
 | 
			
		||||
        if word.empty? || inflections(locale).uncountables.uncountable?(result)
 | 
			
		||||
          result
 | 
			
		||||
        else
 | 
			
		||||
          rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
 | 
			
		||||
          result
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,147 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "active_support/core_ext/string/multibyte"
 | 
			
		||||
require "active_support/i18n"
 | 
			
		||||
 | 
			
		||||
module ActiveSupport
 | 
			
		||||
  module Inflector
 | 
			
		||||
    ALLOWED_ENCODINGS_FOR_TRANSLITERATE = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030].freeze
 | 
			
		||||
 | 
			
		||||
    # Replaces non-ASCII characters with an ASCII approximation, or if none
 | 
			
		||||
    # exists, a replacement character which defaults to "?".
 | 
			
		||||
    #
 | 
			
		||||
    #    transliterate('Ærøskøbing')
 | 
			
		||||
    #    # => "AEroskobing"
 | 
			
		||||
    #
 | 
			
		||||
    # Default approximations are provided for Western/Latin characters,
 | 
			
		||||
    # e.g, "ø", "ñ", "é", "ß", etc.
 | 
			
		||||
    #
 | 
			
		||||
    # This method is I18n aware, so you can set up custom approximations for a
 | 
			
		||||
    # locale. This can be useful, for example, to transliterate German's "ü"
 | 
			
		||||
    # and "ö" to "ue" and "oe", or to add support for transliterating Russian
 | 
			
		||||
    # to ASCII.
 | 
			
		||||
    #
 | 
			
		||||
    # In order to make your custom transliterations available, you must set
 | 
			
		||||
    # them as the <tt>i18n.transliterate.rule</tt> i18n key:
 | 
			
		||||
    #
 | 
			
		||||
    #   # Store the transliterations in locales/de.yml
 | 
			
		||||
    #   i18n:
 | 
			
		||||
    #     transliterate:
 | 
			
		||||
    #       rule:
 | 
			
		||||
    #         ü: "ue"
 | 
			
		||||
    #         ö: "oe"
 | 
			
		||||
    #
 | 
			
		||||
    #   # Or set them using Ruby
 | 
			
		||||
    #   I18n.backend.store_translations(:de, i18n: {
 | 
			
		||||
    #     transliterate: {
 | 
			
		||||
    #       rule: {
 | 
			
		||||
    #         'ü' => 'ue',
 | 
			
		||||
    #         'ö' => 'oe'
 | 
			
		||||
    #       }
 | 
			
		||||
    #     }
 | 
			
		||||
    #   })
 | 
			
		||||
    #
 | 
			
		||||
    # The value for <tt>i18n.transliterate.rule</tt> can be a simple Hash that
 | 
			
		||||
    # maps characters to ASCII approximations as shown above, or, for more
 | 
			
		||||
    # complex requirements, a Proc:
 | 
			
		||||
    #
 | 
			
		||||
    #   I18n.backend.store_translations(:de, i18n: {
 | 
			
		||||
    #     transliterate: {
 | 
			
		||||
    #       rule: ->(string) { MyTransliterator.transliterate(string) }
 | 
			
		||||
    #     }
 | 
			
		||||
    #   })
 | 
			
		||||
    #
 | 
			
		||||
    # Now you can have different transliterations for each locale:
 | 
			
		||||
    #
 | 
			
		||||
    #   transliterate('Jürgen', locale: :en)
 | 
			
		||||
    #   # => "Jurgen"
 | 
			
		||||
    #
 | 
			
		||||
    #   transliterate('Jürgen', locale: :de)
 | 
			
		||||
    #   # => "Juergen"
 | 
			
		||||
    #
 | 
			
		||||
    # Transliteration is restricted to UTF-8, US-ASCII and GB18030 strings
 | 
			
		||||
    # Other encodings will raise an ArgumentError.
 | 
			
		||||
    def transliterate(string, replacement = "?", locale: nil)
 | 
			
		||||
      string = string.dup if string.frozen?
 | 
			
		||||
      raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
 | 
			
		||||
      raise ArgumentError, "Cannot transliterate strings with #{string.encoding} encoding" unless ALLOWED_ENCODINGS_FOR_TRANSLITERATE.include?(string.encoding)
 | 
			
		||||
 | 
			
		||||
      input_encoding = string.encoding
 | 
			
		||||
 | 
			
		||||
      # US-ASCII is a subset of UTF-8 so we'll force encoding as UTF-8 if
 | 
			
		||||
      # US-ASCII is given. This way we can let tidy_bytes handle the string
 | 
			
		||||
      # in the same way as we do for UTF-8
 | 
			
		||||
      string.force_encoding(Encoding::UTF_8) if string.encoding == Encoding::US_ASCII
 | 
			
		||||
 | 
			
		||||
      # GB18030 is Unicode compatible but is not a direct mapping so needs to be
 | 
			
		||||
      # transcoded. Using invalid/undef :replace will result in loss of data in
 | 
			
		||||
      # the event of invalid characters, but since tidy_bytes will replace
 | 
			
		||||
      # invalid/undef with a "?" we're safe to do the same beforehand
 | 
			
		||||
      string.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) if string.encoding == Encoding::GB18030
 | 
			
		||||
 | 
			
		||||
      transliterated = I18n.transliterate(
 | 
			
		||||
        ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc),
 | 
			
		||||
        replacement: replacement,
 | 
			
		||||
        locale: locale
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      # Restore the string encoding of the input if it was not UTF-8.
 | 
			
		||||
      # Apply invalid/undef :replace as tidy_bytes does
 | 
			
		||||
      transliterated.encode!(input_encoding, invalid: :replace, undef: :replace) if input_encoding != transliterated.encoding
 | 
			
		||||
 | 
			
		||||
      transliterated
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Replaces special characters in a string so that it may be used as part of
 | 
			
		||||
    # a 'pretty' URL.
 | 
			
		||||
    #
 | 
			
		||||
    #   parameterize("Donald E. Knuth") # => "donald-e-knuth"
 | 
			
		||||
    #   parameterize("^très|Jolie-- ")  # => "tres-jolie"
 | 
			
		||||
    #
 | 
			
		||||
    # To use a custom separator, override the +separator+ argument.
 | 
			
		||||
    #
 | 
			
		||||
    #   parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
 | 
			
		||||
    #   parameterize("^très|Jolie__ ", separator: '_')  # => "tres_jolie"
 | 
			
		||||
    #
 | 
			
		||||
    # To preserve the case of the characters in a string, use the +preserve_case+ argument.
 | 
			
		||||
    #
 | 
			
		||||
    #   parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
 | 
			
		||||
    #   parameterize("^très|Jolie-- ", preserve_case: true) # => "tres-Jolie"
 | 
			
		||||
    #
 | 
			
		||||
    # It preserves dashes and underscores unless they are used as separators:
 | 
			
		||||
    #
 | 
			
		||||
    #   parameterize("^très|Jolie__ ")                 # => "tres-jolie__"
 | 
			
		||||
    #   parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--"
 | 
			
		||||
    #   parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--"
 | 
			
		||||
    #
 | 
			
		||||
    # If the optional parameter +locale+ is specified,
 | 
			
		||||
    # the word will be parameterized as a word of that language.
 | 
			
		||||
    # By default, this parameter is set to <tt>nil</tt> and it will use
 | 
			
		||||
    # the configured <tt>I18n.locale</tt>.
 | 
			
		||||
    def parameterize(string, separator: "-", preserve_case: false, locale: nil)
 | 
			
		||||
      # Replace accented chars with their ASCII equivalents.
 | 
			
		||||
      parameterized_string = transliterate(string, locale: locale)
 | 
			
		||||
 | 
			
		||||
      # Turn unwanted chars into the separator.
 | 
			
		||||
      parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
 | 
			
		||||
 | 
			
		||||
      unless separator.nil? || separator.empty?
 | 
			
		||||
        if separator == "-"
 | 
			
		||||
          re_duplicate_separator        = /-{2,}/
 | 
			
		||||
          re_leading_trailing_separator = /^-|-$/i
 | 
			
		||||
        else
 | 
			
		||||
          re_sep = Regexp.escape(separator)
 | 
			
		||||
          re_duplicate_separator        = /#{re_sep}{2,}/
 | 
			
		||||
          re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
 | 
			
		||||
        end
 | 
			
		||||
        # No more than one of the separator in a row.
 | 
			
		||||
        parameterized_string.gsub!(re_duplicate_separator, separator)
 | 
			
		||||
        # Remove leading/trailing separator.
 | 
			
		||||
        parameterized_string.gsub!(re_leading_trailing_separator, "")
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      parameterized_string.downcase! unless preserve_case
 | 
			
		||||
      parameterized_string
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,81 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module ActiveSupport
 | 
			
		||||
  # lazy_load_hooks allows Rails to lazily load a lot of components and thus
 | 
			
		||||
  # making the app boot faster. Because of this feature now there is no need to
 | 
			
		||||
  # require <tt>ActiveRecord::Base</tt> at boot time purely to apply
 | 
			
		||||
  # configuration. Instead a hook is registered that applies configuration once
 | 
			
		||||
  # <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is
 | 
			
		||||
  # used as example but this feature can be applied elsewhere too.
 | 
			
		||||
  #
 | 
			
		||||
  # Here is an example where +on_load+ method is called to register a hook.
 | 
			
		||||
  #
 | 
			
		||||
  #   initializer 'active_record.initialize_timezone' do
 | 
			
		||||
  #     ActiveSupport.on_load(:active_record) do
 | 
			
		||||
  #       self.time_zone_aware_attributes = true
 | 
			
		||||
  #       self.default_timezone = :utc
 | 
			
		||||
  #     end
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  # When the entirety of +ActiveRecord::Base+ has been
 | 
			
		||||
  # evaluated then +run_load_hooks+ is invoked. The very last line of
 | 
			
		||||
  # +ActiveRecord::Base+ is:
 | 
			
		||||
  #
 | 
			
		||||
  #   ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
 | 
			
		||||
  module LazyLoadHooks
 | 
			
		||||
    def self.extended(base) # :nodoc:
 | 
			
		||||
      base.class_eval do
 | 
			
		||||
        @load_hooks = Hash.new { |h, k| h[k] = [] }
 | 
			
		||||
        @loaded     = Hash.new { |h, k| h[k] = [] }
 | 
			
		||||
        @run_once   = Hash.new { |h, k| h[k] = [] }
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Declares a block that will be executed when a Rails component is fully
 | 
			
		||||
    # loaded.
 | 
			
		||||
    #
 | 
			
		||||
    # Options:
 | 
			
		||||
    #
 | 
			
		||||
    # * <tt>:yield</tt> - Yields the object that run_load_hooks to +block+.
 | 
			
		||||
    # * <tt>:run_once</tt> - Given +block+ will run only once.
 | 
			
		||||
    def on_load(name, options = {}, &block)
 | 
			
		||||
      @loaded[name].each do |base|
 | 
			
		||||
        execute_hook(name, base, options, block)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      @load_hooks[name] << [block, options]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def run_load_hooks(name, base = Object)
 | 
			
		||||
      @loaded[name] << base
 | 
			
		||||
      @load_hooks[name].each do |hook, options|
 | 
			
		||||
        execute_hook(name, base, options, hook)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
      def with_execution_control(name, block, once)
 | 
			
		||||
        unless @run_once[name].include?(block)
 | 
			
		||||
          @run_once[name] << block if once
 | 
			
		||||
 | 
			
		||||
          yield
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def execute_hook(name, base, options, block)
 | 
			
		||||
        with_execution_control(name, block, options[:run_once]) do
 | 
			
		||||
          if options[:yield]
 | 
			
		||||
            block.call(base)
 | 
			
		||||
          else
 | 
			
		||||
            if base.is_a?(Module)
 | 
			
		||||
              base.class_eval(&block)
 | 
			
		||||
            else
 | 
			
		||||
              base.instance_eval(&block)
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  extend LazyLoadHooks
 | 
			
		||||
end
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module ActiveSupport #:nodoc:
 | 
			
		||||
  module Multibyte
 | 
			
		||||
    autoload :Chars, "active_support/multibyte/chars"
 | 
			
		||||
    autoload :Unicode, "active_support/multibyte/unicode"
 | 
			
		||||
 | 
			
		||||
    # The proxy class returned when calling mb_chars. You can use this accessor
 | 
			
		||||
    # to configure your own proxy class so you can support other encodings. See
 | 
			
		||||
    # the ActiveSupport::Multibyte::Chars implementation for an example how to
 | 
			
		||||
    # do this.
 | 
			
		||||
    #
 | 
			
		||||
    #   ActiveSupport::Multibyte.proxy_class = CharsForUTF32
 | 
			
		||||
    def self.proxy_class=(klass)
 | 
			
		||||
      @proxy_class = klass
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Returns the current proxy class.
 | 
			
		||||
    def self.proxy_class
 | 
			
		||||
      @proxy_class ||= ActiveSupport::Multibyte::Chars
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										202
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/addressable-2.8.5/LICENSE.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/addressable-2.8.5/LICENSE.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,202 @@
 | 
			
		||||
 | 
			
		||||
                              Apache License
 | 
			
		||||
                        Version 2.0, January 2004
 | 
			
		||||
                     http://www.apache.org/licenses/
 | 
			
		||||
 | 
			
		||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 | 
			
		||||
 | 
			
		||||
1. Definitions.
 | 
			
		||||
 | 
			
		||||
   "License" shall mean the terms and conditions for use, reproduction,
 | 
			
		||||
   and distribution as defined by Sections 1 through 9 of this document.
 | 
			
		||||
 | 
			
		||||
   "Licensor" shall mean the copyright owner or entity authorized by
 | 
			
		||||
   the copyright owner that is granting the License.
 | 
			
		||||
 | 
			
		||||
   "Legal Entity" shall mean the union of the acting entity and all
 | 
			
		||||
   other entities that control, are controlled by, or are under common
 | 
			
		||||
   control with that entity. For the purposes of this definition,
 | 
			
		||||
   "control" means (i) the power, direct or indirect, to cause the
 | 
			
		||||
   direction or management of such entity, whether by contract or
 | 
			
		||||
   otherwise, or (ii) ownership of fifty percent (50%) or more of the
 | 
			
		||||
   outstanding shares, or (iii) beneficial ownership of such entity.
 | 
			
		||||
 | 
			
		||||
   "You" (or "Your") shall mean an individual or Legal Entity
 | 
			
		||||
   exercising permissions granted by this License.
 | 
			
		||||
 | 
			
		||||
   "Source" form shall mean the preferred form for making modifications,
 | 
			
		||||
   including but not limited to software source code, documentation
 | 
			
		||||
   source, and configuration files.
 | 
			
		||||
 | 
			
		||||
   "Object" form shall mean any form resulting from mechanical
 | 
			
		||||
   transformation or translation of a Source form, including but
 | 
			
		||||
   not limited to compiled object code, generated documentation,
 | 
			
		||||
   and conversions to other media types.
 | 
			
		||||
 | 
			
		||||
   "Work" shall mean the work of authorship, whether in Source or
 | 
			
		||||
   Object form, made available under the License, as indicated by a
 | 
			
		||||
   copyright notice that is included in or attached to the work
 | 
			
		||||
   (an example is provided in the Appendix below).
 | 
			
		||||
 | 
			
		||||
   "Derivative Works" shall mean any work, whether in Source or Object
 | 
			
		||||
   form, that is based on (or derived from) the Work and for which the
 | 
			
		||||
   editorial revisions, annotations, elaborations, or other modifications
 | 
			
		||||
   represent, as a whole, an original work of authorship. For the purposes
 | 
			
		||||
   of this License, Derivative Works shall not include works that remain
 | 
			
		||||
   separable from, or merely link (or bind by name) to the interfaces of,
 | 
			
		||||
   the Work and Derivative Works thereof.
 | 
			
		||||
 | 
			
		||||
   "Contribution" shall mean any work of authorship, including
 | 
			
		||||
   the original version of the Work and any modifications or additions
 | 
			
		||||
   to that Work or Derivative Works thereof, that is intentionally
 | 
			
		||||
   submitted to Licensor for inclusion in the Work by the copyright owner
 | 
			
		||||
   or by an individual or Legal Entity authorized to submit on behalf of
 | 
			
		||||
   the copyright owner. For the purposes of this definition, "submitted"
 | 
			
		||||
   means any form of electronic, verbal, or written communication sent
 | 
			
		||||
   to the Licensor or its representatives, including but not limited to
 | 
			
		||||
   communication on electronic mailing lists, source code control systems,
 | 
			
		||||
   and issue tracking systems that are managed by, or on behalf of, the
 | 
			
		||||
   Licensor for the purpose of discussing and improving the Work, but
 | 
			
		||||
   excluding communication that is conspicuously marked or otherwise
 | 
			
		||||
   designated in writing by the copyright owner as "Not a Contribution."
 | 
			
		||||
 | 
			
		||||
   "Contributor" shall mean Licensor and any individual or Legal Entity
 | 
			
		||||
   on behalf of whom a Contribution has been received by Licensor and
 | 
			
		||||
   subsequently incorporated within the Work.
 | 
			
		||||
 | 
			
		||||
2. Grant of Copyright License. Subject to the terms and conditions of
 | 
			
		||||
   this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
   copyright license to reproduce, prepare Derivative Works of,
 | 
			
		||||
   publicly display, publicly perform, sublicense, and distribute the
 | 
			
		||||
   Work and such Derivative Works in Source or Object form.
 | 
			
		||||
 | 
			
		||||
3. Grant of Patent License. Subject to the terms and conditions of
 | 
			
		||||
   this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
   (except as stated in this section) patent license to make, have made,
 | 
			
		||||
   use, offer to sell, sell, import, and otherwise transfer the Work,
 | 
			
		||||
   where such license applies only to those patent claims licensable
 | 
			
		||||
   by such Contributor that are necessarily infringed by their
 | 
			
		||||
   Contribution(s) alone or by combination of their Contribution(s)
 | 
			
		||||
   with the Work to which such Contribution(s) was submitted. If You
 | 
			
		||||
   institute patent litigation against any entity (including a
 | 
			
		||||
   cross-claim or counterclaim in a lawsuit) alleging that the Work
 | 
			
		||||
   or a Contribution incorporated within the Work constitutes direct
 | 
			
		||||
   or contributory patent infringement, then any patent licenses
 | 
			
		||||
   granted to You under this License for that Work shall terminate
 | 
			
		||||
   as of the date such litigation is filed.
 | 
			
		||||
 | 
			
		||||
4. Redistribution. You may reproduce and distribute copies of the
 | 
			
		||||
   Work or Derivative Works thereof in any medium, with or without
 | 
			
		||||
   modifications, and in Source or Object form, provided that You
 | 
			
		||||
   meet the following conditions:
 | 
			
		||||
 | 
			
		||||
   (a) You must give any other recipients of the Work or
 | 
			
		||||
       Derivative Works a copy of this License; and
 | 
			
		||||
 | 
			
		||||
   (b) You must cause any modified files to carry prominent notices
 | 
			
		||||
       stating that You changed the files; and
 | 
			
		||||
 | 
			
		||||
   (c) You must retain, in the Source form of any Derivative Works
 | 
			
		||||
       that You distribute, all copyright, patent, trademark, and
 | 
			
		||||
       attribution notices from the Source form of the Work,
 | 
			
		||||
       excluding those notices that do not pertain to any part of
 | 
			
		||||
       the Derivative Works; and
 | 
			
		||||
 | 
			
		||||
   (d) If the Work includes a "NOTICE" text file as part of its
 | 
			
		||||
       distribution, then any Derivative Works that You distribute must
 | 
			
		||||
       include a readable copy of the attribution notices contained
 | 
			
		||||
       within such NOTICE file, excluding those notices that do not
 | 
			
		||||
       pertain to any part of the Derivative Works, in at least one
 | 
			
		||||
       of the following places: within a NOTICE text file distributed
 | 
			
		||||
       as part of the Derivative Works; within the Source form or
 | 
			
		||||
       documentation, if provided along with the Derivative Works; or,
 | 
			
		||||
       within a display generated by the Derivative Works, if and
 | 
			
		||||
       wherever such third-party notices normally appear. The contents
 | 
			
		||||
       of the NOTICE file are for informational purposes only and
 | 
			
		||||
       do not modify the License. You may add Your own attribution
 | 
			
		||||
       notices within Derivative Works that You distribute, alongside
 | 
			
		||||
       or as an addendum to the NOTICE text from the Work, provided
 | 
			
		||||
       that such additional attribution notices cannot be construed
 | 
			
		||||
       as modifying the License.
 | 
			
		||||
 | 
			
		||||
   You may add Your own copyright statement to Your modifications and
 | 
			
		||||
   may provide additional or different license terms and conditions
 | 
			
		||||
   for use, reproduction, or distribution of Your modifications, or
 | 
			
		||||
   for any such Derivative Works as a whole, provided Your use,
 | 
			
		||||
   reproduction, and distribution of the Work otherwise complies with
 | 
			
		||||
   the conditions stated in this License.
 | 
			
		||||
 | 
			
		||||
5. Submission of Contributions. Unless You explicitly state otherwise,
 | 
			
		||||
   any Contribution intentionally submitted for inclusion in the Work
 | 
			
		||||
   by You to the Licensor shall be under the terms and conditions of
 | 
			
		||||
   this License, without any additional terms or conditions.
 | 
			
		||||
   Notwithstanding the above, nothing herein shall supersede or modify
 | 
			
		||||
   the terms of any separate license agreement you may have executed
 | 
			
		||||
   with Licensor regarding such Contributions.
 | 
			
		||||
 | 
			
		||||
6. Trademarks. This License does not grant permission to use the trade
 | 
			
		||||
   names, trademarks, service marks, or product names of the Licensor,
 | 
			
		||||
   except as required for reasonable and customary use in describing the
 | 
			
		||||
   origin of the Work and reproducing the content of the NOTICE file.
 | 
			
		||||
 | 
			
		||||
7. Disclaimer of Warranty. Unless required by applicable law or
 | 
			
		||||
   agreed to in writing, Licensor provides the Work (and each
 | 
			
		||||
   Contributor provides its Contributions) on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
   implied, including, without limitation, any warranties or conditions
 | 
			
		||||
   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 | 
			
		||||
   PARTICULAR PURPOSE. You are solely responsible for determining the
 | 
			
		||||
   appropriateness of using or redistributing the Work and assume any
 | 
			
		||||
   risks associated with Your exercise of permissions under this License.
 | 
			
		||||
 | 
			
		||||
8. Limitation of Liability. In no event and under no legal theory,
 | 
			
		||||
   whether in tort (including negligence), contract, or otherwise,
 | 
			
		||||
   unless required by applicable law (such as deliberate and grossly
 | 
			
		||||
   negligent acts) or agreed to in writing, shall any Contributor be
 | 
			
		||||
   liable to You for damages, including any direct, indirect, special,
 | 
			
		||||
   incidental, or consequential damages of any character arising as a
 | 
			
		||||
   result of this License or out of the use or inability to use the
 | 
			
		||||
   Work (including but not limited to damages for loss of goodwill,
 | 
			
		||||
   work stoppage, computer failure or malfunction, or any and all
 | 
			
		||||
   other commercial damages or losses), even if such Contributor
 | 
			
		||||
   has been advised of the possibility of such damages.
 | 
			
		||||
 | 
			
		||||
9. Accepting Warranty or Additional Liability. While redistributing
 | 
			
		||||
   the Work or Derivative Works thereof, You may choose to offer,
 | 
			
		||||
   and charge a fee for, acceptance of support, warranty, indemnity,
 | 
			
		||||
   or other liability obligations and/or rights consistent with this
 | 
			
		||||
   License. However, in accepting such obligations, You may act only
 | 
			
		||||
   on Your own behalf and on Your sole responsibility, not on behalf
 | 
			
		||||
   of any other Contributor, and only if You agree to indemnify,
 | 
			
		||||
   defend, and hold each Contributor harmless for any liability
 | 
			
		||||
   incurred by, or claims asserted against, such Contributor by reason
 | 
			
		||||
   of your accepting any such warranty or additional liability.
 | 
			
		||||
 | 
			
		||||
END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
APPENDIX: How to apply the Apache License to your work.
 | 
			
		||||
 | 
			
		||||
   To apply the Apache License to your work, attach the following
 | 
			
		||||
   boilerplate notice, with the fields enclosed by brackets "[]"
 | 
			
		||||
   replaced with your own identifying information. (Don't include
 | 
			
		||||
   the brackets!)  The text should be enclosed in the appropriate
 | 
			
		||||
   comment syntax for the file format. We also recommend that a
 | 
			
		||||
   file or class name and description of purpose be included on the
 | 
			
		||||
   same "printed page" as the copyright notice for easier
 | 
			
		||||
   identification within third-party archives.
 | 
			
		||||
 | 
			
		||||
Copyright [yyyy] [name of copyright owner]
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
							
								
								
									
										25
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/bindata-2.4.15/LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/bindata-2.4.15/LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
BSD 2-Clause License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2007-2022, Dion Mendel
 | 
			
		||||
All rights reserved.
 | 
			
		||||
 | 
			
		||||
Redistribution and use in source and binary forms, with or without
 | 
			
		||||
modification, are permitted provided that the following conditions are met:
 | 
			
		||||
 | 
			
		||||
1. Redistributions of source code must retain the above copyright notice, this
 | 
			
		||||
   list of conditions and the following disclaimer.
 | 
			
		||||
 | 
			
		||||
2. Redistributions in binary form must reproduce the above copyright notice,
 | 
			
		||||
   this list of conditions and the following disclaimer in the documentation
 | 
			
		||||
   and/or other materials provided with the distribution.
 | 
			
		||||
 | 
			
		||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 | 
			
		||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
			
		||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 | 
			
		||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 | 
			
		||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
			
		||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 | 
			
		||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 | 
			
		||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 | 
			
		||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | 
			
		||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
							
								
								
									
										21
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/concurrent-ruby-1.2.2/LICENSE.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/concurrent-ruby-1.2.2/LICENSE.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
Copyright (c) Jerry D'Antonio -- released under the MIT license.
 | 
			
		||||
 | 
			
		||||
http://www.opensource.org/licenses/mit-license.php
 | 
			
		||||
 | 
			
		||||
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 OR COPYRIGHT HOLDERS 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.
 | 
			
		||||
							
								
								
									
										22
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.6.3/LICENSE.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/did_you_mean-1.6.3/LICENSE.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
Copyright (c) 2014-2016 Yuki Nishijima
 | 
			
		||||
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
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 OR COPYRIGHT HOLDERS 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.
 | 
			
		||||
							
								
								
									
										20
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/i18n-1.14.1/MIT-LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/i18n-1.14.1/MIT-LICENSE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
Copyright (c) 2008 The Ruby I18n team
 | 
			
		||||
 | 
			
		||||
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 OR COPYRIGHT HOLDERS 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.
 | 
			
		||||
							
								
								
									
										20
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/plist-3.7.0/LICENSE.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/plist-3.7.0/LICENSE.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
Copyright (c) 2006-2010, Ben Bleything and Patrick May
 | 
			
		||||
 | 
			
		||||
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 OR COPYRIGHT HOLDERS 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.
 | 
			
		||||
							
								
								
									
										22
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/public_suffix-5.0.3/LICENSE.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/public_suffix-5.0.3/LICENSE.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
Copyright (c) 2009-2023 Simone Carletti <weppos@weppos.net>
 | 
			
		||||
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
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 OR COPYRIGHT HOLDERS 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.
 | 
			
		||||
@ -1,71 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
# Copyright (C) 2007-2019 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
 | 
			
		||||
#
 | 
			
		||||
# 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 <tt>autoload</tt>ed here,
 | 
			
		||||
# so it should be enough just to <tt>require 'rack'</tt> in your code.
 | 
			
		||||
 | 
			
		||||
require_relative 'rack/version'
 | 
			
		||||
require_relative 'rack/constants'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  autoload :Builder, "rack/builder"
 | 
			
		||||
  autoload :BodyProxy, "rack/body_proxy"
 | 
			
		||||
  autoload :Cascade, "rack/cascade"
 | 
			
		||||
  autoload :Chunked, "rack/chunked"
 | 
			
		||||
  autoload :CommonLogger, "rack/common_logger"
 | 
			
		||||
  autoload :ConditionalGet, "rack/conditional_get"
 | 
			
		||||
  autoload :Config, "rack/config"
 | 
			
		||||
  autoload :ContentLength, "rack/content_length"
 | 
			
		||||
  autoload :ContentType, "rack/content_type"
 | 
			
		||||
  autoload :ETag, "rack/etag"
 | 
			
		||||
  autoload :Events, "rack/events"
 | 
			
		||||
  autoload :File, "rack/file"
 | 
			
		||||
  autoload :Files, "rack/files"
 | 
			
		||||
  autoload :Deflater, "rack/deflater"
 | 
			
		||||
  autoload :Directory, "rack/directory"
 | 
			
		||||
  autoload :ForwardRequest, "rack/recursive"
 | 
			
		||||
  autoload :Handler, "rack/handler"
 | 
			
		||||
  autoload :Head, "rack/head"
 | 
			
		||||
  autoload :Headers, "rack/headers"
 | 
			
		||||
  autoload :Lint, "rack/lint"
 | 
			
		||||
  autoload :Lock, "rack/lock"
 | 
			
		||||
  autoload :Logger, "rack/logger"
 | 
			
		||||
  autoload :MediaType, "rack/media_type"
 | 
			
		||||
  autoload :MethodOverride, "rack/method_override"
 | 
			
		||||
  autoload :Mime, "rack/mime"
 | 
			
		||||
  autoload :NullLogger, "rack/null_logger"
 | 
			
		||||
  autoload :QueryParser, "rack/query_parser"
 | 
			
		||||
  autoload :Recursive, "rack/recursive"
 | 
			
		||||
  autoload :Reloader, "rack/reloader"
 | 
			
		||||
  autoload :RewindableInput, "rack/rewindable_input"
 | 
			
		||||
  autoload :Runtime, "rack/runtime"
 | 
			
		||||
  autoload :Sendfile, "rack/sendfile"
 | 
			
		||||
  autoload :Server, "rack/server"
 | 
			
		||||
  autoload :ShowExceptions, "rack/show_exceptions"
 | 
			
		||||
  autoload :ShowStatus, "rack/show_status"
 | 
			
		||||
  autoload :Static, "rack/static"
 | 
			
		||||
  autoload :TempfileReaper, "rack/tempfile_reaper"
 | 
			
		||||
  autoload :URLMap, "rack/urlmap"
 | 
			
		||||
  autoload :Utils, "rack/utils"
 | 
			
		||||
  autoload :Multipart, "rack/multipart"
 | 
			
		||||
 | 
			
		||||
  autoload :MockRequest, "rack/mock_request"
 | 
			
		||||
  autoload :MockResponse, "rack/mock_response"
 | 
			
		||||
 | 
			
		||||
  autoload :Request, "rack/request"
 | 
			
		||||
  autoload :Response, "rack/response"
 | 
			
		||||
 | 
			
		||||
  module Auth
 | 
			
		||||
    autoload :Basic, "rack/auth/basic"
 | 
			
		||||
    autoload :AbstractRequest, "rack/auth/abstract/request"
 | 
			
		||||
    autoload :AbstractHandler, "rack/auth/abstract/handler"
 | 
			
		||||
    autoload :Digest, "rack/auth/digest"
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,41 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,49 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,59 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require_relative 'abstract/handler'
 | 
			
		||||
require_relative 'abstract/request'
 | 
			
		||||
require 'base64'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  module Auth
 | 
			
		||||
    # Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617.
 | 
			
		||||
    #
 | 
			
		||||
    # Initialize with the Rack application that you want protecting,
 | 
			
		||||
    # and a block that checks if a username and password pair are valid.
 | 
			
		||||
 | 
			
		||||
    class Basic < AbstractHandler
 | 
			
		||||
 | 
			
		||||
      def call(env)
 | 
			
		||||
        auth = Basic::Request.new(env)
 | 
			
		||||
 | 
			
		||||
        return unauthorized unless auth.provided?
 | 
			
		||||
 | 
			
		||||
        return bad_request unless auth.basic?
 | 
			
		||||
 | 
			
		||||
        if valid?(auth)
 | 
			
		||||
          env['REMOTE_USER'] = auth.username
 | 
			
		||||
 | 
			
		||||
          return @app.call(env)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        unauthorized
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      private
 | 
			
		||||
 | 
			
		||||
      def challenge
 | 
			
		||||
        'Basic realm="%s"' % realm
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def valid?(auth)
 | 
			
		||||
        @authenticator.call(*auth.credentials)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      class Request < Auth::AbstractRequest
 | 
			
		||||
        def basic?
 | 
			
		||||
          "basic" == scheme && credentials.length == 2
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def credentials
 | 
			
		||||
          @credentials ||= Base64.decode64(params).split(':', 2)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def username
 | 
			
		||||
          credentials.first
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,256 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require_relative 'abstract/handler'
 | 
			
		||||
require_relative 'abstract/request'
 | 
			
		||||
require 'digest/md5'
 | 
			
		||||
require 'base64'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  warn "Rack::Auth::Digest is deprecated and will be removed in Rack 3.1", uplevel: 1
 | 
			
		||||
 | 
			
		||||
  module Auth
 | 
			
		||||
    module Digest
 | 
			
		||||
      # Rack::Auth::Digest::Nonce is the default nonce generator for the
 | 
			
		||||
      # Rack::Auth::Digest::MD5 authentication handler.
 | 
			
		||||
      #
 | 
			
		||||
      # +private_key+ needs to set to a constant string.
 | 
			
		||||
      #
 | 
			
		||||
      # +time_limit+ can be optionally set to an integer (number of seconds),
 | 
			
		||||
      # to limit the validity of the generated nonces.
 | 
			
		||||
 | 
			
		||||
      class Nonce
 | 
			
		||||
 | 
			
		||||
        class << self
 | 
			
		||||
          attr_accessor :private_key, :time_limit
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def self.parse(string)
 | 
			
		||||
          new(*Base64.decode64(string).split(' ', 2))
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def initialize(timestamp = Time.now, given_digest = nil)
 | 
			
		||||
          @timestamp, @given_digest = timestamp.to_i, given_digest
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def to_s
 | 
			
		||||
          Base64.encode64("#{@timestamp} #{digest}").strip
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def digest
 | 
			
		||||
          ::Digest::MD5.hexdigest("#{@timestamp}:#{self.class.private_key}")
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def valid?
 | 
			
		||||
          digest == @given_digest
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def stale?
 | 
			
		||||
          !self.class.time_limit.nil? && (Time.now.to_i - @timestamp) > self.class.time_limit
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def fresh?
 | 
			
		||||
          !stale?
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      class Params < Hash
 | 
			
		||||
 | 
			
		||||
        def self.parse(str)
 | 
			
		||||
          Params[*split_header_value(str).map do |param|
 | 
			
		||||
            k, v = param.split('=', 2)
 | 
			
		||||
            [k, dequote(v)]
 | 
			
		||||
          end.flatten]
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def self.dequote(str) # From WEBrick::HTTPUtils
 | 
			
		||||
          ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
 | 
			
		||||
          ret.gsub!(/\\(.)/, "\\1")
 | 
			
		||||
          ret
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def self.split_header_value(str)
 | 
			
		||||
          str.scan(/\w+\=(?:"[^\"]+"|[^,]+)/n)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def initialize
 | 
			
		||||
          super()
 | 
			
		||||
 | 
			
		||||
          yield self if block_given?
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def [](k)
 | 
			
		||||
          super k.to_s
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def []=(k, v)
 | 
			
		||||
          super k.to_s, v.to_s
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        UNQUOTED = ['nc', 'stale']
 | 
			
		||||
 | 
			
		||||
        def to_s
 | 
			
		||||
          map do |k, v|
 | 
			
		||||
            "#{k}=#{(UNQUOTED.include?(k) ? v.to_s : quote(v))}"
 | 
			
		||||
          end.join(', ')
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def quote(str) # From WEBrick::HTTPUtils
 | 
			
		||||
          '"' + str.gsub(/[\\\"]/o, "\\\1") + '"'
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      class Request < Auth::AbstractRequest
 | 
			
		||||
        def method
 | 
			
		||||
          @env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] || @env[REQUEST_METHOD]
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def digest?
 | 
			
		||||
          "digest" == scheme
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def correct_uri?
 | 
			
		||||
          request.fullpath == uri
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def nonce
 | 
			
		||||
          @nonce ||= Nonce.parse(params['nonce'])
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def params
 | 
			
		||||
          @params ||= Params.parse(parts.last)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def respond_to?(sym, *)
 | 
			
		||||
          super or params.has_key? sym.to_s
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def method_missing(sym, *args)
 | 
			
		||||
          return super unless params.has_key?(key = sym.to_s)
 | 
			
		||||
          return params[key] if args.size == 0
 | 
			
		||||
          raise ArgumentError, "wrong number of arguments (#{args.size} for 0)"
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of
 | 
			
		||||
      # HTTP Digest Authentication, as per RFC 2617.
 | 
			
		||||
      #
 | 
			
		||||
      # Initialize with the [Rack] application that you want protecting,
 | 
			
		||||
      # and a block that looks up a plaintext password for a given username.
 | 
			
		||||
      #
 | 
			
		||||
      # +opaque+ needs to be set to a constant base64/hexadecimal string.
 | 
			
		||||
      #
 | 
			
		||||
      class MD5 < AbstractHandler
 | 
			
		||||
 | 
			
		||||
        attr_accessor :opaque
 | 
			
		||||
 | 
			
		||||
        attr_writer :passwords_hashed
 | 
			
		||||
 | 
			
		||||
        def initialize(app, realm = nil, opaque = nil, &authenticator)
 | 
			
		||||
          @passwords_hashed = nil
 | 
			
		||||
          if opaque.nil? and realm.respond_to? :values_at
 | 
			
		||||
            realm, opaque, @passwords_hashed = realm.values_at :realm, :opaque, :passwords_hashed
 | 
			
		||||
          end
 | 
			
		||||
          super(app, realm, &authenticator)
 | 
			
		||||
          @opaque = opaque
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def passwords_hashed?
 | 
			
		||||
          !!@passwords_hashed
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def call(env)
 | 
			
		||||
          auth = Request.new(env)
 | 
			
		||||
 | 
			
		||||
          unless auth.provided?
 | 
			
		||||
            return unauthorized
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth)
 | 
			
		||||
            return bad_request
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          if valid?(auth)
 | 
			
		||||
            if auth.nonce.stale?
 | 
			
		||||
              return unauthorized(challenge(stale: true))
 | 
			
		||||
            else
 | 
			
		||||
              env['REMOTE_USER'] = auth.username
 | 
			
		||||
 | 
			
		||||
              return @app.call(env)
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          unauthorized
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        private
 | 
			
		||||
 | 
			
		||||
        QOP = 'auth'
 | 
			
		||||
 | 
			
		||||
        def params(hash = {})
 | 
			
		||||
          Params.new do |params|
 | 
			
		||||
            params['realm'] = realm
 | 
			
		||||
            params['nonce'] = Nonce.new.to_s
 | 
			
		||||
            params['opaque'] = H(opaque)
 | 
			
		||||
            params['qop'] = QOP
 | 
			
		||||
 | 
			
		||||
            hash.each { |k, v| params[k] = v }
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def challenge(hash = {})
 | 
			
		||||
          "Digest #{params(hash)}"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def valid?(auth)
 | 
			
		||||
          valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def valid_qop?(auth)
 | 
			
		||||
          QOP == auth.qop
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def valid_opaque?(auth)
 | 
			
		||||
          H(opaque) == auth.opaque
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def valid_nonce?(auth)
 | 
			
		||||
          auth.nonce.valid?
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def valid_digest?(auth)
 | 
			
		||||
          pw = @authenticator.call(auth.username)
 | 
			
		||||
          pw && Rack::Utils.secure_compare(digest(auth, pw), auth.response)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def md5(data)
 | 
			
		||||
          ::Digest::MD5.hexdigest(data)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        alias :H :md5
 | 
			
		||||
 | 
			
		||||
        def KD(secret, data)
 | 
			
		||||
          H "#{secret}:#{data}"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def A1(auth, password)
 | 
			
		||||
          "#{auth.username}:#{auth.realm}:#{password}"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def A2(auth)
 | 
			
		||||
          "#{auth.method}:#{auth.uri}"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def digest(auth, password)
 | 
			
		||||
          password_hash = passwords_hashed? ? password : H(A1(auth, password))
 | 
			
		||||
 | 
			
		||||
          KD password_hash, "#{auth.nonce}:#{auth.nc}:#{auth.cnonce}:#{QOP}:#{H A2(auth)}"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
require_relative '../digest'
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
require_relative '../digest'
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
require_relative '../digest'
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
require_relative '../digest'
 | 
			
		||||
@ -1,47 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Proxy for response bodies allowing calling a block when
 | 
			
		||||
  # the response body is closed (after the response has been fully
 | 
			
		||||
  # sent to the client).
 | 
			
		||||
  class BodyProxy
 | 
			
		||||
    # Set the response body to wrap, and the block to call when the
 | 
			
		||||
    # response has been fully sent.
 | 
			
		||||
    def initialize(body, &block)
 | 
			
		||||
      @body = body
 | 
			
		||||
      @block = block
 | 
			
		||||
      @closed = false
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Return whether the wrapped body responds to the method.
 | 
			
		||||
    def respond_to_missing?(method_name, include_all = false)
 | 
			
		||||
      super or @body.respond_to?(method_name, include_all)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # If not already closed, close the wrapped body and
 | 
			
		||||
    # then call the block the proxy was initialized with.
 | 
			
		||||
    def close
 | 
			
		||||
      return if @closed
 | 
			
		||||
      @closed = true
 | 
			
		||||
      begin
 | 
			
		||||
        @body.close if @body.respond_to?(:close)
 | 
			
		||||
      ensure
 | 
			
		||||
        @block.call
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Whether the proxy is closed.  The proxy starts as not closed,
 | 
			
		||||
    # and becomes closed on the first call to close.
 | 
			
		||||
    def closed?
 | 
			
		||||
      @closed
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Delegate missing methods to the wrapped body.
 | 
			
		||||
    def method_missing(method_name, *args, &block)
 | 
			
		||||
      @body.__send__(method_name, *args, &block)
 | 
			
		||||
    end
 | 
			
		||||
    # :nocov:
 | 
			
		||||
    ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
 | 
			
		||||
    # :nocov:
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,277 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require_relative 'urlmap'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Rack::Builder provides a domain-specific language (DSL) to construct Rack
 | 
			
		||||
  # applications. It is primarily used to parse +config.ru+ files which
 | 
			
		||||
  # instantiate several middleware and a final application which are hosted
 | 
			
		||||
  # by a Rack-compatible web server.
 | 
			
		||||
  #
 | 
			
		||||
  # Example:
 | 
			
		||||
  #
 | 
			
		||||
  #   app = Rack::Builder.new do
 | 
			
		||||
  #     use Rack::CommonLogger
 | 
			
		||||
  #     map "/ok" do
 | 
			
		||||
  #       run lambda { |env| [200, {'content-type' => 'text/plain'}, ['OK']] }
 | 
			
		||||
  #     end
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   run app
 | 
			
		||||
  #
 | 
			
		||||
  # Or
 | 
			
		||||
  #
 | 
			
		||||
  #   app = Rack::Builder.app do
 | 
			
		||||
  #     use Rack::CommonLogger
 | 
			
		||||
  #     run lambda { |env| [200, {'content-type' => 'text/plain'}, ['OK']] }
 | 
			
		||||
  #   end
 | 
			
		||||
  #
 | 
			
		||||
  #   run app
 | 
			
		||||
  #
 | 
			
		||||
  # +use+ adds middleware to the stack, +run+ dispatches to an application.
 | 
			
		||||
  # You can use +map+ to construct a Rack::URLMap in a convenient way.
 | 
			
		||||
  class Builder
 | 
			
		||||
 | 
			
		||||
    # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
 | 
			
		||||
    UTF_8_BOM = '\xef\xbb\xbf'
 | 
			
		||||
 | 
			
		||||
    # Parse the given config file to get a Rack application.
 | 
			
		||||
    #
 | 
			
		||||
    # If the config file ends in +.ru+, it is treated as a
 | 
			
		||||
    # rackup file and the contents will be treated as if
 | 
			
		||||
    # specified inside a Rack::Builder block.
 | 
			
		||||
    #
 | 
			
		||||
    # If the config file does not end in +.ru+, it is
 | 
			
		||||
    # required and Rack will use the basename of the file
 | 
			
		||||
    # to guess which constant will be the Rack application to run.
 | 
			
		||||
    #
 | 
			
		||||
    # Examples:
 | 
			
		||||
    #
 | 
			
		||||
    #   Rack::Builder.parse_file('config.ru')
 | 
			
		||||
    #   # Rack application built using Rack::Builder.new
 | 
			
		||||
    #
 | 
			
		||||
    #   Rack::Builder.parse_file('app.rb')
 | 
			
		||||
    #   # requires app.rb, which can be anywhere in Ruby's
 | 
			
		||||
    #   # load path. After requiring, assumes App constant
 | 
			
		||||
    #   # contains Rack application
 | 
			
		||||
    #
 | 
			
		||||
    #   Rack::Builder.parse_file('./my_app.rb')
 | 
			
		||||
    #   # requires ./my_app.rb, which should be in the
 | 
			
		||||
    #   # process's current directory.  After requiring,
 | 
			
		||||
    #   # assumes MyApp constant contains Rack application
 | 
			
		||||
    def self.parse_file(path)
 | 
			
		||||
      if path.end_with?('.ru')
 | 
			
		||||
        return self.load_file(path)
 | 
			
		||||
      else
 | 
			
		||||
        require path
 | 
			
		||||
        return Object.const_get(::File.basename(path, '.rb').split('_').map(&:capitalize).join(''))
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Load the given file as a rackup file, treating the
 | 
			
		||||
    # contents as if specified inside a Rack::Builder block.
 | 
			
		||||
    #
 | 
			
		||||
    # Ignores content in the file after +__END__+, so that
 | 
			
		||||
    # use of +__END__+ will not result in a syntax error.
 | 
			
		||||
    #
 | 
			
		||||
    # Example config.ru file:
 | 
			
		||||
    #
 | 
			
		||||
    #   $ cat config.ru
 | 
			
		||||
    #
 | 
			
		||||
    #   use Rack::ContentLength
 | 
			
		||||
    #   require './app.rb'
 | 
			
		||||
    #   run App
 | 
			
		||||
    def self.load_file(path)
 | 
			
		||||
      config = ::File.read(path)
 | 
			
		||||
      config.slice!(/\A#{UTF_8_BOM}/) if config.encoding == Encoding::UTF_8
 | 
			
		||||
 | 
			
		||||
      if config[/^#\\(.*)/]
 | 
			
		||||
        fail "Parsing options from the first comment line is no longer supported: #{path}"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      config.sub!(/^__END__\n.*\Z/m, '')
 | 
			
		||||
 | 
			
		||||
      return new_from_string(config, path)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Evaluate the given +builder_script+ string in the context of
 | 
			
		||||
    # a Rack::Builder block, returning a Rack application.
 | 
			
		||||
    def self.new_from_string(builder_script, file = "(rackup)")
 | 
			
		||||
      # We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance.
 | 
			
		||||
      # We cannot use instance_eval(String) as that would resolve constants differently.
 | 
			
		||||
      binding, builder = TOPLEVEL_BINDING.eval('Rack::Builder.new.instance_eval { [binding, self] }')
 | 
			
		||||
      eval builder_script, binding, file
 | 
			
		||||
 | 
			
		||||
      return builder.to_app
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Initialize a new Rack::Builder instance.  +default_app+ specifies the
 | 
			
		||||
    # default application if +run+ is not called later.  If a block
 | 
			
		||||
    # is given, it is evaluated in the context of the instance.
 | 
			
		||||
    def initialize(default_app = nil, &block)
 | 
			
		||||
      @use = []
 | 
			
		||||
      @map = nil
 | 
			
		||||
      @run = default_app
 | 
			
		||||
      @warmup = nil
 | 
			
		||||
      @freeze_app = false
 | 
			
		||||
 | 
			
		||||
      instance_eval(&block) if block_given?
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Create a new Rack::Builder instance and return the Rack application
 | 
			
		||||
    # generated from it.
 | 
			
		||||
    def self.app(default_app = nil, &block)
 | 
			
		||||
      self.new(default_app, &block).to_app
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Specifies middleware to use in a stack.
 | 
			
		||||
    #
 | 
			
		||||
    #   class Middleware
 | 
			
		||||
    #     def initialize(app)
 | 
			
		||||
    #       @app = app
 | 
			
		||||
    #     end
 | 
			
		||||
    #
 | 
			
		||||
    #     def call(env)
 | 
			
		||||
    #       env["rack.some_header"] = "setting an example"
 | 
			
		||||
    #       @app.call(env)
 | 
			
		||||
    #     end
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    #   use Middleware
 | 
			
		||||
    #   run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] }
 | 
			
		||||
    #
 | 
			
		||||
    # All requests through to this application will first be processed by the middleware class.
 | 
			
		||||
    # The +call+ method in this example sets an additional environment key which then can be
 | 
			
		||||
    # referenced in the application if required.
 | 
			
		||||
    def use(middleware, *args, &block)
 | 
			
		||||
      if @map
 | 
			
		||||
        mapping, @map = @map, nil
 | 
			
		||||
        @use << proc { |app| generate_map(app, mapping) }
 | 
			
		||||
      end
 | 
			
		||||
      @use << proc { |app| middleware.new(app, *args, &block) }
 | 
			
		||||
    end
 | 
			
		||||
    # :nocov:
 | 
			
		||||
    ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
 | 
			
		||||
    # :nocov:
 | 
			
		||||
 | 
			
		||||
    # Takes a block or argument that is an object that responds to #call and
 | 
			
		||||
    # returns a Rack response.
 | 
			
		||||
    #
 | 
			
		||||
    # You can use a block:
 | 
			
		||||
    #
 | 
			
		||||
    #   run do |env|
 | 
			
		||||
    #     [200, { "content-type" => "text/plain" }, ["Hello World!"]]
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    # You can also provide a lambda:
 | 
			
		||||
    #
 | 
			
		||||
    #   run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] }
 | 
			
		||||
    #
 | 
			
		||||
    # You can also provide a class instance:
 | 
			
		||||
    #
 | 
			
		||||
    #   class Heartbeat
 | 
			
		||||
    #     def call(env)
 | 
			
		||||
    #      [200, { "content-type" => "text/plain" }, ["OK"]]
 | 
			
		||||
    #     end
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    #   run Heartbeat.new
 | 
			
		||||
    #
 | 
			
		||||
    def run(app = nil, &block)
 | 
			
		||||
      raise ArgumentError, "Both app and block given!" if app && block_given?
 | 
			
		||||
 | 
			
		||||
      @run = app || block
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Takes a lambda or block that is used to warm-up the application. This block is called
 | 
			
		||||
    # before the Rack application is returned by to_app.
 | 
			
		||||
    #
 | 
			
		||||
    #   warmup do |app|
 | 
			
		||||
    #     client = Rack::MockRequest.new(app)
 | 
			
		||||
    #     client.get('/')
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    #   use SomeMiddleware
 | 
			
		||||
    #   run MyApp
 | 
			
		||||
    def warmup(prc = nil, &block)
 | 
			
		||||
      @warmup = prc || block
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Creates a route within the application.  Routes under the mapped path will be sent to
 | 
			
		||||
    # the Rack application specified by run inside the block.  Other requests will be sent to the
 | 
			
		||||
    # default application specified by run outside the block.
 | 
			
		||||
    #
 | 
			
		||||
    #   class App
 | 
			
		||||
    #     def call(env)
 | 
			
		||||
    #       [200, {'content-type' => 'text/plain'}, ["Hello World"]]
 | 
			
		||||
    #     end
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    #   class Heartbeat
 | 
			
		||||
    #     def call(env)
 | 
			
		||||
    #       [200, { "content-type" => "text/plain" }, ["OK"]]
 | 
			
		||||
    #     end
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    #   app = Rack::Builder.app do
 | 
			
		||||
    #     map '/heartbeat' do
 | 
			
		||||
    #       run Heartbeat.new
 | 
			
		||||
    #     end
 | 
			
		||||
    #     run App.new
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    #   run app
 | 
			
		||||
    #
 | 
			
		||||
    # The +use+ method can also be used inside the block to specify middleware to run under a specific path:
 | 
			
		||||
    #
 | 
			
		||||
    #   app = Rack::Builder.app do
 | 
			
		||||
    #     map '/heartbeat' do
 | 
			
		||||
    #       use Middleware
 | 
			
		||||
    #       run Heartbeat.new
 | 
			
		||||
    #     end
 | 
			
		||||
    #     run App.new
 | 
			
		||||
    #   end
 | 
			
		||||
    #
 | 
			
		||||
    # This example includes a piece of middleware which will run before +/heartbeat+ requests hit +Heartbeat+.
 | 
			
		||||
    #
 | 
			
		||||
    # Note that providing a +path+ of +/+ will ignore any default application given in a +run+ statement
 | 
			
		||||
    # outside the block.
 | 
			
		||||
    def map(path, &block)
 | 
			
		||||
      @map ||= {}
 | 
			
		||||
      @map[path] = block
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Freeze the app (set using run) and all middleware instances when building the application
 | 
			
		||||
    # in to_app.
 | 
			
		||||
    def freeze_app
 | 
			
		||||
      @freeze_app = true
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Return the Rack application generated by this instance.
 | 
			
		||||
    def to_app
 | 
			
		||||
      app = @map ? generate_map(@run, @map) : @run
 | 
			
		||||
      fail "missing run or map statement" unless app
 | 
			
		||||
      app.freeze if @freeze_app
 | 
			
		||||
      app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } }
 | 
			
		||||
      @warmup.call(app) if @warmup
 | 
			
		||||
      app
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Call the Rack application generated by this builder instance. Note that
 | 
			
		||||
    # this rebuilds the Rack application and runs the warmup code (if any)
 | 
			
		||||
    # every time it is called, so it should not be used if performance is important.
 | 
			
		||||
    def call(env)
 | 
			
		||||
      to_app.call(env)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    # Generate a URLMap instance by generating new Rack applications for each
 | 
			
		||||
    # map block in this instance.
 | 
			
		||||
    def generate_map(default_app, mapping)
 | 
			
		||||
      mapped = default_app ? { '/' => default_app } : {}
 | 
			
		||||
      mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
 | 
			
		||||
      URLMap.new(mapped)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,70 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require_relative 'constants'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Rack::Cascade tries a request on several apps, and returns the
 | 
			
		||||
  # first response that is not 404 or 405 (or in a list of configured
 | 
			
		||||
  # status codes).  If all applications tried return one of the configured
 | 
			
		||||
  # status codes, return the last response.
 | 
			
		||||
 | 
			
		||||
  class Cascade
 | 
			
		||||
    # deprecated, no longer used
 | 
			
		||||
    NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
 | 
			
		||||
 | 
			
		||||
    # An array of applications to try in order.
 | 
			
		||||
    attr_reader :apps
 | 
			
		||||
 | 
			
		||||
    # Set the apps to send requests to, and what statuses result in
 | 
			
		||||
    # cascading.  Arguments:
 | 
			
		||||
    #
 | 
			
		||||
    # apps: An enumerable of rack applications.
 | 
			
		||||
    # cascade_for: The statuses to use cascading for.  If a response is received
 | 
			
		||||
    #              from an app, the next app is tried.
 | 
			
		||||
    def initialize(apps, cascade_for = [404, 405])
 | 
			
		||||
      @apps = []
 | 
			
		||||
      apps.each { |app| add app }
 | 
			
		||||
 | 
			
		||||
      @cascade_for = {}
 | 
			
		||||
      [*cascade_for].each { |status| @cascade_for[status] = true }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Call each app in order.  If the responses uses a status that requires
 | 
			
		||||
    # cascading, try the next app.  If all responses require cascading,
 | 
			
		||||
    # return the response from the last app.
 | 
			
		||||
    def call(env)
 | 
			
		||||
      return [404, { CONTENT_TYPE => "text/plain" }, []] if @apps.empty?
 | 
			
		||||
      result = nil
 | 
			
		||||
      last_body = nil
 | 
			
		||||
 | 
			
		||||
      @apps.each do |app|
 | 
			
		||||
        # The SPEC says that the body must be closed after it has been iterated
 | 
			
		||||
        # by the server, or if it is replaced by a middleware action. Cascade
 | 
			
		||||
        # replaces the body each time a cascade happens. It is assumed that nil
 | 
			
		||||
        # does not respond to close, otherwise the previous application body
 | 
			
		||||
        # will be closed. The final application body will not be closed, as it
 | 
			
		||||
        # will be passed to the server as a result.
 | 
			
		||||
        last_body.close if last_body.respond_to? :close
 | 
			
		||||
 | 
			
		||||
        result = app.call(env)
 | 
			
		||||
        return result unless @cascade_for.include?(result[0].to_i)
 | 
			
		||||
        last_body = result[2]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      result
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Append an app to the list of apps to cascade.  This app will
 | 
			
		||||
    # be tried last.
 | 
			
		||||
    def add(app)
 | 
			
		||||
      @apps << app
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Whether the given app is one of the apps to cascade to.
 | 
			
		||||
    def include?(app)
 | 
			
		||||
      @apps.include?(app)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    alias_method :<<, :add
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,120 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require_relative 'constants'
 | 
			
		||||
require_relative 'utils'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  warn "Rack::Chunked is deprecated and will be removed in Rack 3.1", uplevel: 1
 | 
			
		||||
 | 
			
		||||
  # Middleware that applies chunked transfer encoding to response bodies
 | 
			
		||||
  # when the response does not include a content-length header.
 | 
			
		||||
  #
 | 
			
		||||
  # This supports the trailer response header to allow the use of trailing
 | 
			
		||||
  # headers in the chunked encoding.  However, using this requires you manually
 | 
			
		||||
  # specify a response body that supports a +trailers+ method.  Example:
 | 
			
		||||
  #
 | 
			
		||||
  #   [200, { 'trailer' => 'expires'}, ["Hello", "World"]]
 | 
			
		||||
  #   # error raised
 | 
			
		||||
  #
 | 
			
		||||
  #   body = ["Hello", "World"]
 | 
			
		||||
  #   def body.trailers
 | 
			
		||||
  #     { 'expires' => Time.now.to_s }
 | 
			
		||||
  #   end
 | 
			
		||||
  #   [200, { 'trailer' => 'expires'}, body]
 | 
			
		||||
  #   # No exception raised
 | 
			
		||||
  class Chunked
 | 
			
		||||
    include Rack::Utils
 | 
			
		||||
 | 
			
		||||
    # A body wrapper that emits chunked responses.
 | 
			
		||||
    class Body
 | 
			
		||||
      TERM = "\r\n"
 | 
			
		||||
      TAIL = "0#{TERM}"
 | 
			
		||||
 | 
			
		||||
      # Store the response body to be chunked.
 | 
			
		||||
      def initialize(body)
 | 
			
		||||
        @body = body
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # For each element yielded by the response body, yield
 | 
			
		||||
      # the element in chunked encoding.
 | 
			
		||||
      def each(&block)
 | 
			
		||||
        term = TERM
 | 
			
		||||
        @body.each do |chunk|
 | 
			
		||||
          size = chunk.bytesize
 | 
			
		||||
          next if size == 0
 | 
			
		||||
 | 
			
		||||
          yield [size.to_s(16), term, chunk.b, term].join
 | 
			
		||||
        end
 | 
			
		||||
        yield TAIL
 | 
			
		||||
        yield_trailers(&block)
 | 
			
		||||
        yield term
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Close the response body if the response body supports it.
 | 
			
		||||
      def close
 | 
			
		||||
        @body.close if @body.respond_to?(:close)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      private
 | 
			
		||||
 | 
			
		||||
      # Do nothing as this class does not support trailer headers.
 | 
			
		||||
      def yield_trailers
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # A body wrapper that emits chunked responses and also supports
 | 
			
		||||
    # sending Trailer headers.  Note that the response body provided to
 | 
			
		||||
    # initialize must have a +trailers+ method that returns a hash
 | 
			
		||||
    # of trailer headers, and the rack response itself should have a
 | 
			
		||||
    # Trailer header listing the headers that the +trailers+ method
 | 
			
		||||
    # will return.
 | 
			
		||||
    class TrailerBody < Body
 | 
			
		||||
      private
 | 
			
		||||
 | 
			
		||||
      # Yield strings for each trailer header.
 | 
			
		||||
      def yield_trailers
 | 
			
		||||
        @body.trailers.each_pair do |k, v|
 | 
			
		||||
          yield "#{k}: #{v}\r\n"
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def initialize(app)
 | 
			
		||||
      @app = app
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Whether the HTTP version supports chunked encoding (HTTP 1.1 does).
 | 
			
		||||
    def chunkable_version?(ver)
 | 
			
		||||
      case ver
 | 
			
		||||
      # pre-HTTP/1.0 (informally "HTTP/0.9") HTTP requests did not have
 | 
			
		||||
      # a version (nor response headers)
 | 
			
		||||
      when 'HTTP/1.0', nil, 'HTTP/0.9'
 | 
			
		||||
        false
 | 
			
		||||
      else
 | 
			
		||||
        true
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # If the rack app returns a response that should have a body,
 | 
			
		||||
    # but does not have content-length or transfer-encoding headers,
 | 
			
		||||
    # modify the response to use chunked transfer-encoding.
 | 
			
		||||
    def call(env)
 | 
			
		||||
      status, headers, body = response = @app.call(env)
 | 
			
		||||
 | 
			
		||||
      if chunkable_version?(env[SERVER_PROTOCOL]) &&
 | 
			
		||||
         !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
 | 
			
		||||
         !headers[CONTENT_LENGTH] &&
 | 
			
		||||
         !headers[TRANSFER_ENCODING]
 | 
			
		||||
 | 
			
		||||
        headers[TRANSFER_ENCODING] = 'chunked'
 | 
			
		||||
        if headers['trailer']
 | 
			
		||||
          response[2] = TrailerBody.new(body)
 | 
			
		||||
        else
 | 
			
		||||
          response[2] = Body.new(body)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      response
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,88 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require_relative 'constants'
 | 
			
		||||
require_relative 'utils'
 | 
			
		||||
require_relative 'body_proxy'
 | 
			
		||||
require_relative 'request'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Rack::CommonLogger forwards every request to the given +app+, and
 | 
			
		||||
  # logs a line in the
 | 
			
		||||
  # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
 | 
			
		||||
  # to the configured logger.
 | 
			
		||||
  class CommonLogger
 | 
			
		||||
    # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
 | 
			
		||||
    #
 | 
			
		||||
    #   lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
 | 
			
		||||
    #
 | 
			
		||||
    #   %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
 | 
			
		||||
    #
 | 
			
		||||
    # The actual format is slightly different than the above due to the
 | 
			
		||||
    # separation of SCRIPT_NAME and PATH_INFO, and because the elapsed
 | 
			
		||||
    # time in seconds is included at the end.
 | 
			
		||||
    FORMAT = %{%s - %s [%s] "%s %s%s%s %s" %d %s %0.4f\n}
 | 
			
		||||
 | 
			
		||||
    # +logger+ can be any object that supports the +write+ or +<<+ methods,
 | 
			
		||||
    # which includes the standard library Logger.  These methods are called
 | 
			
		||||
    # with a single string argument, the log message.
 | 
			
		||||
    # If +logger+ is nil, CommonLogger will fall back <tt>env['rack.errors']</tt>.
 | 
			
		||||
    def initialize(app, logger = nil)
 | 
			
		||||
      @app = app
 | 
			
		||||
      @logger = logger
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Log all requests in common_log format after a response has been
 | 
			
		||||
    # returned.  Note that if the app raises an exception, the request
 | 
			
		||||
    # will not be logged, so if exception handling middleware are used,
 | 
			
		||||
    # they should be loaded after this middleware.  Additionally, because
 | 
			
		||||
    # the logging happens after the request body has been fully sent, any
 | 
			
		||||
    # exceptions raised during the sending of the response body will
 | 
			
		||||
    # cause the request not to be logged.
 | 
			
		||||
    def call(env)
 | 
			
		||||
      began_at = Utils.clock_time
 | 
			
		||||
      status, headers, body = response = @app.call(env)
 | 
			
		||||
 | 
			
		||||
      response[2] = BodyProxy.new(body) { log(env, status, headers, began_at) }
 | 
			
		||||
      response
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    # Log the request to the configured logger.
 | 
			
		||||
    def log(env, status, response_headers, began_at)
 | 
			
		||||
      request = Rack::Request.new(env)
 | 
			
		||||
      length = extract_content_length(response_headers)
 | 
			
		||||
 | 
			
		||||
      msg = sprintf(FORMAT,
 | 
			
		||||
        request.ip || "-",
 | 
			
		||||
        request.get_header("REMOTE_USER") || "-",
 | 
			
		||||
        Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
 | 
			
		||||
        request.request_method,
 | 
			
		||||
        request.script_name,
 | 
			
		||||
        request.path_info,
 | 
			
		||||
        request.query_string.empty? ? "" : "?#{request.query_string}",
 | 
			
		||||
        request.get_header(SERVER_PROTOCOL),
 | 
			
		||||
        status.to_s[0..3],
 | 
			
		||||
        length,
 | 
			
		||||
        Utils.clock_time - began_at)
 | 
			
		||||
 | 
			
		||||
      msg.gsub!(/[^[:print:]\n]/) { |c| sprintf("\\x%x", c.ord) }
 | 
			
		||||
 | 
			
		||||
      logger = @logger || request.get_header(RACK_ERRORS)
 | 
			
		||||
      # Standard library logger doesn't support write but it supports << which actually
 | 
			
		||||
      # calls to write on the log device without formatting
 | 
			
		||||
      if logger.respond_to?(:write)
 | 
			
		||||
        logger.write(msg)
 | 
			
		||||
      else
 | 
			
		||||
        logger << msg
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Attempt to determine the content length for the response to
 | 
			
		||||
    # include it in the logged data.
 | 
			
		||||
    def extract_content_length(headers)
 | 
			
		||||
      value = headers[CONTENT_LENGTH]
 | 
			
		||||
      !value || value.to_s == '0' ? '-' : value
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,86 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,22 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,64 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Request env keys
 | 
			
		||||
  HTTP_HOST         = 'HTTP_HOST'
 | 
			
		||||
  HTTP_PORT         = 'HTTP_PORT'
 | 
			
		||||
  HTTPS             = 'HTTPS'
 | 
			
		||||
  PATH_INFO         = 'PATH_INFO'
 | 
			
		||||
  REQUEST_METHOD    = 'REQUEST_METHOD'
 | 
			
		||||
  REQUEST_PATH      = 'REQUEST_PATH'
 | 
			
		||||
  SCRIPT_NAME       = 'SCRIPT_NAME'
 | 
			
		||||
  QUERY_STRING      = 'QUERY_STRING'
 | 
			
		||||
  SERVER_PROTOCOL   = 'SERVER_PROTOCOL'
 | 
			
		||||
  SERVER_NAME       = 'SERVER_NAME'
 | 
			
		||||
  SERVER_PORT       = 'SERVER_PORT'
 | 
			
		||||
  HTTP_COOKIE       = 'HTTP_COOKIE'
 | 
			
		||||
 | 
			
		||||
  # Response Header Keys
 | 
			
		||||
  CACHE_CONTROL     = 'cache-control'
 | 
			
		||||
  CONTENT_LENGTH    = 'content-length'
 | 
			
		||||
  CONTENT_TYPE      = 'content-type'
 | 
			
		||||
  ETAG              = 'etag'
 | 
			
		||||
  EXPIRES           = 'expires'
 | 
			
		||||
  SET_COOKIE        = 'set-cookie'
 | 
			
		||||
  TRANSFER_ENCODING = 'transfer-encoding'
 | 
			
		||||
 | 
			
		||||
  # HTTP method verbs
 | 
			
		||||
  GET     = 'GET'
 | 
			
		||||
  POST    = 'POST'
 | 
			
		||||
  PUT     = 'PUT'
 | 
			
		||||
  PATCH   = 'PATCH'
 | 
			
		||||
  DELETE  = 'DELETE'
 | 
			
		||||
  HEAD    = 'HEAD'
 | 
			
		||||
  OPTIONS = 'OPTIONS'
 | 
			
		||||
  LINK    = 'LINK'
 | 
			
		||||
  UNLINK  = 'UNLINK'
 | 
			
		||||
  TRACE   = 'TRACE'
 | 
			
		||||
 | 
			
		||||
  # Rack environment variables
 | 
			
		||||
  RACK_VERSION                        = 'rack.version'
 | 
			
		||||
  RACK_TEMPFILES                      = 'rack.tempfiles'
 | 
			
		||||
  RACK_ERRORS                         = 'rack.errors'
 | 
			
		||||
  RACK_LOGGER                         = 'rack.logger'
 | 
			
		||||
  RACK_INPUT                          = 'rack.input'
 | 
			
		||||
  RACK_SESSION                        = 'rack.session'
 | 
			
		||||
  RACK_SESSION_OPTIONS                = 'rack.session.options'
 | 
			
		||||
  RACK_SHOWSTATUS_DETAIL              = 'rack.showstatus.detail'
 | 
			
		||||
  RACK_URL_SCHEME                     = 'rack.url_scheme'
 | 
			
		||||
  RACK_HIJACK                         = 'rack.hijack'
 | 
			
		||||
  RACK_IS_HIJACK                      = 'rack.hijack?'
 | 
			
		||||
  RACK_RECURSIVE_INCLUDE              = 'rack.recursive.include'
 | 
			
		||||
  RACK_MULTIPART_BUFFER_SIZE          = 'rack.multipart.buffer_size'
 | 
			
		||||
  RACK_MULTIPART_TEMPFILE_FACTORY     = 'rack.multipart.tempfile_factory'
 | 
			
		||||
  RACK_RESPONSE_FINISHED              = 'rack.response_finished'
 | 
			
		||||
  RACK_REQUEST_FORM_INPUT             = 'rack.request.form_input'
 | 
			
		||||
  RACK_REQUEST_FORM_HASH              = 'rack.request.form_hash'
 | 
			
		||||
  RACK_REQUEST_FORM_VARS              = 'rack.request.form_vars'
 | 
			
		||||
  RACK_REQUEST_FORM_ERROR             = 'rack.request.form_error'
 | 
			
		||||
  RACK_REQUEST_COOKIE_HASH            = 'rack.request.cookie_hash'
 | 
			
		||||
  RACK_REQUEST_COOKIE_STRING          = 'rack.request.cookie_string'
 | 
			
		||||
  RACK_REQUEST_QUERY_HASH             = 'rack.request.query_hash'
 | 
			
		||||
  RACK_REQUEST_QUERY_STRING           = 'rack.request.query_string'
 | 
			
		||||
  RACK_METHODOVERRIDE_ORIGINAL_METHOD = 'rack.methodoverride.original_method'
 | 
			
		||||
end
 | 
			
		||||
@ -1,34 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,33 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,158 +0,0 @@
 | 
			
		||||
# 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 <tt>use Rack::Deflater, :if => lambda { |*, body| sum=0; body.each { |i| sum += i.length }; sum > 512 }</tt>).
 | 
			
		||||
    #        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
 | 
			
		||||
@ -1,205 +0,0 @@
 | 
			
		||||
# 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 = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>\n"
 | 
			
		||||
    DIR_PAGE_HEADER = <<-PAGE
 | 
			
		||||
<html><head>
 | 
			
		||||
  <title>%s</title>
 | 
			
		||||
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
 | 
			
		||||
  <style type='text/css'>
 | 
			
		||||
table { width:100%%; }
 | 
			
		||||
.name { text-align:left; }
 | 
			
		||||
.size, .mtime { text-align:right; }
 | 
			
		||||
.type { width:11em; }
 | 
			
		||||
.mtime { width:15em; }
 | 
			
		||||
  </style>
 | 
			
		||||
</head><body>
 | 
			
		||||
<h1>%s</h1>
 | 
			
		||||
<hr />
 | 
			
		||||
<table>
 | 
			
		||||
  <tr>
 | 
			
		||||
    <th class='name'>Name</th>
 | 
			
		||||
    <th class='size'>Size</th>
 | 
			
		||||
    <th class='type'>Type</th>
 | 
			
		||||
    <th class='mtime'>Last Modified</th>
 | 
			
		||||
  </tr>
 | 
			
		||||
    PAGE
 | 
			
		||||
    DIR_PAGE_FOOTER = <<-PAGE
 | 
			
		||||
</table>
 | 
			
		||||
<hr />
 | 
			
		||||
</body></html>
 | 
			
		||||
    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
 | 
			
		||||
@ -1,68 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,157 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require_relative 'files'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  warn "Rack::File is deprecated and will be removed in Rack 3.1", uplevel: 1
 | 
			
		||||
 | 
			
		||||
  File = Files
 | 
			
		||||
end
 | 
			
		||||
@ -1,216 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,26 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,154 +0,0 @@
 | 
			
		||||
module Rack
 | 
			
		||||
  # Rack::Headers is a Hash subclass that downcases all keys.  It's designed
 | 
			
		||||
  # to be used by rack applications that don't implement the Rack 3 SPEC
 | 
			
		||||
  # (by using non-lowercase response header keys), automatically handling
 | 
			
		||||
  # the downcasing of keys.
 | 
			
		||||
  class Headers < Hash
 | 
			
		||||
    def self.[](*items)
 | 
			
		||||
      if items.length % 2 != 0
 | 
			
		||||
        if items.length == 1 && items.first.is_a?(Hash)
 | 
			
		||||
          new.merge!(items.first)
 | 
			
		||||
        else
 | 
			
		||||
          raise ArgumentError, "odd number of arguments for Rack::Headers"
 | 
			
		||||
        end
 | 
			
		||||
      else
 | 
			
		||||
        hash = new
 | 
			
		||||
        loop do
 | 
			
		||||
          break if items.length == 0
 | 
			
		||||
          key = items.shift
 | 
			
		||||
          value = items.shift
 | 
			
		||||
          hash[key] = value
 | 
			
		||||
        end
 | 
			
		||||
        hash
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def [](key)
 | 
			
		||||
      super(downcase_key(key))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def []=(key, value)
 | 
			
		||||
      super(key.downcase.freeze, value)
 | 
			
		||||
    end
 | 
			
		||||
    alias store []=
 | 
			
		||||
 | 
			
		||||
    def assoc(key)
 | 
			
		||||
      super(downcase_key(key))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def compare_by_identity
 | 
			
		||||
      raise TypeError, "Rack::Headers cannot compare by identity, use regular Hash"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def delete(key)
 | 
			
		||||
      super(downcase_key(key))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def dig(key, *a)
 | 
			
		||||
      super(downcase_key(key), *a)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def fetch(key, *default, &block)
 | 
			
		||||
      key = downcase_key(key)
 | 
			
		||||
      super
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def fetch_values(*a)
 | 
			
		||||
      super(*a.map!{|key| downcase_key(key)})
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def has_key?(key)
 | 
			
		||||
      super(downcase_key(key))
 | 
			
		||||
    end
 | 
			
		||||
    alias include? has_key?
 | 
			
		||||
    alias key? has_key?
 | 
			
		||||
    alias member? has_key?
 | 
			
		||||
 | 
			
		||||
    def invert
 | 
			
		||||
      hash = self.class.new
 | 
			
		||||
      each{|key, value| hash[value] = key}
 | 
			
		||||
      hash
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def merge(hash, &block)
 | 
			
		||||
      dup.merge!(hash, &block)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def reject(&block)
 | 
			
		||||
      hash = dup
 | 
			
		||||
      hash.reject!(&block)
 | 
			
		||||
      hash
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def replace(hash)
 | 
			
		||||
      clear
 | 
			
		||||
      update(hash)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def select(&block)
 | 
			
		||||
      hash = dup
 | 
			
		||||
      hash.select!(&block)
 | 
			
		||||
      hash
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def to_proc
 | 
			
		||||
      lambda{|x| self[x]}
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def transform_values(&block)
 | 
			
		||||
      dup.transform_values!(&block)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def update(hash, &block)
 | 
			
		||||
      hash.each do |key, value|
 | 
			
		||||
        self[key] = if block_given? && include?(key)
 | 
			
		||||
          block.call(key, self[key], value)
 | 
			
		||||
        else
 | 
			
		||||
          value
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      self
 | 
			
		||||
    end
 | 
			
		||||
    alias merge! update
 | 
			
		||||
 | 
			
		||||
    def values_at(*keys)
 | 
			
		||||
      keys.map{|key| self[key]}
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # :nocov:
 | 
			
		||||
    if RUBY_VERSION >= '2.5'
 | 
			
		||||
    # :nocov:
 | 
			
		||||
      def slice(*a)
 | 
			
		||||
        h = self.class.new
 | 
			
		||||
        a.each{|k| h[k] = self[k] if has_key?(k)}
 | 
			
		||||
        h
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def transform_keys(&block)
 | 
			
		||||
        dup.transform_keys!(&block)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def transform_keys!
 | 
			
		||||
        hash = self.class.new
 | 
			
		||||
        each do |k, v|
 | 
			
		||||
          hash[yield k] = v
 | 
			
		||||
        end
 | 
			
		||||
        replace(hash)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # :nocov:
 | 
			
		||||
    if RUBY_VERSION >= '3.0'
 | 
			
		||||
    # :nocov:
 | 
			
		||||
      def except(*a)
 | 
			
		||||
        super(*a.map!{|key| downcase_key(key)})
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    def downcase_key(key)
 | 
			
		||||
      key.is_a?(String) ? key.downcase : key
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,907 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'forwardable'
 | 
			
		||||
 | 
			
		||||
require_relative 'constants'
 | 
			
		||||
require_relative 'utils'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Rack::Lint validates your application and the requests and
 | 
			
		||||
  # responses according to the Rack spec.
 | 
			
		||||
 | 
			
		||||
  class Lint
 | 
			
		||||
    def initialize(app)
 | 
			
		||||
      @app = app
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # :stopdoc:
 | 
			
		||||
 | 
			
		||||
    class LintError < RuntimeError; end
 | 
			
		||||
    # AUTHORS: n.b. The trailing whitespace between paragraphs is important and
 | 
			
		||||
    # should not be removed. The whitespace creates paragraphs in the RDoc
 | 
			
		||||
    # output.
 | 
			
		||||
    #
 | 
			
		||||
    ## This specification aims to formalize the Rack protocol. You
 | 
			
		||||
    ## can (and should) use Rack::Lint to enforce it.
 | 
			
		||||
    ##
 | 
			
		||||
    ## When you develop middleware, be sure to add a Lint before and
 | 
			
		||||
    ## after to catch all mistakes.
 | 
			
		||||
    ##
 | 
			
		||||
    ## = Rack applications
 | 
			
		||||
    ##
 | 
			
		||||
    ## A Rack application is a Ruby object (not a class) that
 | 
			
		||||
    ## responds to +call+.
 | 
			
		||||
    def call(env = nil)
 | 
			
		||||
      Wrapper.new(@app, env).response
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    class Wrapper
 | 
			
		||||
      def initialize(app, env)
 | 
			
		||||
        @app = app
 | 
			
		||||
        @env = env
 | 
			
		||||
        @response = nil
 | 
			
		||||
        @head_request = false
 | 
			
		||||
 | 
			
		||||
        @status = nil
 | 
			
		||||
        @headers = nil
 | 
			
		||||
        @body = nil
 | 
			
		||||
        @invoked = nil
 | 
			
		||||
        @content_length = nil
 | 
			
		||||
        @closed = false
 | 
			
		||||
        @size = 0
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def response
 | 
			
		||||
        ## It takes exactly one argument, the *environment*
 | 
			
		||||
        raise LintError, "No env given" unless @env
 | 
			
		||||
        check_environment(@env)
 | 
			
		||||
 | 
			
		||||
        @env[RACK_INPUT] = InputWrapper.new(@env[RACK_INPUT])
 | 
			
		||||
        @env[RACK_ERRORS] = ErrorWrapper.new(@env[RACK_ERRORS])
 | 
			
		||||
 | 
			
		||||
        ## and returns a non-frozen Array of exactly three values:
 | 
			
		||||
        @response = @app.call(@env)
 | 
			
		||||
        raise LintError, "response is not an Array, but #{@response.class}" unless @response.kind_of? Array
 | 
			
		||||
        raise LintError, "response is frozen" if @response.frozen?
 | 
			
		||||
        raise LintError, "response array has #{@response.size} elements instead of 3" unless @response.size == 3
 | 
			
		||||
 | 
			
		||||
        @status, @headers, @body = @response
 | 
			
		||||
        ## The *status*,
 | 
			
		||||
        check_status(@status)
 | 
			
		||||
 | 
			
		||||
        ## the *headers*,
 | 
			
		||||
        check_headers(@headers)
 | 
			
		||||
 | 
			
		||||
        hijack_proc = check_hijack_response(@headers, @env)
 | 
			
		||||
        if hijack_proc
 | 
			
		||||
          @headers[RACK_HIJACK] = hijack_proc
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## and the *body*.
 | 
			
		||||
        check_content_type(@status, @headers)
 | 
			
		||||
        check_content_length(@status, @headers)
 | 
			
		||||
        @head_request = @env[REQUEST_METHOD] == HEAD
 | 
			
		||||
 | 
			
		||||
        @lint = (@env['rack.lint'] ||= []) << self
 | 
			
		||||
 | 
			
		||||
        if (@env['rack.lint.body_iteration'] ||= 0) > 0
 | 
			
		||||
          raise LintError, "Middleware must not call #each directly"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        return [@status, @headers, self]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## == The Environment
 | 
			
		||||
      ##
 | 
			
		||||
      def check_environment(env)
 | 
			
		||||
        ## The environment must be an unfrozen instance of Hash that includes
 | 
			
		||||
        ## CGI-like headers. The Rack application is free to modify the
 | 
			
		||||
        ## environment.
 | 
			
		||||
        raise LintError, "env #{env.inspect} is not a Hash, but #{env.class}" unless env.kind_of? Hash
 | 
			
		||||
        raise LintError, "env should not be frozen, but is" if env.frozen?
 | 
			
		||||
 | 
			
		||||
        ##
 | 
			
		||||
        ## The environment is required to include these variables
 | 
			
		||||
        ## (adopted from {PEP 333}[https://peps.python.org/pep-0333/]), except when they'd be empty, but see
 | 
			
		||||
        ## below.
 | 
			
		||||
 | 
			
		||||
        ## <tt>REQUEST_METHOD</tt>:: The HTTP request method, such as
 | 
			
		||||
        ##                           "GET" or "POST". This cannot ever
 | 
			
		||||
        ##                           be an empty string, and so is
 | 
			
		||||
        ##                           always required.
 | 
			
		||||
 | 
			
		||||
        ## <tt>SCRIPT_NAME</tt>:: 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.
 | 
			
		||||
 | 
			
		||||
        ## <tt>PATH_INFO</tt>:: 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.
 | 
			
		||||
 | 
			
		||||
        ## <tt>QUERY_STRING</tt>:: The portion of the request URL that
 | 
			
		||||
        ##                         follows the <tt>?</tt>, if any. May be
 | 
			
		||||
        ##                         empty, but is always required!
 | 
			
		||||
 | 
			
		||||
        ## <tt>SERVER_NAME</tt>:: When combined with <tt>SCRIPT_NAME</tt> and
 | 
			
		||||
        ##                        <tt>PATH_INFO</tt>, these variables can be
 | 
			
		||||
        ##                        used to complete the URL. Note, however,
 | 
			
		||||
        ##                        that <tt>HTTP_HOST</tt>, if present,
 | 
			
		||||
        ##                        should be used in preference to
 | 
			
		||||
        ##                        <tt>SERVER_NAME</tt> for reconstructing
 | 
			
		||||
        ##                        the request URL.
 | 
			
		||||
        ##                        <tt>SERVER_NAME</tt> can never be an empty
 | 
			
		||||
        ##                        string, and so is always required.
 | 
			
		||||
 | 
			
		||||
        ## <tt>SERVER_PORT</tt>:: 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.
 | 
			
		||||
 | 
			
		||||
        ## <tt>SERVER_PROTOCOL</tt>:: A string representing the HTTP version used
 | 
			
		||||
        ##                            for the request.
 | 
			
		||||
 | 
			
		||||
        ## <tt>HTTP_</tt> Variables:: Variables corresponding to the
 | 
			
		||||
        ##                            client-supplied HTTP request
 | 
			
		||||
        ##                            headers (i.e., variables whose
 | 
			
		||||
        ##                            names begin with <tt>HTTP_</tt>). 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:
 | 
			
		||||
 | 
			
		||||
        ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the
 | 
			
		||||
        ##                            request URL.
 | 
			
		||||
 | 
			
		||||
        ## <tt>rack.input</tt>:: See below, the input stream.
 | 
			
		||||
 | 
			
		||||
        ## <tt>rack.errors</tt>:: See below, the error stream.
 | 
			
		||||
 | 
			
		||||
        ## <tt>rack.hijack?</tt>:: See below, if present and true, indicates
 | 
			
		||||
        ##                         that the server supports partial hijacking.
 | 
			
		||||
 | 
			
		||||
        ## <tt>rack.hijack</tt>:: See below, if present, an object responding
 | 
			
		||||
        ##                        to +call+ that is used to perform a full
 | 
			
		||||
        ##                        hijack.
 | 
			
		||||
 | 
			
		||||
        ## Additional environment specifications have approved to
 | 
			
		||||
        ## standardized middleware APIs. None of these are required to
 | 
			
		||||
        ## be implemented by the server.
 | 
			
		||||
 | 
			
		||||
        ## <tt>rack.session</tt>:: 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
 | 
			
		||||
 | 
			
		||||
        ## <tt>rack.logger</tt>:: 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
 | 
			
		||||
 | 
			
		||||
        ## <tt>rack.multipart.buffer_size</tt>:: 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
 | 
			
		||||
 | 
			
		||||
        ## <tt>rack.multipart.tempfile_factory</tt>:: 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 <tt>rack.</tt>
 | 
			
		||||
        ## is reserved for use with the Rack core distribution and other
 | 
			
		||||
        ## accepted specifications and must not be used otherwise.
 | 
			
		||||
        ##
 | 
			
		||||
 | 
			
		||||
        %w[REQUEST_METHOD SERVER_NAME QUERY_STRING SERVER_PROTOCOL
 | 
			
		||||
           rack.input rack.errors].each { |header|
 | 
			
		||||
          raise LintError, "env missing required key #{header}" unless env.include? header
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ## The <tt>SERVER_PORT</tt> 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 <tt>SERVER_NAME</tt> 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 <tt>HTTP_HOST</tt> 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 <tt>SERVER_PROTOCOL</tt> must match the regexp <tt>HTTP/\d(\.\d)?</tt>.
 | 
			
		||||
        server_protocol = env['SERVER_PROTOCOL']
 | 
			
		||||
        unless %r{HTTP/\d(\.\d)?}.match?(server_protocol)
 | 
			
		||||
          raise LintError, "env[SERVER_PROTOCOL] does not match HTTP/\\d(\\.\\d)?"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## If the <tt>HTTP_VERSION</tt> is present, it must equal the <tt>SERVER_PROTOCOL</tt>.
 | 
			
		||||
        if env['HTTP_VERSION'] && env['HTTP_VERSION'] != server_protocol
 | 
			
		||||
          raise LintError, "env[HTTP_VERSION] does not equal env[SERVER_PROTOCOL]"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## The environment must not contain the keys
 | 
			
		||||
        ## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
 | 
			
		||||
        ## (use the versions without <tt>HTTP_</tt>).
 | 
			
		||||
        %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:
 | 
			
		||||
 | 
			
		||||
        ## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
 | 
			
		||||
        unless %w[http https].include?(env[RACK_URL_SCHEME])
 | 
			
		||||
          raise LintError, "rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## * There must be a valid input stream in <tt>rack.input</tt>.
 | 
			
		||||
        check_input env[RACK_INPUT]
 | 
			
		||||
        ## * There must be a valid error stream in <tt>rack.errors</tt>.
 | 
			
		||||
        check_error env[RACK_ERRORS]
 | 
			
		||||
        ## * There may be a valid hijack callback in <tt>rack.hijack</tt>
 | 
			
		||||
        check_hijack env
 | 
			
		||||
 | 
			
		||||
        ## * The <tt>REQUEST_METHOD</tt> 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 <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
 | 
			
		||||
        if env.include?(SCRIPT_NAME) && env[SCRIPT_NAME] != "" && env[SCRIPT_NAME] !~ /\A\//
 | 
			
		||||
          raise LintError, "SCRIPT_NAME must start with /"
 | 
			
		||||
        end
 | 
			
		||||
        ## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
 | 
			
		||||
        if env.include?(PATH_INFO) && env[PATH_INFO] != "" && env[PATH_INFO] !~ /\A\//
 | 
			
		||||
          raise LintError, "PATH_INFO must start with /"
 | 
			
		||||
        end
 | 
			
		||||
        ## * The <tt>CONTENT_LENGTH</tt>, 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 <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
 | 
			
		||||
        ##   set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
 | 
			
		||||
        ##   <tt>SCRIPT_NAME</tt> 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
 | 
			
		||||
        ##   <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
 | 
			
		||||
        unless env[SCRIPT_NAME] != "/"
 | 
			
		||||
          raise LintError, "SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## <tt>rack.response_finished</tt>:: An array of callables run by the server after the response has been
 | 
			
		||||
        ## processed. This would typically be invoked after sending the response to the client, but it could also be
 | 
			
		||||
        ## invoked if an error occurs while generating the response or sending the response; in that case, the error
 | 
			
		||||
        ## argument will be a subclass of +Exception+.
 | 
			
		||||
        ## The callables are invoked with +env, status, headers, error+ arguments and should not raise any
 | 
			
		||||
        ## exceptions. They should be invoked in reverse order of registration.
 | 
			
		||||
        if callables = env[RACK_RESPONSE_FINISHED]
 | 
			
		||||
          raise LintError, "rack.response_finished must be an array of callable objects" unless callables.is_a?(Array)
 | 
			
		||||
 | 
			
		||||
          callables.each do |callable|
 | 
			
		||||
            raise LintError, "rack.response_finished values must respond to call(env, status, headers, error)" unless callable.respond_to?(:call)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## === The Input Stream
 | 
			
		||||
      ##
 | 
			
		||||
      ## The input stream is an IO-like object which contains the raw HTTP
 | 
			
		||||
      ## POST data.
 | 
			
		||||
      def check_input(input)
 | 
			
		||||
        ## When applicable, its external encoding must be "ASCII-8BIT" and it
 | 
			
		||||
        ## must be opened in binary mode, for Ruby 1.9 compatibility.
 | 
			
		||||
        if input.respond_to?(:external_encoding) && input.external_encoding != Encoding::ASCII_8BIT
 | 
			
		||||
          raise LintError, "rack.input #{input} does not have ASCII-8BIT as its external encoding"
 | 
			
		||||
        end
 | 
			
		||||
        if input.respond_to?(:binmode?) && !input.binmode?
 | 
			
		||||
          raise LintError, "rack.input #{input} is not opened in binary mode"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## The input stream must respond to +gets+, +each+, and +read+.
 | 
			
		||||
        [:gets, :each, :read].each { |method|
 | 
			
		||||
          unless input.respond_to? method
 | 
			
		||||
            raise LintError, "rack.input #{input} does not respond to ##{method}"
 | 
			
		||||
          end
 | 
			
		||||
        }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      class InputWrapper
 | 
			
		||||
        def initialize(input)
 | 
			
		||||
          @input = input
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## * +gets+ must be called without arguments and return a string,
 | 
			
		||||
        ##   or +nil+ on EOF.
 | 
			
		||||
        def gets(*args)
 | 
			
		||||
          raise LintError, "rack.input#gets called with arguments" unless args.size == 0
 | 
			
		||||
          v = @input.gets
 | 
			
		||||
          unless v.nil? or v.kind_of? String
 | 
			
		||||
            raise LintError, "rack.input#gets didn't return a String"
 | 
			
		||||
          end
 | 
			
		||||
          v
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## * +read+ behaves like IO#read.
 | 
			
		||||
        ##   Its signature is <tt>read([length, [buffer]])</tt>.
 | 
			
		||||
        ##
 | 
			
		||||
        ##   If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
 | 
			
		||||
        ##   and +buffer+ must be a String and may not be nil.
 | 
			
		||||
        ##
 | 
			
		||||
        ##   If +length+ is given and not nil, then this method reads at most
 | 
			
		||||
        ##   +length+ bytes from the input stream.
 | 
			
		||||
        ##
 | 
			
		||||
        ##   If +length+ is not given or nil, then this method reads
 | 
			
		||||
        ##   all data until EOF.
 | 
			
		||||
        ##
 | 
			
		||||
        ##   When EOF is reached, this method returns nil if +length+ is given
 | 
			
		||||
        ##   and not nil, or "" if +length+ is not given or is nil.
 | 
			
		||||
        ##
 | 
			
		||||
        ##   If +buffer+ is given, then the read data will be placed
 | 
			
		||||
        ##   into +buffer+ instead of a newly created String object.
 | 
			
		||||
        def read(*args)
 | 
			
		||||
          unless args.size <= 2
 | 
			
		||||
            raise LintError, "rack.input#read called with too many arguments"
 | 
			
		||||
          end
 | 
			
		||||
          if args.size >= 1
 | 
			
		||||
            unless args.first.kind_of?(Integer) || args.first.nil?
 | 
			
		||||
              raise LintError, "rack.input#read called with non-integer and non-nil length"
 | 
			
		||||
            end
 | 
			
		||||
            unless args.first.nil? || args.first >= 0
 | 
			
		||||
              raise LintError, "rack.input#read called with a negative length"
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
          if args.size >= 2
 | 
			
		||||
            unless args[1].kind_of?(String)
 | 
			
		||||
              raise LintError, "rack.input#read called with non-String buffer"
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          v = @input.read(*args)
 | 
			
		||||
 | 
			
		||||
          unless v.nil? or v.kind_of? String
 | 
			
		||||
            raise LintError, "rack.input#read didn't return nil or a String"
 | 
			
		||||
          end
 | 
			
		||||
          if args[0].nil?
 | 
			
		||||
            unless !v.nil?
 | 
			
		||||
              raise LintError, "rack.input#read(nil) returned nil on EOF"
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          v
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## * +each+ must be called without arguments and only yield Strings.
 | 
			
		||||
        def each(*args)
 | 
			
		||||
          raise LintError, "rack.input#each called with arguments" unless args.size == 0
 | 
			
		||||
          @input.each { |line|
 | 
			
		||||
            unless line.kind_of? String
 | 
			
		||||
              raise LintError, "rack.input#each didn't yield a String"
 | 
			
		||||
            end
 | 
			
		||||
            yield line
 | 
			
		||||
          }
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## * +close+ can be called on the input stream to indicate that the
 | 
			
		||||
        ## any remaining input is not needed.
 | 
			
		||||
        def close(*args)
 | 
			
		||||
          @input.close(*args)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## === The Error Stream
 | 
			
		||||
      ##
 | 
			
		||||
      def check_error(error)
 | 
			
		||||
        ## The error stream must respond to +puts+, +write+ and +flush+.
 | 
			
		||||
        [:puts, :write, :flush].each { |method|
 | 
			
		||||
          unless error.respond_to? method
 | 
			
		||||
            raise LintError, "rack.error #{error} does not respond to ##{method}"
 | 
			
		||||
          end
 | 
			
		||||
        }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      class ErrorWrapper
 | 
			
		||||
        def initialize(error)
 | 
			
		||||
          @error = error
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## * +puts+ must be called with a single argument that responds to +to_s+.
 | 
			
		||||
        def puts(str)
 | 
			
		||||
          @error.puts str
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## * +write+ must be called with a single argument that is a String.
 | 
			
		||||
        def write(str)
 | 
			
		||||
          raise LintError, "rack.errors#write not called with a String" unless str.kind_of? String
 | 
			
		||||
          @error.write str
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## * +flush+ must be called without arguments and must be called
 | 
			
		||||
        ##   in order to make the error appear for sure.
 | 
			
		||||
        def flush
 | 
			
		||||
          @error.flush
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        ## * +close+ must never be called on the error stream.
 | 
			
		||||
        def close(*args)
 | 
			
		||||
          raise LintError, "rack.errors#close must not be called"
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## === Hijacking
 | 
			
		||||
      ##
 | 
			
		||||
      ## The hijacking interfaces provides a means for an application to take
 | 
			
		||||
      ## control of the HTTP connection. There are two distinct hijack
 | 
			
		||||
      ## interfaces: full hijacking where the application takes over the raw
 | 
			
		||||
      ## connection, and partial hijacking where the application takes over
 | 
			
		||||
      ## just the response body stream. In both cases, the application is
 | 
			
		||||
      ## responsible for closing the hijacked stream.
 | 
			
		||||
      ##
 | 
			
		||||
      ## Full hijacking only works with HTTP/1. Partial hijacking is functionally
 | 
			
		||||
      ## equivalent to streaming bodies, and is still optionally supported for
 | 
			
		||||
      ## backwards compatibility with older Rack versions.
 | 
			
		||||
      ##
 | 
			
		||||
      ## ==== Full Hijack
 | 
			
		||||
      ##
 | 
			
		||||
      ## Full hijack is used to completely take over an HTTP/1 connection. It
 | 
			
		||||
      ## occurs before any headers are written and causes the request to
 | 
			
		||||
      ## ignores any response generated by the application.
 | 
			
		||||
      ##
 | 
			
		||||
      ## It is intended to be used when applications need access to raw HTTP/1
 | 
			
		||||
      ## connection.
 | 
			
		||||
      ##
 | 
			
		||||
      def check_hijack(env)
 | 
			
		||||
        ## If +rack.hijack+ is present in +env+, it must respond to +call+
 | 
			
		||||
        if original_hijack = env[RACK_HIJACK]
 | 
			
		||||
          raise LintError, "rack.hijack must respond to call" unless original_hijack.respond_to?(:call)
 | 
			
		||||
 | 
			
		||||
          env[RACK_HIJACK] = proc do
 | 
			
		||||
            io = original_hijack.call
 | 
			
		||||
 | 
			
		||||
            ## and return an +IO+ instance which can be used to read and write
 | 
			
		||||
            ## to the underlying connection using HTTP/1 semantics and
 | 
			
		||||
            ## formatting.
 | 
			
		||||
            raise LintError, "rack.hijack must return an IO instance" unless io.is_a?(IO)
 | 
			
		||||
 | 
			
		||||
            io
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## ==== Partial Hijack
 | 
			
		||||
      ##
 | 
			
		||||
      ## Partial hijack is used for bi-directional streaming of the request and
 | 
			
		||||
      ## response body. It occurs after the status and headers are written by
 | 
			
		||||
      ## the server and causes the server to ignore the Body of the response.
 | 
			
		||||
      ##
 | 
			
		||||
      ## It is intended to be used when applications need bi-directional
 | 
			
		||||
      ## streaming.
 | 
			
		||||
      ##
 | 
			
		||||
      def check_hijack_response(headers, env)
 | 
			
		||||
        ## If +rack.hijack?+ is present in +env+ and truthy,
 | 
			
		||||
        if env[RACK_IS_HIJACK]
 | 
			
		||||
          ## an application may set the special response header +rack.hijack+
 | 
			
		||||
          if original_hijack = headers[RACK_HIJACK]
 | 
			
		||||
            ## to an object that responds to +call+,
 | 
			
		||||
            unless original_hijack.respond_to?(:call)
 | 
			
		||||
              raise LintError, 'rack.hijack header must respond to #call'
 | 
			
		||||
            end
 | 
			
		||||
            ## accepting a +stream+ argument.
 | 
			
		||||
            return proc do |io|
 | 
			
		||||
              original_hijack.call StreamWrapper.new(io)
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
          ##
 | 
			
		||||
          ## After the response status and headers have been sent, this hijack
 | 
			
		||||
          ## callback will be invoked with a +stream+ argument which follows the
 | 
			
		||||
          ## same interface as outlined in "Streaming Body". Servers must
 | 
			
		||||
          ## ignore the +body+ part of the response tuple when the
 | 
			
		||||
          ## +rack.hijack+ response header is present. Using an empty +Array+
 | 
			
		||||
          ## instance is recommended.
 | 
			
		||||
        else
 | 
			
		||||
          ##
 | 
			
		||||
          ## The special response header +rack.hijack+ must only be set
 | 
			
		||||
          ## if the request +env+ has a truthy +rack.hijack?+.
 | 
			
		||||
          if headers.key?(RACK_HIJACK)
 | 
			
		||||
            raise LintError, 'rack.hijack header must not be present if server does not support hijacking'
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        nil
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ## == The Response
 | 
			
		||||
      ##
 | 
			
		||||
      ## === The Status
 | 
			
		||||
      ##
 | 
			
		||||
      def check_status(status)
 | 
			
		||||
        ## This is an HTTP status. It must be an Integer greater than or equal to
 | 
			
		||||
        ## 100.
 | 
			
		||||
        unless status.is_a?(Integer) && status >= 100
 | 
			
		||||
          raise LintError, "Status must be an Integer >=100"
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## === The Headers
 | 
			
		||||
      ##
 | 
			
		||||
      def check_headers(headers)
 | 
			
		||||
        ## The headers must be a unfrozen Hash.
 | 
			
		||||
        unless headers.kind_of?(Hash)
 | 
			
		||||
          raise LintError, "headers object should be a hash, but isn't (got #{headers.class} as headers)"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        if headers.frozen?
 | 
			
		||||
          raise LintError, "headers object should not be frozen, but is"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        headers.each do |key, value|
 | 
			
		||||
          ## The header keys must be Strings.
 | 
			
		||||
          unless key.kind_of? String
 | 
			
		||||
            raise LintError, "header key must be a string, was #{key.class}"
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          ## Special headers starting "rack." are for communicating with the
 | 
			
		||||
          ## server, and must not be sent back to the client.
 | 
			
		||||
          next if key.start_with?("rack.")
 | 
			
		||||
 | 
			
		||||
          ## The header must not contain a +Status+ key.
 | 
			
		||||
          raise LintError, "header must not contain status" if key == "status"
 | 
			
		||||
          ## Header keys must conform to RFC7230 token specification, i.e. cannot
 | 
			
		||||
          ## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}".
 | 
			
		||||
          raise LintError, "invalid header name: #{key}" if key =~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/
 | 
			
		||||
          ## Header keys must not contain uppercase ASCII characters (A-Z).
 | 
			
		||||
          raise LintError, "uppercase character in header name: #{key}" if key =~ /[A-Z]/
 | 
			
		||||
 | 
			
		||||
          ## Header values must be either a String instance,
 | 
			
		||||
          if value.kind_of?(String)
 | 
			
		||||
            check_header_value(key, value)
 | 
			
		||||
          elsif value.kind_of?(Array)
 | 
			
		||||
            ## or an Array of String instances,
 | 
			
		||||
            value.each{|value| check_header_value(key, value)}
 | 
			
		||||
          else
 | 
			
		||||
            raise LintError, "a header value must be a String or Array of Strings, but the value of '#{key}' is a #{value.class}"
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def check_header_value(key, value)
 | 
			
		||||
        ## such that each String instance must not contain characters below 037.
 | 
			
		||||
        if value =~ /[\000-\037]/
 | 
			
		||||
          raise LintError, "invalid header value #{key}: #{value.inspect}"
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## === The content-type
 | 
			
		||||
      ##
 | 
			
		||||
      def check_content_type(status, headers)
 | 
			
		||||
        headers.each { |key, value|
 | 
			
		||||
          ## There must not be a <tt>content-type</tt> header key when the +Status+ is 1xx,
 | 
			
		||||
          ## 204, or 304.
 | 
			
		||||
          if key == "content-type"
 | 
			
		||||
            if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
 | 
			
		||||
              raise LintError, "content-type header found in #{status} response, not allowed"
 | 
			
		||||
            end
 | 
			
		||||
            return
 | 
			
		||||
          end
 | 
			
		||||
        }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## === The content-length
 | 
			
		||||
      ##
 | 
			
		||||
      def check_content_length(status, headers)
 | 
			
		||||
        headers.each { |key, value|
 | 
			
		||||
          if key == 'content-length'
 | 
			
		||||
            ## There must not be a <tt>content-length</tt> header key when the
 | 
			
		||||
            ## +Status+ is 1xx, 204, or 304.
 | 
			
		||||
            if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
 | 
			
		||||
              raise LintError, "content-length header found in #{status} response, not allowed"
 | 
			
		||||
            end
 | 
			
		||||
            @content_length = value
 | 
			
		||||
          end
 | 
			
		||||
        }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def verify_content_length(size)
 | 
			
		||||
        if @head_request
 | 
			
		||||
          unless size == 0
 | 
			
		||||
            raise LintError, "Response body was given for HEAD request, but should be empty"
 | 
			
		||||
          end
 | 
			
		||||
        elsif @content_length
 | 
			
		||||
          unless @content_length == size.to_s
 | 
			
		||||
            raise LintError, "content-length header was #{@content_length}, but should be #{size}"
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## === The Body
 | 
			
		||||
      ##
 | 
			
		||||
      ## The Body is typically an +Array+ of +String+ instances, an enumerable
 | 
			
		||||
      ## that yields +String+ instances, a +Proc+ instance, or a File-like
 | 
			
		||||
      ## object.
 | 
			
		||||
      ##
 | 
			
		||||
      ## The Body must respond to +each+ or +call+. It may optionally respond
 | 
			
		||||
      ## to +to_path+ or +to_ary+. A Body that responds to +each+ is considered
 | 
			
		||||
      ## to be an Enumerable Body. A Body that responds to +call+ is considered
 | 
			
		||||
      ## to be a Streaming Body.
 | 
			
		||||
      ##
 | 
			
		||||
      ## A Body that responds to both +each+ and +call+ must be treated as an
 | 
			
		||||
      ## Enumerable Body, not a Streaming Body. If it responds to +each+, you
 | 
			
		||||
      ## must call +each+ and not +call+. If the Body doesn't respond to
 | 
			
		||||
      ## +each+, then you can assume it responds to +call+.
 | 
			
		||||
      ##
 | 
			
		||||
      ## The Body must either be consumed or returned. The Body is consumed by
 | 
			
		||||
      ## optionally calling either +each+ or +call+.
 | 
			
		||||
      ## Then, if the Body responds to +close+, it must be called to release
 | 
			
		||||
      ## any resources associated with the generation of the body.
 | 
			
		||||
      ## In other words, +close+ must always be called at least once; typically
 | 
			
		||||
      ## after the web server has sent the response to the client, but also in
 | 
			
		||||
      ## cases where the Rack application makes internal/virtual requests and
 | 
			
		||||
      ## discards the response.
 | 
			
		||||
      ##
 | 
			
		||||
      def close
 | 
			
		||||
        ##
 | 
			
		||||
        ## After calling +close+, the Body is considered closed and should not
 | 
			
		||||
        ## be consumed again.
 | 
			
		||||
        @closed = true
 | 
			
		||||
 | 
			
		||||
        ## If the original Body is replaced by a new Body, the new Body must
 | 
			
		||||
        ## also consume the original Body by calling +close+ if possible.
 | 
			
		||||
        @body.close if @body.respond_to?(:close)
 | 
			
		||||
 | 
			
		||||
        index = @lint.index(self)
 | 
			
		||||
        unless @env['rack.lint'][0..index].all? {|lint| lint.instance_variable_get(:@closed)}
 | 
			
		||||
          raise LintError, "Body has not been closed"
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def verify_to_path
 | 
			
		||||
        ##
 | 
			
		||||
        ## If the Body responds to +to_path+, it must return a +String+
 | 
			
		||||
        ## path for the local file system whose contents are identical
 | 
			
		||||
        ## to that produced by calling +each+; this may be used by the
 | 
			
		||||
        ## server as an alternative, possibly more efficient way to
 | 
			
		||||
        ## transport the response. The +to_path+ method does not consume
 | 
			
		||||
        ## the body.
 | 
			
		||||
        if @body.respond_to?(:to_path)
 | 
			
		||||
          unless ::File.exist? @body.to_path
 | 
			
		||||
            raise LintError, "The file identified by body.to_path does not exist"
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## ==== Enumerable Body
 | 
			
		||||
      ##
 | 
			
		||||
      def each
 | 
			
		||||
        ## The Enumerable Body must respond to +each+.
 | 
			
		||||
        raise LintError, "Enumerable Body must respond to each" unless @body.respond_to?(:each)
 | 
			
		||||
 | 
			
		||||
        ## It must only be called once.
 | 
			
		||||
        raise LintError, "Response body must only be invoked once (#{@invoked})" unless @invoked.nil?
 | 
			
		||||
 | 
			
		||||
        ## It must not be called after being closed.
 | 
			
		||||
        raise LintError, "Response body is already closed" if @closed
 | 
			
		||||
 | 
			
		||||
        @invoked = :each
 | 
			
		||||
 | 
			
		||||
        @body.each do |chunk|
 | 
			
		||||
          ## and must only yield String values.
 | 
			
		||||
          unless chunk.kind_of? String
 | 
			
		||||
            raise LintError, "Body yielded non-string value #{chunk.inspect}"
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          ##
 | 
			
		||||
          ## The Body itself should not be an instance of String, as this will
 | 
			
		||||
          ## break in Ruby 1.9.
 | 
			
		||||
          ##
 | 
			
		||||
          ## Middleware must not call +each+ directly on the Body.
 | 
			
		||||
          ## Instead, middleware can return a new Body that calls +each+ on the
 | 
			
		||||
          ## original Body, yielding at least once per iteration.
 | 
			
		||||
          if @lint[0] == self
 | 
			
		||||
            @env['rack.lint.body_iteration'] += 1
 | 
			
		||||
          else
 | 
			
		||||
            if (@env['rack.lint.body_iteration'] -= 1) > 0
 | 
			
		||||
              raise LintError, "New body must yield at least once per iteration of old body"
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          @size += chunk.bytesize
 | 
			
		||||
          yield chunk
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        verify_content_length(@size)
 | 
			
		||||
 | 
			
		||||
        verify_to_path
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      BODY_METHODS = {to_ary: true, each: true, call: true, to_path: true}
 | 
			
		||||
 | 
			
		||||
      def to_path
 | 
			
		||||
        @body.to_path
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def respond_to?(name, *)
 | 
			
		||||
        if BODY_METHODS.key?(name)
 | 
			
		||||
          @body.respond_to?(name)
 | 
			
		||||
        else
 | 
			
		||||
          super
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## If the Body responds to +to_ary+, it must return an +Array+ whose
 | 
			
		||||
      ## contents are identical to that produced by calling +each+.
 | 
			
		||||
      ## Middleware may call +to_ary+ directly on the Body and return a new
 | 
			
		||||
      ## Body in its place. In other words, middleware can only process the
 | 
			
		||||
      ## Body directly if it responds to +to_ary+. If the Body responds to both
 | 
			
		||||
      ## +to_ary+ and +close+, its implementation of +to_ary+ must call
 | 
			
		||||
      ## +close+.
 | 
			
		||||
      def to_ary
 | 
			
		||||
        @body.to_ary.tap do |content|
 | 
			
		||||
          unless content == @body.enum_for.to_a
 | 
			
		||||
            raise LintError, "#to_ary not identical to contents produced by calling #each"
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      ensure
 | 
			
		||||
        close
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      ## ==== Streaming Body
 | 
			
		||||
      ##
 | 
			
		||||
      def call(stream)
 | 
			
		||||
        ## The Streaming Body must respond to +call+.
 | 
			
		||||
        raise LintError, "Streaming Body must respond to call" unless @body.respond_to?(:call)
 | 
			
		||||
 | 
			
		||||
        ## It must only be called once.
 | 
			
		||||
        raise LintError, "Response body must only be invoked once (#{@invoked})" unless @invoked.nil?
 | 
			
		||||
 | 
			
		||||
        ## It must not be called after being closed.
 | 
			
		||||
        raise LintError, "Response body is already closed" if @closed
 | 
			
		||||
 | 
			
		||||
        @invoked = :call
 | 
			
		||||
 | 
			
		||||
        ## It takes a +stream+ argument.
 | 
			
		||||
        ##
 | 
			
		||||
        ## The +stream+ argument must implement:
 | 
			
		||||
        ## <tt>read, write, <<, flush, close, close_read, close_write, closed?</tt>
 | 
			
		||||
        ##
 | 
			
		||||
        @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.
 | 
			
		||||
@ -1,29 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,22 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'logger'
 | 
			
		||||
 | 
			
		||||
require_relative 'constants'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Sets up rack.logger to write to rack.errors stream
 | 
			
		||||
  class Logger
 | 
			
		||||
    def initialize(app, level = ::Logger::INFO)
 | 
			
		||||
      @app, @level = app, level
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def call(env)
 | 
			
		||||
      logger = ::Logger.new(env[RACK_ERRORS])
 | 
			
		||||
      logger.level = @level
 | 
			
		||||
 | 
			
		||||
      env[RACK_LOGGER] = logger
 | 
			
		||||
      @app.call(env)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,43 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Rack::MediaType parse media type and parameters out of content_type string
 | 
			
		||||
 | 
			
		||||
  class MediaType
 | 
			
		||||
    SPLIT_PATTERN = %r{\s*[;,]\s*}
 | 
			
		||||
 | 
			
		||||
    class << self
 | 
			
		||||
      # The media type (type/subtype) portion of the CONTENT_TYPE header
 | 
			
		||||
      # without any media type parameters. e.g., when CONTENT_TYPE is
 | 
			
		||||
      # "text/plain;charset=utf-8", the media-type is "text/plain".
 | 
			
		||||
      #
 | 
			
		||||
      # For more information on the use of media types in HTTP, see:
 | 
			
		||||
      # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
 | 
			
		||||
      def type(content_type)
 | 
			
		||||
        return nil unless content_type
 | 
			
		||||
        content_type.split(SPLIT_PATTERN, 2).first.tap(&:downcase!)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # The media type parameters provided in CONTENT_TYPE as a Hash, or
 | 
			
		||||
      # an empty Hash if no CONTENT_TYPE or media-type parameters were
 | 
			
		||||
      # provided.  e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
 | 
			
		||||
      # this method responds with the following Hash:
 | 
			
		||||
      #   { 'charset' => 'utf-8' }
 | 
			
		||||
      def params(content_type)
 | 
			
		||||
        return {} if content_type.nil?
 | 
			
		||||
 | 
			
		||||
        content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
 | 
			
		||||
          k, v = s.split('=', 2)
 | 
			
		||||
 | 
			
		||||
          hsh[k.tap(&:downcase!)] = strip_doublequotes(v)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      private
 | 
			
		||||
 | 
			
		||||
        def strip_doublequotes(str)
 | 
			
		||||
          (str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str
 | 
			
		||||
        end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,56 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,693 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  module Mime
 | 
			
		||||
    # Returns String with mime type if found, otherwise use +fallback+.
 | 
			
		||||
    # +ext+ should be filename extension in the '.ext' format that
 | 
			
		||||
    #       File.extname(file) returns.
 | 
			
		||||
    # +fallback+ may be any object
 | 
			
		||||
    #
 | 
			
		||||
    # Also see the documentation for MIME_TYPES
 | 
			
		||||
    #
 | 
			
		||||
    # Usage:
 | 
			
		||||
    #     Rack::Mime.mime_type('.foo')
 | 
			
		||||
    #
 | 
			
		||||
    # This is a shortcut for:
 | 
			
		||||
    #     Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
 | 
			
		||||
 | 
			
		||||
    def mime_type(ext, fallback = 'application/octet-stream')
 | 
			
		||||
      MIME_TYPES.fetch(ext.to_s.downcase, fallback)
 | 
			
		||||
    end
 | 
			
		||||
    module_function :mime_type
 | 
			
		||||
 | 
			
		||||
    # Returns true if the given value is a mime match for the given mime match
 | 
			
		||||
    # specification, false otherwise.
 | 
			
		||||
    #
 | 
			
		||||
    #    Rack::Mime.match?('text/html', 'text/*') => true
 | 
			
		||||
    #    Rack::Mime.match?('text/plain', '*') => true
 | 
			
		||||
    #    Rack::Mime.match?('text/html', 'application/json') => false
 | 
			
		||||
 | 
			
		||||
    def match?(value, matcher)
 | 
			
		||||
      v1, v2 = value.split('/', 2)
 | 
			
		||||
      m1, m2 = matcher.split('/', 2)
 | 
			
		||||
 | 
			
		||||
      (m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2)
 | 
			
		||||
    end
 | 
			
		||||
    module_function :match?
 | 
			
		||||
 | 
			
		||||
    # List of most common mime-types, selected various sources
 | 
			
		||||
    # according to their usefulness in a webserving scope for Ruby
 | 
			
		||||
    # users.
 | 
			
		||||
    #
 | 
			
		||||
    # To amend this list with your local mime.types list you can use:
 | 
			
		||||
    #
 | 
			
		||||
    #     require 'webrick/httputils'
 | 
			
		||||
    #     list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types')
 | 
			
		||||
    #     Rack::Mime::MIME_TYPES.merge!(list)
 | 
			
		||||
    #
 | 
			
		||||
    # N.B. On Ubuntu the mime.types file does not include the leading period, so
 | 
			
		||||
    # users may need to modify the data before merging into the hash.
 | 
			
		||||
 | 
			
		||||
    MIME_TYPES = {
 | 
			
		||||
      ".123"       => "application/vnd.lotus-1-2-3",
 | 
			
		||||
      ".3dml"      => "text/vnd.in3d.3dml",
 | 
			
		||||
      ".3g2"       => "video/3gpp2",
 | 
			
		||||
      ".3gp"       => "video/3gpp",
 | 
			
		||||
      ".a"         => "application/octet-stream",
 | 
			
		||||
      ".acc"       => "application/vnd.americandynamics.acc",
 | 
			
		||||
      ".ace"       => "application/x-ace-compressed",
 | 
			
		||||
      ".acu"       => "application/vnd.acucobol",
 | 
			
		||||
      ".aep"       => "application/vnd.audiograph",
 | 
			
		||||
      ".afp"       => "application/vnd.ibm.modcap",
 | 
			
		||||
      ".ai"        => "application/postscript",
 | 
			
		||||
      ".aif"       => "audio/x-aiff",
 | 
			
		||||
      ".aiff"      => "audio/x-aiff",
 | 
			
		||||
      ".ami"       => "application/vnd.amiga.ami",
 | 
			
		||||
      ".apng"      => "image/apng",
 | 
			
		||||
      ".appcache"  => "text/cache-manifest",
 | 
			
		||||
      ".apr"       => "application/vnd.lotus-approach",
 | 
			
		||||
      ".asc"       => "application/pgp-signature",
 | 
			
		||||
      ".asf"       => "video/x-ms-asf",
 | 
			
		||||
      ".asm"       => "text/x-asm",
 | 
			
		||||
      ".aso"       => "application/vnd.accpac.simply.aso",
 | 
			
		||||
      ".asx"       => "video/x-ms-asf",
 | 
			
		||||
      ".atc"       => "application/vnd.acucorp",
 | 
			
		||||
      ".atom"      => "application/atom+xml",
 | 
			
		||||
      ".atomcat"   => "application/atomcat+xml",
 | 
			
		||||
      ".atomsvc"   => "application/atomsvc+xml",
 | 
			
		||||
      ".atx"       => "application/vnd.antix.game-component",
 | 
			
		||||
      ".au"        => "audio/basic",
 | 
			
		||||
      ".avi"       => "video/x-msvideo",
 | 
			
		||||
      ".avif"      => "image/avif",
 | 
			
		||||
      ".bat"       => "application/x-msdownload",
 | 
			
		||||
      ".bcpio"     => "application/x-bcpio",
 | 
			
		||||
      ".bdm"       => "application/vnd.syncml.dm+wbxml",
 | 
			
		||||
      ".bh2"       => "application/vnd.fujitsu.oasysprs",
 | 
			
		||||
      ".bin"       => "application/octet-stream",
 | 
			
		||||
      ".bmi"       => "application/vnd.bmi",
 | 
			
		||||
      ".bmp"       => "image/bmp",
 | 
			
		||||
      ".box"       => "application/vnd.previewsystems.box",
 | 
			
		||||
      ".btif"      => "image/prs.btif",
 | 
			
		||||
      ".bz"        => "application/x-bzip",
 | 
			
		||||
      ".bz2"       => "application/x-bzip2",
 | 
			
		||||
      ".c"         => "text/x-c",
 | 
			
		||||
      ".c4g"       => "application/vnd.clonk.c4group",
 | 
			
		||||
      ".cab"       => "application/vnd.ms-cab-compressed",
 | 
			
		||||
      ".cc"        => "text/x-c",
 | 
			
		||||
      ".ccxml"     => "application/ccxml+xml",
 | 
			
		||||
      ".cdbcmsg"   => "application/vnd.contact.cmsg",
 | 
			
		||||
      ".cdkey"     => "application/vnd.mediastation.cdkey",
 | 
			
		||||
      ".cdx"       => "chemical/x-cdx",
 | 
			
		||||
      ".cdxml"     => "application/vnd.chemdraw+xml",
 | 
			
		||||
      ".cdy"       => "application/vnd.cinderella",
 | 
			
		||||
      ".cer"       => "application/pkix-cert",
 | 
			
		||||
      ".cgm"       => "image/cgm",
 | 
			
		||||
      ".chat"      => "application/x-chat",
 | 
			
		||||
      ".chm"       => "application/vnd.ms-htmlhelp",
 | 
			
		||||
      ".chrt"      => "application/vnd.kde.kchart",
 | 
			
		||||
      ".cif"       => "chemical/x-cif",
 | 
			
		||||
      ".cii"       => "application/vnd.anser-web-certificate-issue-initiation",
 | 
			
		||||
      ".cil"       => "application/vnd.ms-artgalry",
 | 
			
		||||
      ".cla"       => "application/vnd.claymore",
 | 
			
		||||
      ".class"     => "application/octet-stream",
 | 
			
		||||
      ".clkk"      => "application/vnd.crick.clicker.keyboard",
 | 
			
		||||
      ".clkp"      => "application/vnd.crick.clicker.palette",
 | 
			
		||||
      ".clkt"      => "application/vnd.crick.clicker.template",
 | 
			
		||||
      ".clkw"      => "application/vnd.crick.clicker.wordbank",
 | 
			
		||||
      ".clkx"      => "application/vnd.crick.clicker",
 | 
			
		||||
      ".clp"       => "application/x-msclip",
 | 
			
		||||
      ".cmc"       => "application/vnd.cosmocaller",
 | 
			
		||||
      ".cmdf"      => "chemical/x-cmdf",
 | 
			
		||||
      ".cml"       => "chemical/x-cml",
 | 
			
		||||
      ".cmp"       => "application/vnd.yellowriver-custom-menu",
 | 
			
		||||
      ".cmx"       => "image/x-cmx",
 | 
			
		||||
      ".com"       => "application/x-msdownload",
 | 
			
		||||
      ".conf"      => "text/plain",
 | 
			
		||||
      ".cpio"      => "application/x-cpio",
 | 
			
		||||
      ".cpp"       => "text/x-c",
 | 
			
		||||
      ".cpt"       => "application/mac-compactpro",
 | 
			
		||||
      ".crd"       => "application/x-mscardfile",
 | 
			
		||||
      ".crl"       => "application/pkix-crl",
 | 
			
		||||
      ".crt"       => "application/x-x509-ca-cert",
 | 
			
		||||
      ".csh"       => "application/x-csh",
 | 
			
		||||
      ".csml"      => "chemical/x-csml",
 | 
			
		||||
      ".csp"       => "application/vnd.commonspace",
 | 
			
		||||
      ".css"       => "text/css",
 | 
			
		||||
      ".csv"       => "text/csv",
 | 
			
		||||
      ".curl"      => "application/vnd.curl",
 | 
			
		||||
      ".cww"       => "application/prs.cww",
 | 
			
		||||
      ".cxx"       => "text/x-c",
 | 
			
		||||
      ".daf"       => "application/vnd.mobius.daf",
 | 
			
		||||
      ".davmount"  => "application/davmount+xml",
 | 
			
		||||
      ".dcr"       => "application/x-director",
 | 
			
		||||
      ".dd2"       => "application/vnd.oma.dd2+xml",
 | 
			
		||||
      ".ddd"       => "application/vnd.fujixerox.ddd",
 | 
			
		||||
      ".deb"       => "application/x-debian-package",
 | 
			
		||||
      ".der"       => "application/x-x509-ca-cert",
 | 
			
		||||
      ".dfac"      => "application/vnd.dreamfactory",
 | 
			
		||||
      ".diff"      => "text/x-diff",
 | 
			
		||||
      ".dis"       => "application/vnd.mobius.dis",
 | 
			
		||||
      ".djv"       => "image/vnd.djvu",
 | 
			
		||||
      ".djvu"      => "image/vnd.djvu",
 | 
			
		||||
      ".dll"       => "application/x-msdownload",
 | 
			
		||||
      ".dmg"       => "application/octet-stream",
 | 
			
		||||
      ".dna"       => "application/vnd.dna",
 | 
			
		||||
      ".doc"       => "application/msword",
 | 
			
		||||
      ".docm"      => "application/vnd.ms-word.document.macroEnabled.12",
 | 
			
		||||
      ".docx"      => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
 | 
			
		||||
      ".dot"       => "application/msword",
 | 
			
		||||
      ".dotm"      => "application/vnd.ms-word.template.macroEnabled.12",
 | 
			
		||||
      ".dotx"      => "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
 | 
			
		||||
      ".dp"        => "application/vnd.osgi.dp",
 | 
			
		||||
      ".dpg"       => "application/vnd.dpgraph",
 | 
			
		||||
      ".dsc"       => "text/prs.lines.tag",
 | 
			
		||||
      ".dtd"       => "application/xml-dtd",
 | 
			
		||||
      ".dts"       => "audio/vnd.dts",
 | 
			
		||||
      ".dtshd"     => "audio/vnd.dts.hd",
 | 
			
		||||
      ".dv"        => "video/x-dv",
 | 
			
		||||
      ".dvi"       => "application/x-dvi",
 | 
			
		||||
      ".dwf"       => "model/vnd.dwf",
 | 
			
		||||
      ".dwg"       => "image/vnd.dwg",
 | 
			
		||||
      ".dxf"       => "image/vnd.dxf",
 | 
			
		||||
      ".dxp"       => "application/vnd.spotfire.dxp",
 | 
			
		||||
      ".ear"       => "application/java-archive",
 | 
			
		||||
      ".ecelp4800" => "audio/vnd.nuera.ecelp4800",
 | 
			
		||||
      ".ecelp7470" => "audio/vnd.nuera.ecelp7470",
 | 
			
		||||
      ".ecelp9600" => "audio/vnd.nuera.ecelp9600",
 | 
			
		||||
      ".ecma"      => "application/ecmascript",
 | 
			
		||||
      ".edm"       => "application/vnd.novadigm.edm",
 | 
			
		||||
      ".edx"       => "application/vnd.novadigm.edx",
 | 
			
		||||
      ".efif"      => "application/vnd.picsel",
 | 
			
		||||
      ".ei6"       => "application/vnd.pg.osasli",
 | 
			
		||||
      ".eml"       => "message/rfc822",
 | 
			
		||||
      ".eol"       => "audio/vnd.digital-winds",
 | 
			
		||||
      ".eot"       => "application/vnd.ms-fontobject",
 | 
			
		||||
      ".eps"       => "application/postscript",
 | 
			
		||||
      ".es3"       => "application/vnd.eszigno3+xml",
 | 
			
		||||
      ".esf"       => "application/vnd.epson.esf",
 | 
			
		||||
      ".etx"       => "text/x-setext",
 | 
			
		||||
      ".exe"       => "application/x-msdownload",
 | 
			
		||||
      ".ext"       => "application/vnd.novadigm.ext",
 | 
			
		||||
      ".ez"        => "application/andrew-inset",
 | 
			
		||||
      ".ez2"       => "application/vnd.ezpix-album",
 | 
			
		||||
      ".ez3"       => "application/vnd.ezpix-package",
 | 
			
		||||
      ".f"         => "text/x-fortran",
 | 
			
		||||
      ".f77"       => "text/x-fortran",
 | 
			
		||||
      ".f90"       => "text/x-fortran",
 | 
			
		||||
      ".fbs"       => "image/vnd.fastbidsheet",
 | 
			
		||||
      ".fdf"       => "application/vnd.fdf",
 | 
			
		||||
      ".fe_launch" => "application/vnd.denovo.fcselayout-link",
 | 
			
		||||
      ".fg5"       => "application/vnd.fujitsu.oasysgp",
 | 
			
		||||
      ".fli"       => "video/x-fli",
 | 
			
		||||
      ".flif"      => "image/flif",
 | 
			
		||||
      ".flo"       => "application/vnd.micrografx.flo",
 | 
			
		||||
      ".flv"       => "video/x-flv",
 | 
			
		||||
      ".flw"       => "application/vnd.kde.kivio",
 | 
			
		||||
      ".flx"       => "text/vnd.fmi.flexstor",
 | 
			
		||||
      ".fly"       => "text/vnd.fly",
 | 
			
		||||
      ".fm"        => "application/vnd.framemaker",
 | 
			
		||||
      ".fnc"       => "application/vnd.frogans.fnc",
 | 
			
		||||
      ".for"       => "text/x-fortran",
 | 
			
		||||
      ".fpx"       => "image/vnd.fpx",
 | 
			
		||||
      ".fsc"       => "application/vnd.fsc.weblaunch",
 | 
			
		||||
      ".fst"       => "image/vnd.fst",
 | 
			
		||||
      ".ftc"       => "application/vnd.fluxtime.clip",
 | 
			
		||||
      ".fti"       => "application/vnd.anser-web-funds-transfer-initiation",
 | 
			
		||||
      ".fvt"       => "video/vnd.fvt",
 | 
			
		||||
      ".fzs"       => "application/vnd.fuzzysheet",
 | 
			
		||||
      ".g3"        => "image/g3fax",
 | 
			
		||||
      ".gac"       => "application/vnd.groove-account",
 | 
			
		||||
      ".gdl"       => "model/vnd.gdl",
 | 
			
		||||
      ".gem"       => "application/octet-stream",
 | 
			
		||||
      ".gemspec"   => "text/x-script.ruby",
 | 
			
		||||
      ".ghf"       => "application/vnd.groove-help",
 | 
			
		||||
      ".gif"       => "image/gif",
 | 
			
		||||
      ".gim"       => "application/vnd.groove-identity-message",
 | 
			
		||||
      ".gmx"       => "application/vnd.gmx",
 | 
			
		||||
      ".gph"       => "application/vnd.flographit",
 | 
			
		||||
      ".gqf"       => "application/vnd.grafeq",
 | 
			
		||||
      ".gram"      => "application/srgs",
 | 
			
		||||
      ".grv"       => "application/vnd.groove-injector",
 | 
			
		||||
      ".grxml"     => "application/srgs+xml",
 | 
			
		||||
      ".gtar"      => "application/x-gtar",
 | 
			
		||||
      ".gtm"       => "application/vnd.groove-tool-message",
 | 
			
		||||
      ".gtw"       => "model/vnd.gtw",
 | 
			
		||||
      ".gv"        => "text/vnd.graphviz",
 | 
			
		||||
      ".gz"        => "application/x-gzip",
 | 
			
		||||
      ".h"         => "text/x-c",
 | 
			
		||||
      ".h261"      => "video/h261",
 | 
			
		||||
      ".h263"      => "video/h263",
 | 
			
		||||
      ".h264"      => "video/h264",
 | 
			
		||||
      ".hbci"      => "application/vnd.hbci",
 | 
			
		||||
      ".hdf"       => "application/x-hdf",
 | 
			
		||||
      ".heic"      => "image/heic",
 | 
			
		||||
      ".heics"     => "image/heic-sequence",
 | 
			
		||||
      ".heif"      => "image/heif",
 | 
			
		||||
      ".heifs"     => "image/heif-sequence",
 | 
			
		||||
      ".hh"        => "text/x-c",
 | 
			
		||||
      ".hlp"       => "application/winhlp",
 | 
			
		||||
      ".hpgl"      => "application/vnd.hp-hpgl",
 | 
			
		||||
      ".hpid"      => "application/vnd.hp-hpid",
 | 
			
		||||
      ".hps"       => "application/vnd.hp-hps",
 | 
			
		||||
      ".hqx"       => "application/mac-binhex40",
 | 
			
		||||
      ".htc"       => "text/x-component",
 | 
			
		||||
      ".htke"      => "application/vnd.kenameaapp",
 | 
			
		||||
      ".htm"       => "text/html",
 | 
			
		||||
      ".html"      => "text/html",
 | 
			
		||||
      ".hvd"       => "application/vnd.yamaha.hv-dic",
 | 
			
		||||
      ".hvp"       => "application/vnd.yamaha.hv-voice",
 | 
			
		||||
      ".hvs"       => "application/vnd.yamaha.hv-script",
 | 
			
		||||
      ".icc"       => "application/vnd.iccprofile",
 | 
			
		||||
      ".ice"       => "x-conference/x-cooltalk",
 | 
			
		||||
      ".ico"       => "image/vnd.microsoft.icon",
 | 
			
		||||
      ".ics"       => "text/calendar",
 | 
			
		||||
      ".ief"       => "image/ief",
 | 
			
		||||
      ".ifb"       => "text/calendar",
 | 
			
		||||
      ".ifm"       => "application/vnd.shana.informed.formdata",
 | 
			
		||||
      ".igl"       => "application/vnd.igloader",
 | 
			
		||||
      ".igs"       => "model/iges",
 | 
			
		||||
      ".igx"       => "application/vnd.micrografx.igx",
 | 
			
		||||
      ".iif"       => "application/vnd.shana.informed.interchange",
 | 
			
		||||
      ".imp"       => "application/vnd.accpac.simply.imp",
 | 
			
		||||
      ".ims"       => "application/vnd.ms-ims",
 | 
			
		||||
      ".ipk"       => "application/vnd.shana.informed.package",
 | 
			
		||||
      ".irm"       => "application/vnd.ibm.rights-management",
 | 
			
		||||
      ".irp"       => "application/vnd.irepository.package+xml",
 | 
			
		||||
      ".iso"       => "application/octet-stream",
 | 
			
		||||
      ".itp"       => "application/vnd.shana.informed.formtemplate",
 | 
			
		||||
      ".ivp"       => "application/vnd.immervision-ivp",
 | 
			
		||||
      ".ivu"       => "application/vnd.immervision-ivu",
 | 
			
		||||
      ".jad"       => "text/vnd.sun.j2me.app-descriptor",
 | 
			
		||||
      ".jam"       => "application/vnd.jam",
 | 
			
		||||
      ".jar"       => "application/java-archive",
 | 
			
		||||
      ".java"      => "text/x-java-source",
 | 
			
		||||
      ".jisp"      => "application/vnd.jisp",
 | 
			
		||||
      ".jlt"       => "application/vnd.hp-jlyt",
 | 
			
		||||
      ".jnlp"      => "application/x-java-jnlp-file",
 | 
			
		||||
      ".joda"      => "application/vnd.joost.joda-archive",
 | 
			
		||||
      ".jp2"       => "image/jp2",
 | 
			
		||||
      ".jpeg"      => "image/jpeg",
 | 
			
		||||
      ".jpg"       => "image/jpeg",
 | 
			
		||||
      ".jpgv"      => "video/jpeg",
 | 
			
		||||
      ".jpm"       => "video/jpm",
 | 
			
		||||
      ".js"        => "application/javascript",
 | 
			
		||||
      ".json"      => "application/json",
 | 
			
		||||
      ".karbon"    => "application/vnd.kde.karbon",
 | 
			
		||||
      ".kfo"       => "application/vnd.kde.kformula",
 | 
			
		||||
      ".kia"       => "application/vnd.kidspiration",
 | 
			
		||||
      ".kml"       => "application/vnd.google-earth.kml+xml",
 | 
			
		||||
      ".kmz"       => "application/vnd.google-earth.kmz",
 | 
			
		||||
      ".kne"       => "application/vnd.kinar",
 | 
			
		||||
      ".kon"       => "application/vnd.kde.kontour",
 | 
			
		||||
      ".kpr"       => "application/vnd.kde.kpresenter",
 | 
			
		||||
      ".ksp"       => "application/vnd.kde.kspread",
 | 
			
		||||
      ".ktz"       => "application/vnd.kahootz",
 | 
			
		||||
      ".kwd"       => "application/vnd.kde.kword",
 | 
			
		||||
      ".latex"     => "application/x-latex",
 | 
			
		||||
      ".lbd"       => "application/vnd.llamagraphics.life-balance.desktop",
 | 
			
		||||
      ".lbe"       => "application/vnd.llamagraphics.life-balance.exchange+xml",
 | 
			
		||||
      ".les"       => "application/vnd.hhe.lesson-player",
 | 
			
		||||
      ".link66"    => "application/vnd.route66.link66+xml",
 | 
			
		||||
      ".log"       => "text/plain",
 | 
			
		||||
      ".lostxml"   => "application/lost+xml",
 | 
			
		||||
      ".lrm"       => "application/vnd.ms-lrm",
 | 
			
		||||
      ".ltf"       => "application/vnd.frogans.ltf",
 | 
			
		||||
      ".lvp"       => "audio/vnd.lucent.voice",
 | 
			
		||||
      ".lwp"       => "application/vnd.lotus-wordpro",
 | 
			
		||||
      ".m3u"       => "audio/x-mpegurl",
 | 
			
		||||
      ".m3u8"      => "application/x-mpegurl",
 | 
			
		||||
      ".m4a"       => "audio/mp4a-latm",
 | 
			
		||||
      ".m4v"       => "video/mp4",
 | 
			
		||||
      ".ma"        => "application/mathematica",
 | 
			
		||||
      ".mag"       => "application/vnd.ecowin.chart",
 | 
			
		||||
      ".man"       => "text/troff",
 | 
			
		||||
      ".manifest"  => "text/cache-manifest",
 | 
			
		||||
      ".mathml"    => "application/mathml+xml",
 | 
			
		||||
      ".mbk"       => "application/vnd.mobius.mbk",
 | 
			
		||||
      ".mbox"      => "application/mbox",
 | 
			
		||||
      ".mc1"       => "application/vnd.medcalcdata",
 | 
			
		||||
      ".mcd"       => "application/vnd.mcd",
 | 
			
		||||
      ".mdb"       => "application/x-msaccess",
 | 
			
		||||
      ".mdi"       => "image/vnd.ms-modi",
 | 
			
		||||
      ".mdoc"      => "text/troff",
 | 
			
		||||
      ".me"        => "text/troff",
 | 
			
		||||
      ".mfm"       => "application/vnd.mfmp",
 | 
			
		||||
      ".mgz"       => "application/vnd.proteus.magazine",
 | 
			
		||||
      ".mid"       => "audio/midi",
 | 
			
		||||
      ".midi"      => "audio/midi",
 | 
			
		||||
      ".mif"       => "application/vnd.mif",
 | 
			
		||||
      ".mime"      => "message/rfc822",
 | 
			
		||||
      ".mj2"       => "video/mj2",
 | 
			
		||||
      ".mlp"       => "application/vnd.dolby.mlp",
 | 
			
		||||
      ".mmd"       => "application/vnd.chipnuts.karaoke-mmd",
 | 
			
		||||
      ".mmf"       => "application/vnd.smaf",
 | 
			
		||||
      ".mml"       => "application/mathml+xml",
 | 
			
		||||
      ".mmr"       => "image/vnd.fujixerox.edmics-mmr",
 | 
			
		||||
      ".mng"       => "video/x-mng",
 | 
			
		||||
      ".mny"       => "application/x-msmoney",
 | 
			
		||||
      ".mov"       => "video/quicktime",
 | 
			
		||||
      ".movie"     => "video/x-sgi-movie",
 | 
			
		||||
      ".mp3"       => "audio/mpeg",
 | 
			
		||||
      ".mp4"       => "video/mp4",
 | 
			
		||||
      ".mp4a"      => "audio/mp4",
 | 
			
		||||
      ".mp4s"      => "application/mp4",
 | 
			
		||||
      ".mp4v"      => "video/mp4",
 | 
			
		||||
      ".mpc"       => "application/vnd.mophun.certificate",
 | 
			
		||||
      ".mpd"       => "application/dash+xml",
 | 
			
		||||
      ".mpeg"      => "video/mpeg",
 | 
			
		||||
      ".mpg"       => "video/mpeg",
 | 
			
		||||
      ".mpga"      => "audio/mpeg",
 | 
			
		||||
      ".mpkg"      => "application/vnd.apple.installer+xml",
 | 
			
		||||
      ".mpm"       => "application/vnd.blueice.multipass",
 | 
			
		||||
      ".mpn"       => "application/vnd.mophun.application",
 | 
			
		||||
      ".mpp"       => "application/vnd.ms-project",
 | 
			
		||||
      ".mpy"       => "application/vnd.ibm.minipay",
 | 
			
		||||
      ".mqy"       => "application/vnd.mobius.mqy",
 | 
			
		||||
      ".mrc"       => "application/marc",
 | 
			
		||||
      ".ms"        => "text/troff",
 | 
			
		||||
      ".mscml"     => "application/mediaservercontrol+xml",
 | 
			
		||||
      ".mseq"      => "application/vnd.mseq",
 | 
			
		||||
      ".msf"       => "application/vnd.epson.msf",
 | 
			
		||||
      ".msh"       => "model/mesh",
 | 
			
		||||
      ".msi"       => "application/x-msdownload",
 | 
			
		||||
      ".msl"       => "application/vnd.mobius.msl",
 | 
			
		||||
      ".msty"      => "application/vnd.muvee.style",
 | 
			
		||||
      ".mts"       => "model/vnd.mts",
 | 
			
		||||
      ".mus"       => "application/vnd.musician",
 | 
			
		||||
      ".mvb"       => "application/x-msmediaview",
 | 
			
		||||
      ".mwf"       => "application/vnd.mfer",
 | 
			
		||||
      ".mxf"       => "application/mxf",
 | 
			
		||||
      ".mxl"       => "application/vnd.recordare.musicxml",
 | 
			
		||||
      ".mxml"      => "application/xv+xml",
 | 
			
		||||
      ".mxs"       => "application/vnd.triscape.mxs",
 | 
			
		||||
      ".mxu"       => "video/vnd.mpegurl",
 | 
			
		||||
      ".n"         => "application/vnd.nokia.n-gage.symbian.install",
 | 
			
		||||
      ".nc"        => "application/x-netcdf",
 | 
			
		||||
      ".ngdat"     => "application/vnd.nokia.n-gage.data",
 | 
			
		||||
      ".nlu"       => "application/vnd.neurolanguage.nlu",
 | 
			
		||||
      ".nml"       => "application/vnd.enliven",
 | 
			
		||||
      ".nnd"       => "application/vnd.noblenet-directory",
 | 
			
		||||
      ".nns"       => "application/vnd.noblenet-sealer",
 | 
			
		||||
      ".nnw"       => "application/vnd.noblenet-web",
 | 
			
		||||
      ".npx"       => "image/vnd.net-fpx",
 | 
			
		||||
      ".nsf"       => "application/vnd.lotus-notes",
 | 
			
		||||
      ".oa2"       => "application/vnd.fujitsu.oasys2",
 | 
			
		||||
      ".oa3"       => "application/vnd.fujitsu.oasys3",
 | 
			
		||||
      ".oas"       => "application/vnd.fujitsu.oasys",
 | 
			
		||||
      ".obd"       => "application/x-msbinder",
 | 
			
		||||
      ".oda"       => "application/oda",
 | 
			
		||||
      ".odc"       => "application/vnd.oasis.opendocument.chart",
 | 
			
		||||
      ".odf"       => "application/vnd.oasis.opendocument.formula",
 | 
			
		||||
      ".odg"       => "application/vnd.oasis.opendocument.graphics",
 | 
			
		||||
      ".odi"       => "application/vnd.oasis.opendocument.image",
 | 
			
		||||
      ".odp"       => "application/vnd.oasis.opendocument.presentation",
 | 
			
		||||
      ".ods"       => "application/vnd.oasis.opendocument.spreadsheet",
 | 
			
		||||
      ".odt"       => "application/vnd.oasis.opendocument.text",
 | 
			
		||||
      ".oga"       => "audio/ogg",
 | 
			
		||||
      ".ogg"       => "application/ogg",
 | 
			
		||||
      ".ogv"       => "video/ogg",
 | 
			
		||||
      ".ogx"       => "application/ogg",
 | 
			
		||||
      ".org"       => "application/vnd.lotus-organizer",
 | 
			
		||||
      ".otc"       => "application/vnd.oasis.opendocument.chart-template",
 | 
			
		||||
      ".otf"       => "application/vnd.oasis.opendocument.formula-template",
 | 
			
		||||
      ".otg"       => "application/vnd.oasis.opendocument.graphics-template",
 | 
			
		||||
      ".oth"       => "application/vnd.oasis.opendocument.text-web",
 | 
			
		||||
      ".oti"       => "application/vnd.oasis.opendocument.image-template",
 | 
			
		||||
      ".otm"       => "application/vnd.oasis.opendocument.text-master",
 | 
			
		||||
      ".ots"       => "application/vnd.oasis.opendocument.spreadsheet-template",
 | 
			
		||||
      ".ott"       => "application/vnd.oasis.opendocument.text-template",
 | 
			
		||||
      ".oxt"       => "application/vnd.openofficeorg.extension",
 | 
			
		||||
      ".p"         => "text/x-pascal",
 | 
			
		||||
      ".p10"       => "application/pkcs10",
 | 
			
		||||
      ".p12"       => "application/x-pkcs12",
 | 
			
		||||
      ".p7b"       => "application/x-pkcs7-certificates",
 | 
			
		||||
      ".p7m"       => "application/pkcs7-mime",
 | 
			
		||||
      ".p7r"       => "application/x-pkcs7-certreqresp",
 | 
			
		||||
      ".p7s"       => "application/pkcs7-signature",
 | 
			
		||||
      ".pas"       => "text/x-pascal",
 | 
			
		||||
      ".pbd"       => "application/vnd.powerbuilder6",
 | 
			
		||||
      ".pbm"       => "image/x-portable-bitmap",
 | 
			
		||||
      ".pcl"       => "application/vnd.hp-pcl",
 | 
			
		||||
      ".pclxl"     => "application/vnd.hp-pclxl",
 | 
			
		||||
      ".pcx"       => "image/x-pcx",
 | 
			
		||||
      ".pdb"       => "chemical/x-pdb",
 | 
			
		||||
      ".pdf"       => "application/pdf",
 | 
			
		||||
      ".pem"       => "application/x-x509-ca-cert",
 | 
			
		||||
      ".pfr"       => "application/font-tdpfr",
 | 
			
		||||
      ".pgm"       => "image/x-portable-graymap",
 | 
			
		||||
      ".pgn"       => "application/x-chess-pgn",
 | 
			
		||||
      ".pgp"       => "application/pgp-encrypted",
 | 
			
		||||
      ".pic"       => "image/x-pict",
 | 
			
		||||
      ".pict"      => "image/pict",
 | 
			
		||||
      ".pkg"       => "application/octet-stream",
 | 
			
		||||
      ".pki"       => "application/pkixcmp",
 | 
			
		||||
      ".pkipath"   => "application/pkix-pkipath",
 | 
			
		||||
      ".pl"        => "text/x-script.perl",
 | 
			
		||||
      ".plb"       => "application/vnd.3gpp.pic-bw-large",
 | 
			
		||||
      ".plc"       => "application/vnd.mobius.plc",
 | 
			
		||||
      ".plf"       => "application/vnd.pocketlearn",
 | 
			
		||||
      ".pls"       => "application/pls+xml",
 | 
			
		||||
      ".pm"        => "text/x-script.perl-module",
 | 
			
		||||
      ".pml"       => "application/vnd.ctc-posml",
 | 
			
		||||
      ".png"       => "image/png",
 | 
			
		||||
      ".pnm"       => "image/x-portable-anymap",
 | 
			
		||||
      ".pntg"      => "image/x-macpaint",
 | 
			
		||||
      ".portpkg"   => "application/vnd.macports.portpkg",
 | 
			
		||||
      ".pot"       => "application/vnd.ms-powerpoint",
 | 
			
		||||
      ".potm"      => "application/vnd.ms-powerpoint.template.macroEnabled.12",
 | 
			
		||||
      ".potx"      => "application/vnd.openxmlformats-officedocument.presentationml.template",
 | 
			
		||||
      ".ppa"       => "application/vnd.ms-powerpoint",
 | 
			
		||||
      ".ppam"      => "application/vnd.ms-powerpoint.addin.macroEnabled.12",
 | 
			
		||||
      ".ppd"       => "application/vnd.cups-ppd",
 | 
			
		||||
      ".ppm"       => "image/x-portable-pixmap",
 | 
			
		||||
      ".pps"       => "application/vnd.ms-powerpoint",
 | 
			
		||||
      ".ppsm"      => "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
 | 
			
		||||
      ".ppsx"      => "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
 | 
			
		||||
      ".ppt"       => "application/vnd.ms-powerpoint",
 | 
			
		||||
      ".pptm"      => "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
 | 
			
		||||
      ".pptx"      => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
 | 
			
		||||
      ".prc"       => "application/vnd.palm",
 | 
			
		||||
      ".pre"       => "application/vnd.lotus-freelance",
 | 
			
		||||
      ".prf"       => "application/pics-rules",
 | 
			
		||||
      ".ps"        => "application/postscript",
 | 
			
		||||
      ".psb"       => "application/vnd.3gpp.pic-bw-small",
 | 
			
		||||
      ".psd"       => "image/vnd.adobe.photoshop",
 | 
			
		||||
      ".ptid"      => "application/vnd.pvi.ptid1",
 | 
			
		||||
      ".pub"       => "application/x-mspublisher",
 | 
			
		||||
      ".pvb"       => "application/vnd.3gpp.pic-bw-var",
 | 
			
		||||
      ".pwn"       => "application/vnd.3m.post-it-notes",
 | 
			
		||||
      ".py"        => "text/x-script.python",
 | 
			
		||||
      ".pya"       => "audio/vnd.ms-playready.media.pya",
 | 
			
		||||
      ".pyv"       => "video/vnd.ms-playready.media.pyv",
 | 
			
		||||
      ".qam"       => "application/vnd.epson.quickanime",
 | 
			
		||||
      ".qbo"       => "application/vnd.intu.qbo",
 | 
			
		||||
      ".qfx"       => "application/vnd.intu.qfx",
 | 
			
		||||
      ".qps"       => "application/vnd.publishare-delta-tree",
 | 
			
		||||
      ".qt"        => "video/quicktime",
 | 
			
		||||
      ".qtif"      => "image/x-quicktime",
 | 
			
		||||
      ".qxd"       => "application/vnd.quark.quarkxpress",
 | 
			
		||||
      ".ra"        => "audio/x-pn-realaudio",
 | 
			
		||||
      ".rake"      => "text/x-script.ruby",
 | 
			
		||||
      ".ram"       => "audio/x-pn-realaudio",
 | 
			
		||||
      ".rar"       => "application/x-rar-compressed",
 | 
			
		||||
      ".ras"       => "image/x-cmu-raster",
 | 
			
		||||
      ".rb"        => "text/x-script.ruby",
 | 
			
		||||
      ".rcprofile" => "application/vnd.ipunplugged.rcprofile",
 | 
			
		||||
      ".rdf"       => "application/rdf+xml",
 | 
			
		||||
      ".rdz"       => "application/vnd.data-vision.rdz",
 | 
			
		||||
      ".rep"       => "application/vnd.businessobjects",
 | 
			
		||||
      ".rgb"       => "image/x-rgb",
 | 
			
		||||
      ".rif"       => "application/reginfo+xml",
 | 
			
		||||
      ".rl"        => "application/resource-lists+xml",
 | 
			
		||||
      ".rlc"       => "image/vnd.fujixerox.edmics-rlc",
 | 
			
		||||
      ".rld"       => "application/resource-lists-diff+xml",
 | 
			
		||||
      ".rm"        => "application/vnd.rn-realmedia",
 | 
			
		||||
      ".rmp"       => "audio/x-pn-realaudio-plugin",
 | 
			
		||||
      ".rms"       => "application/vnd.jcp.javame.midlet-rms",
 | 
			
		||||
      ".rnc"       => "application/relax-ng-compact-syntax",
 | 
			
		||||
      ".roff"      => "text/troff",
 | 
			
		||||
      ".rpm"       => "application/x-redhat-package-manager",
 | 
			
		||||
      ".rpss"      => "application/vnd.nokia.radio-presets",
 | 
			
		||||
      ".rpst"      => "application/vnd.nokia.radio-preset",
 | 
			
		||||
      ".rq"        => "application/sparql-query",
 | 
			
		||||
      ".rs"        => "application/rls-services+xml",
 | 
			
		||||
      ".rsd"       => "application/rsd+xml",
 | 
			
		||||
      ".rss"       => "application/rss+xml",
 | 
			
		||||
      ".rtf"       => "application/rtf",
 | 
			
		||||
      ".rtx"       => "text/richtext",
 | 
			
		||||
      ".ru"        => "text/x-script.ruby",
 | 
			
		||||
      ".s"         => "text/x-asm",
 | 
			
		||||
      ".saf"       => "application/vnd.yamaha.smaf-audio",
 | 
			
		||||
      ".sbml"      => "application/sbml+xml",
 | 
			
		||||
      ".sc"        => "application/vnd.ibm.secure-container",
 | 
			
		||||
      ".scd"       => "application/x-msschedule",
 | 
			
		||||
      ".scm"       => "application/vnd.lotus-screencam",
 | 
			
		||||
      ".scq"       => "application/scvp-cv-request",
 | 
			
		||||
      ".scs"       => "application/scvp-cv-response",
 | 
			
		||||
      ".sdkm"      => "application/vnd.solent.sdkm+xml",
 | 
			
		||||
      ".sdp"       => "application/sdp",
 | 
			
		||||
      ".see"       => "application/vnd.seemail",
 | 
			
		||||
      ".sema"      => "application/vnd.sema",
 | 
			
		||||
      ".semd"      => "application/vnd.semd",
 | 
			
		||||
      ".semf"      => "application/vnd.semf",
 | 
			
		||||
      ".setpay"    => "application/set-payment-initiation",
 | 
			
		||||
      ".setreg"    => "application/set-registration-initiation",
 | 
			
		||||
      ".sfd"       => "application/vnd.hydrostatix.sof-data",
 | 
			
		||||
      ".sfs"       => "application/vnd.spotfire.sfs",
 | 
			
		||||
      ".sgm"       => "text/sgml",
 | 
			
		||||
      ".sgml"      => "text/sgml",
 | 
			
		||||
      ".sh"        => "application/x-sh",
 | 
			
		||||
      ".shar"      => "application/x-shar",
 | 
			
		||||
      ".shf"       => "application/shf+xml",
 | 
			
		||||
      ".sig"       => "application/pgp-signature",
 | 
			
		||||
      ".sit"       => "application/x-stuffit",
 | 
			
		||||
      ".sitx"      => "application/x-stuffitx",
 | 
			
		||||
      ".skp"       => "application/vnd.koan",
 | 
			
		||||
      ".slt"       => "application/vnd.epson.salt",
 | 
			
		||||
      ".smi"       => "application/smil+xml",
 | 
			
		||||
      ".snd"       => "audio/basic",
 | 
			
		||||
      ".so"        => "application/octet-stream",
 | 
			
		||||
      ".spf"       => "application/vnd.yamaha.smaf-phrase",
 | 
			
		||||
      ".spl"       => "application/x-futuresplash",
 | 
			
		||||
      ".spot"      => "text/vnd.in3d.spot",
 | 
			
		||||
      ".spp"       => "application/scvp-vp-response",
 | 
			
		||||
      ".spq"       => "application/scvp-vp-request",
 | 
			
		||||
      ".src"       => "application/x-wais-source",
 | 
			
		||||
      ".srt"       => "text/srt",
 | 
			
		||||
      ".srx"       => "application/sparql-results+xml",
 | 
			
		||||
      ".sse"       => "application/vnd.kodak-descriptor",
 | 
			
		||||
      ".ssf"       => "application/vnd.epson.ssf",
 | 
			
		||||
      ".ssml"      => "application/ssml+xml",
 | 
			
		||||
      ".stf"       => "application/vnd.wt.stf",
 | 
			
		||||
      ".stk"       => "application/hyperstudio",
 | 
			
		||||
      ".str"       => "application/vnd.pg.format",
 | 
			
		||||
      ".sus"       => "application/vnd.sus-calendar",
 | 
			
		||||
      ".sv4cpio"   => "application/x-sv4cpio",
 | 
			
		||||
      ".sv4crc"    => "application/x-sv4crc",
 | 
			
		||||
      ".svd"       => "application/vnd.svd",
 | 
			
		||||
      ".svg"       => "image/svg+xml",
 | 
			
		||||
      ".svgz"      => "image/svg+xml",
 | 
			
		||||
      ".swf"       => "application/x-shockwave-flash",
 | 
			
		||||
      ".swi"       => "application/vnd.arastra.swi",
 | 
			
		||||
      ".t"         => "text/troff",
 | 
			
		||||
      ".tao"       => "application/vnd.tao.intent-module-archive",
 | 
			
		||||
      ".tar"       => "application/x-tar",
 | 
			
		||||
      ".tbz"       => "application/x-bzip-compressed-tar",
 | 
			
		||||
      ".tcap"      => "application/vnd.3gpp2.tcap",
 | 
			
		||||
      ".tcl"       => "application/x-tcl",
 | 
			
		||||
      ".tex"       => "application/x-tex",
 | 
			
		||||
      ".texi"      => "application/x-texinfo",
 | 
			
		||||
      ".texinfo"   => "application/x-texinfo",
 | 
			
		||||
      ".text"      => "text/plain",
 | 
			
		||||
      ".tif"       => "image/tiff",
 | 
			
		||||
      ".tiff"      => "image/tiff",
 | 
			
		||||
      ".tmo"       => "application/vnd.tmobile-livetv",
 | 
			
		||||
      ".torrent"   => "application/x-bittorrent",
 | 
			
		||||
      ".tpl"       => "application/vnd.groove-tool-template",
 | 
			
		||||
      ".tpt"       => "application/vnd.trid.tpt",
 | 
			
		||||
      ".tr"        => "text/troff",
 | 
			
		||||
      ".tra"       => "application/vnd.trueapp",
 | 
			
		||||
      ".trm"       => "application/x-msterminal",
 | 
			
		||||
      ".ts"        => "video/mp2t",
 | 
			
		||||
      ".tsv"       => "text/tab-separated-values",
 | 
			
		||||
      ".ttf"       => "application/octet-stream",
 | 
			
		||||
      ".twd"       => "application/vnd.simtech-mindmapper",
 | 
			
		||||
      ".txd"       => "application/vnd.genomatix.tuxedo",
 | 
			
		||||
      ".txf"       => "application/vnd.mobius.txf",
 | 
			
		||||
      ".txt"       => "text/plain",
 | 
			
		||||
      ".ufd"       => "application/vnd.ufdl",
 | 
			
		||||
      ".umj"       => "application/vnd.umajin",
 | 
			
		||||
      ".unityweb"  => "application/vnd.unity",
 | 
			
		||||
      ".uoml"      => "application/vnd.uoml+xml",
 | 
			
		||||
      ".uri"       => "text/uri-list",
 | 
			
		||||
      ".ustar"     => "application/x-ustar",
 | 
			
		||||
      ".utz"       => "application/vnd.uiq.theme",
 | 
			
		||||
      ".uu"        => "text/x-uuencode",
 | 
			
		||||
      ".vcd"       => "application/x-cdlink",
 | 
			
		||||
      ".vcf"       => "text/x-vcard",
 | 
			
		||||
      ".vcg"       => "application/vnd.groove-vcard",
 | 
			
		||||
      ".vcs"       => "text/x-vcalendar",
 | 
			
		||||
      ".vcx"       => "application/vnd.vcx",
 | 
			
		||||
      ".vis"       => "application/vnd.visionary",
 | 
			
		||||
      ".viv"       => "video/vnd.vivo",
 | 
			
		||||
      ".vrml"      => "model/vrml",
 | 
			
		||||
      ".vsd"       => "application/vnd.visio",
 | 
			
		||||
      ".vsf"       => "application/vnd.vsf",
 | 
			
		||||
      ".vtt"       => "text/vtt",
 | 
			
		||||
      ".vtu"       => "model/vnd.vtu",
 | 
			
		||||
      ".vxml"      => "application/voicexml+xml",
 | 
			
		||||
      ".war"       => "application/java-archive",
 | 
			
		||||
      ".wasm"      => "application/wasm",
 | 
			
		||||
      ".wav"       => "audio/x-wav",
 | 
			
		||||
      ".wax"       => "audio/x-ms-wax",
 | 
			
		||||
      ".wbmp"      => "image/vnd.wap.wbmp",
 | 
			
		||||
      ".wbs"       => "application/vnd.criticaltools.wbs+xml",
 | 
			
		||||
      ".wbxml"     => "application/vnd.wap.wbxml",
 | 
			
		||||
      ".webm"      => "video/webm",
 | 
			
		||||
      ".webp"      => "image/webp",
 | 
			
		||||
      ".wm"        => "video/x-ms-wm",
 | 
			
		||||
      ".wma"       => "audio/x-ms-wma",
 | 
			
		||||
      ".wmd"       => "application/x-ms-wmd",
 | 
			
		||||
      ".wmf"       => "application/x-msmetafile",
 | 
			
		||||
      ".wml"       => "text/vnd.wap.wml",
 | 
			
		||||
      ".wmlc"      => "application/vnd.wap.wmlc",
 | 
			
		||||
      ".wmls"      => "text/vnd.wap.wmlscript",
 | 
			
		||||
      ".wmlsc"     => "application/vnd.wap.wmlscriptc",
 | 
			
		||||
      ".wmv"       => "video/x-ms-wmv",
 | 
			
		||||
      ".wmx"       => "video/x-ms-wmx",
 | 
			
		||||
      ".wmz"       => "application/x-ms-wmz",
 | 
			
		||||
      ".woff"      => "application/font-woff",
 | 
			
		||||
      ".woff2"     => "application/font-woff2",
 | 
			
		||||
      ".wpd"       => "application/vnd.wordperfect",
 | 
			
		||||
      ".wpl"       => "application/vnd.ms-wpl",
 | 
			
		||||
      ".wps"       => "application/vnd.ms-works",
 | 
			
		||||
      ".wqd"       => "application/vnd.wqd",
 | 
			
		||||
      ".wri"       => "application/x-mswrite",
 | 
			
		||||
      ".wrl"       => "model/vrml",
 | 
			
		||||
      ".wsdl"      => "application/wsdl+xml",
 | 
			
		||||
      ".wspolicy"  => "application/wspolicy+xml",
 | 
			
		||||
      ".wtb"       => "application/vnd.webturbo",
 | 
			
		||||
      ".wvx"       => "video/x-ms-wvx",
 | 
			
		||||
      ".x3d"       => "application/vnd.hzn-3d-crossword",
 | 
			
		||||
      ".xar"       => "application/vnd.xara",
 | 
			
		||||
      ".xbd"       => "application/vnd.fujixerox.docuworks.binder",
 | 
			
		||||
      ".xbm"       => "image/x-xbitmap",
 | 
			
		||||
      ".xdm"       => "application/vnd.syncml.dm+xml",
 | 
			
		||||
      ".xdp"       => "application/vnd.adobe.xdp+xml",
 | 
			
		||||
      ".xdw"       => "application/vnd.fujixerox.docuworks",
 | 
			
		||||
      ".xenc"      => "application/xenc+xml",
 | 
			
		||||
      ".xer"       => "application/patch-ops-error+xml",
 | 
			
		||||
      ".xfdf"      => "application/vnd.adobe.xfdf",
 | 
			
		||||
      ".xfdl"      => "application/vnd.xfdl",
 | 
			
		||||
      ".xhtml"     => "application/xhtml+xml",
 | 
			
		||||
      ".xif"       => "image/vnd.xiff",
 | 
			
		||||
      ".xla"       => "application/vnd.ms-excel",
 | 
			
		||||
      ".xlam"      => "application/vnd.ms-excel.addin.macroEnabled.12",
 | 
			
		||||
      ".xls"       => "application/vnd.ms-excel",
 | 
			
		||||
      ".xlsb"      => "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
 | 
			
		||||
      ".xlsx"      => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 | 
			
		||||
      ".xlsm"      => "application/vnd.ms-excel.sheet.macroEnabled.12",
 | 
			
		||||
      ".xlt"       => "application/vnd.ms-excel",
 | 
			
		||||
      ".xltx"      => "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
 | 
			
		||||
      ".xml"       => "application/xml",
 | 
			
		||||
      ".xo"        => "application/vnd.olpc-sugar",
 | 
			
		||||
      ".xop"       => "application/xop+xml",
 | 
			
		||||
      ".xpm"       => "image/x-xpixmap",
 | 
			
		||||
      ".xpr"       => "application/vnd.is-xpr",
 | 
			
		||||
      ".xps"       => "application/vnd.ms-xpsdocument",
 | 
			
		||||
      ".xpw"       => "application/vnd.intercon.formnet",
 | 
			
		||||
      ".xsl"       => "application/xml",
 | 
			
		||||
      ".xslt"      => "application/xslt+xml",
 | 
			
		||||
      ".xsm"       => "application/vnd.syncml+xml",
 | 
			
		||||
      ".xspf"      => "application/xspf+xml",
 | 
			
		||||
      ".xul"       => "application/vnd.mozilla.xul+xml",
 | 
			
		||||
      ".xwd"       => "image/x-xwindowdump",
 | 
			
		||||
      ".xyz"       => "chemical/x-xyz",
 | 
			
		||||
      ".yaml"      => "text/yaml",
 | 
			
		||||
      ".yml"       => "text/yaml",
 | 
			
		||||
      ".zaz"       => "application/vnd.zzazz.deck+xml",
 | 
			
		||||
      ".zip"       => "application/zip",
 | 
			
		||||
      ".zmm"       => "application/vnd.handheld-entertainment+xml",
 | 
			
		||||
    }
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,3 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require_relative 'mock_request'
 | 
			
		||||
@ -1,166 +0,0 @@
 | 
			
		||||
# 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.
 | 
			
		||||
  # <tt>:input</tt>:: A String or IO-like to be used as rack.input.
 | 
			
		||||
  # <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
 | 
			
		||||
  # <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
 | 
			
		||||
 | 
			
		||||
  class MockRequest
 | 
			
		||||
    class FatalWarning < RuntimeError
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    class FatalWarner
 | 
			
		||||
      def puts(warning)
 | 
			
		||||
        raise FatalWarning, warning
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def write(warning)
 | 
			
		||||
        raise FatalWarning, warning
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def flush
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def string
 | 
			
		||||
        ""
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    DEFAULT_ENV = {
 | 
			
		||||
      RACK_INPUT        => StringIO.new,
 | 
			
		||||
      RACK_ERRORS       => StringIO.new,
 | 
			
		||||
    }.freeze
 | 
			
		||||
 | 
			
		||||
    def initialize(app)
 | 
			
		||||
      @app = app
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Make a GET request and return a MockResponse. See #request.
 | 
			
		||||
    def get(uri, opts = {})     request(GET, uri, opts)     end
 | 
			
		||||
    # Make a POST request and return a MockResponse. See #request.
 | 
			
		||||
    def post(uri, opts = {})    request(POST, uri, opts)    end
 | 
			
		||||
    # Make a PUT request and return a MockResponse. See #request.
 | 
			
		||||
    def put(uri, opts = {})     request(PUT, uri, opts)     end
 | 
			
		||||
    # Make a PATCH request and return a MockResponse. See #request.
 | 
			
		||||
    def patch(uri, opts = {})   request(PATCH, uri, opts)   end
 | 
			
		||||
    # Make a DELETE request and return a MockResponse. See #request.
 | 
			
		||||
    def delete(uri, opts = {})  request(DELETE, uri, opts)  end
 | 
			
		||||
    # Make a HEAD request and return a MockResponse. See #request.
 | 
			
		||||
    def head(uri, opts = {})    request(HEAD, uri, opts)    end
 | 
			
		||||
    # Make an OPTIONS request and return a MockResponse. See #request.
 | 
			
		||||
    def options(uri, opts = {}) request(OPTIONS, uri, opts) end
 | 
			
		||||
 | 
			
		||||
    # Make a request using the given request method for the given
 | 
			
		||||
    # uri to the rack application and return a MockResponse.
 | 
			
		||||
    # Options given are passed to MockRequest.env_for.
 | 
			
		||||
    def request(method = GET, uri = "", opts = {})
 | 
			
		||||
      env = self.class.env_for(uri, opts.merge(method: method))
 | 
			
		||||
 | 
			
		||||
      if opts[:lint]
 | 
			
		||||
        app = Rack::Lint.new(@app)
 | 
			
		||||
      else
 | 
			
		||||
        app = @app
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      errors = env[RACK_ERRORS]
 | 
			
		||||
      status, headers, body = app.call(env)
 | 
			
		||||
      MockResponse.new(status, headers, body, errors)
 | 
			
		||||
    ensure
 | 
			
		||||
      body.close if body.respond_to?(:close)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # For historical reasons, we're pinning to RFC 2396.
 | 
			
		||||
    # URI::Parser = URI::RFC2396_Parser
 | 
			
		||||
    def self.parse_uri_rfc2396(uri)
 | 
			
		||||
      @parser ||= URI::Parser.new
 | 
			
		||||
      @parser.parse(uri)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Return the Rack environment used for a request to +uri+.
 | 
			
		||||
    # All options that are strings are added to the returned environment.
 | 
			
		||||
    # Options:
 | 
			
		||||
    # :fatal :: Whether to raise an exception if request outputs to rack.errors
 | 
			
		||||
    # :input :: The rack.input to set
 | 
			
		||||
    # :http_version :: The SERVER_PROTOCOL to set
 | 
			
		||||
    # :method :: The HTTP request method to use
 | 
			
		||||
    # :params :: The params to use
 | 
			
		||||
    # :script_name :: The SCRIPT_NAME to set
 | 
			
		||||
    def self.env_for(uri = "", opts = {})
 | 
			
		||||
      uri = parse_uri_rfc2396(uri)
 | 
			
		||||
      uri.path = "/#{uri.path}" unless uri.path[0] == ?/
 | 
			
		||||
 | 
			
		||||
      env = DEFAULT_ENV.dup
 | 
			
		||||
 | 
			
		||||
      env[REQUEST_METHOD]  = (opts[:method] ? opts[:method].to_s.upcase : GET).b
 | 
			
		||||
      env[SERVER_NAME]     = (uri.host || "example.org").b
 | 
			
		||||
      env[SERVER_PORT]     = (uri.port ? uri.port.to_s : "80").b
 | 
			
		||||
      env[SERVER_PROTOCOL] = opts[:http_version] || 'HTTP/1.1'
 | 
			
		||||
      env[QUERY_STRING]    = (uri.query.to_s).b
 | 
			
		||||
      env[PATH_INFO]       = (uri.path).b
 | 
			
		||||
      env[RACK_URL_SCHEME] = (uri.scheme || "http").b
 | 
			
		||||
      env[HTTPS]           = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b
 | 
			
		||||
 | 
			
		||||
      env[SCRIPT_NAME] = opts[:script_name] || ""
 | 
			
		||||
 | 
			
		||||
      if opts[:fatal]
 | 
			
		||||
        env[RACK_ERRORS] = FatalWarner.new
 | 
			
		||||
      else
 | 
			
		||||
        env[RACK_ERRORS] = StringIO.new
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      if params = opts[:params]
 | 
			
		||||
        if env[REQUEST_METHOD] == GET
 | 
			
		||||
          params = Utils.parse_nested_query(params) if params.is_a?(String)
 | 
			
		||||
          params.update(Utils.parse_nested_query(env[QUERY_STRING]))
 | 
			
		||||
          env[QUERY_STRING] = Utils.build_nested_query(params)
 | 
			
		||||
        elsif !opts.has_key?(:input)
 | 
			
		||||
          opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
 | 
			
		||||
          if params.is_a?(Hash)
 | 
			
		||||
            if data = Rack::Multipart.build_multipart(params)
 | 
			
		||||
              opts[:input] = data
 | 
			
		||||
              opts["CONTENT_LENGTH"] ||= data.length.to_s
 | 
			
		||||
              opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}"
 | 
			
		||||
            else
 | 
			
		||||
              opts[:input] = Utils.build_nested_query(params)
 | 
			
		||||
            end
 | 
			
		||||
          else
 | 
			
		||||
            opts[:input] = params
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      opts[:input] ||= String.new
 | 
			
		||||
      if String === opts[:input]
 | 
			
		||||
        rack_input = StringIO.new(opts[:input])
 | 
			
		||||
      else
 | 
			
		||||
        rack_input = opts[:input]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      rack_input.set_encoding(Encoding::BINARY)
 | 
			
		||||
      env[RACK_INPUT] = rack_input
 | 
			
		||||
 | 
			
		||||
      env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
 | 
			
		||||
 | 
			
		||||
      opts.each { |field, value|
 | 
			
		||||
        env[field] = value  if String === field
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      env
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,126 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'cgi/cookie'
 | 
			
		||||
require 'time'
 | 
			
		||||
 | 
			
		||||
require_relative 'response'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Rack::MockResponse provides useful helpers for testing your apps.
 | 
			
		||||
  # Usually, you don't create the MockResponse on your own, but use
 | 
			
		||||
  # MockRequest.
 | 
			
		||||
 | 
			
		||||
  class MockResponse < Rack::Response
 | 
			
		||||
    class << self
 | 
			
		||||
      alias [] new
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Headers
 | 
			
		||||
    attr_reader :original_headers, :cookies
 | 
			
		||||
 | 
			
		||||
    # Errors
 | 
			
		||||
    attr_accessor :errors
 | 
			
		||||
 | 
			
		||||
    def initialize(status, headers, body, errors = nil)
 | 
			
		||||
      @original_headers = headers
 | 
			
		||||
 | 
			
		||||
      if errors
 | 
			
		||||
        @errors = errors.string if errors.respond_to?(:string)
 | 
			
		||||
      else
 | 
			
		||||
        @errors = ""
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      super(body, status, headers)
 | 
			
		||||
 | 
			
		||||
      @cookies = parse_cookies_from_header
 | 
			
		||||
      buffered_body!
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def =~(other)
 | 
			
		||||
      body =~ other
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def match(other)
 | 
			
		||||
      body.match other
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def body
 | 
			
		||||
      return @buffered_body if defined?(@buffered_body)
 | 
			
		||||
 | 
			
		||||
      # FIXME: apparently users of MockResponse expect the return value of
 | 
			
		||||
      # MockResponse#body to be a string.  However, the real response object
 | 
			
		||||
      # returns the body as a list.
 | 
			
		||||
      #
 | 
			
		||||
      # See spec_showstatus.rb:
 | 
			
		||||
      #
 | 
			
		||||
      #   should "not replace existing messages" do
 | 
			
		||||
      #     ...
 | 
			
		||||
      #     res.body.should == "foo!"
 | 
			
		||||
      #   end
 | 
			
		||||
      buffer = @buffered_body = String.new
 | 
			
		||||
 | 
			
		||||
      @body.each do |chunk|
 | 
			
		||||
        buffer << chunk
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      return buffer
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def empty?
 | 
			
		||||
      [201, 204, 304].include? status
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def cookie(name)
 | 
			
		||||
      cookies.fetch(name, nil)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    def parse_cookies_from_header
 | 
			
		||||
      cookies = Hash.new
 | 
			
		||||
      if headers.has_key? 'set-cookie'
 | 
			
		||||
        set_cookie_header = headers.fetch('set-cookie')
 | 
			
		||||
        Array(set_cookie_header).each do |header_value|
 | 
			
		||||
          header_value.split("\n").each do |cookie|
 | 
			
		||||
            cookie_name, cookie_filling = cookie.split('=', 2)
 | 
			
		||||
            cookie_attributes = identify_cookie_attributes cookie_filling
 | 
			
		||||
            parsed_cookie = CGI::Cookie.new(
 | 
			
		||||
              'name' => cookie_name.strip,
 | 
			
		||||
              'value' => cookie_attributes.fetch('value'),
 | 
			
		||||
              'path' => cookie_attributes.fetch('path', nil),
 | 
			
		||||
              'domain' => cookie_attributes.fetch('domain', nil),
 | 
			
		||||
              'expires' => cookie_attributes.fetch('expires', nil),
 | 
			
		||||
              'secure' => cookie_attributes.fetch('secure', false)
 | 
			
		||||
            )
 | 
			
		||||
            cookies.store(cookie_name, parsed_cookie)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      cookies
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def identify_cookie_attributes(cookie_filling)
 | 
			
		||||
      cookie_bits = cookie_filling.split(';')
 | 
			
		||||
      cookie_attributes = Hash.new
 | 
			
		||||
      cookie_attributes.store('value', cookie_bits[0].strip)
 | 
			
		||||
      cookie_bits.drop(1).each do |bit|
 | 
			
		||||
        if bit.include? '='
 | 
			
		||||
          cookie_attribute, attribute_value = bit.split('=', 2)
 | 
			
		||||
          cookie_attributes.store(cookie_attribute.strip.downcase, attribute_value.strip)
 | 
			
		||||
        end
 | 
			
		||||
        if bit.include? 'secure'
 | 
			
		||||
          cookie_attributes.store('secure', true)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      if cookie_attributes.key? 'max-age'
 | 
			
		||||
        cookie_attributes.store('expires', Time.now + cookie_attributes['max-age'].to_i)
 | 
			
		||||
      elsif cookie_attributes.key? 'expires'
 | 
			
		||||
        cookie_attributes.store('expires', Time.httpdate(cookie_attributes['expires']))
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      cookie_attributes
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,44 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require_relative 'constants'
 | 
			
		||||
require_relative 'utils'
 | 
			
		||||
 | 
			
		||||
require_relative 'multipart/parser'
 | 
			
		||||
require_relative 'multipart/generator'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # A multipart form data parser, adapted from IOWA.
 | 
			
		||||
  #
 | 
			
		||||
  # Usually, Rack::Request#POST takes care of calling this.
 | 
			
		||||
  module Multipart
 | 
			
		||||
    MULTIPART_BOUNDARY = "AaB03x"
 | 
			
		||||
 | 
			
		||||
    class << self
 | 
			
		||||
      def parse_multipart(env, params = Rack::Utils.default_query_parser)
 | 
			
		||||
        io = env[RACK_INPUT]
 | 
			
		||||
 | 
			
		||||
        if content_length = env['CONTENT_LENGTH']
 | 
			
		||||
          content_length = content_length.to_i
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        content_type = env['CONTENT_TYPE']
 | 
			
		||||
 | 
			
		||||
        tempfile = env[RACK_MULTIPART_TEMPFILE_FACTORY] || Parser::TEMPFILE_FACTORY
 | 
			
		||||
        bufsize = env[RACK_MULTIPART_BUFFER_SIZE] || Parser::BUFSIZE
 | 
			
		||||
 | 
			
		||||
        info = Parser.parse(io, content_length, content_type, tempfile, bufsize, params)
 | 
			
		||||
        env[RACK_TEMPFILES] = info.tmp_files
 | 
			
		||||
 | 
			
		||||
        return info.params
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def extract_multipart(request, params = Rack::Utils.default_query_parser)
 | 
			
		||||
        parse_multipart(request.env)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def build_multipart(params, first = true)
 | 
			
		||||
        Generator.new(params, first).dump
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,99 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,434 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'strscan'
 | 
			
		||||
 | 
			
		||||
require_relative '../utils'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  module Multipart
 | 
			
		||||
    class MultipartPartLimitError < Errno::EMFILE; end
 | 
			
		||||
 | 
			
		||||
    class MultipartTotalPartLimitError < StandardError; end
 | 
			
		||||
 | 
			
		||||
    # Use specific error class when parsing multipart request
 | 
			
		||||
    # that ends early.
 | 
			
		||||
    class EmptyContentError < ::EOFError; end
 | 
			
		||||
 | 
			
		||||
    # Base class for multipart exceptions that do not subclass from
 | 
			
		||||
    # other exception classes for backwards compatibility.
 | 
			
		||||
    class Error < StandardError; end
 | 
			
		||||
 | 
			
		||||
    EOL = "\r\n"
 | 
			
		||||
    MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
 | 
			
		||||
    TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
 | 
			
		||||
    CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
 | 
			
		||||
    VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
 | 
			
		||||
    BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
 | 
			
		||||
    MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
 | 
			
		||||
    MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
 | 
			
		||||
    MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
 | 
			
		||||
    # Updated definitions from RFC 2231
 | 
			
		||||
    ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
 | 
			
		||||
    ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
 | 
			
		||||
    SECTION = /\*[0-9]+/
 | 
			
		||||
    REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
 | 
			
		||||
    REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
 | 
			
		||||
    EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
 | 
			
		||||
    EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
 | 
			
		||||
    EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
 | 
			
		||||
    EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
 | 
			
		||||
    EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
 | 
			
		||||
    EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
 | 
			
		||||
    EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
 | 
			
		||||
    DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
 | 
			
		||||
    RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
 | 
			
		||||
 | 
			
		||||
    class Parser
 | 
			
		||||
      BUFSIZE = 1_048_576
 | 
			
		||||
      TEXT_PLAIN = "text/plain"
 | 
			
		||||
      TEMPFILE_FACTORY = lambda { |filename, content_type|
 | 
			
		||||
        Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      class BoundedIO # :nodoc:
 | 
			
		||||
        def initialize(io, content_length)
 | 
			
		||||
          @io             = io
 | 
			
		||||
          @content_length = content_length
 | 
			
		||||
          @cursor = 0
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def read(size, outbuf = nil)
 | 
			
		||||
          return if @cursor >= @content_length
 | 
			
		||||
 | 
			
		||||
          left = @content_length - @cursor
 | 
			
		||||
 | 
			
		||||
          str = if left < size
 | 
			
		||||
                  @io.read left, outbuf
 | 
			
		||||
                else
 | 
			
		||||
                  @io.read size, outbuf
 | 
			
		||||
                end
 | 
			
		||||
 | 
			
		||||
          if str
 | 
			
		||||
            @cursor += str.bytesize
 | 
			
		||||
          else
 | 
			
		||||
            # Raise an error for mismatching content-length and actual contents
 | 
			
		||||
            raise EOFError, "bad content body"
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          str
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      MultipartInfo = Struct.new :params, :tmp_files
 | 
			
		||||
      EMPTY         = MultipartInfo.new(nil, [])
 | 
			
		||||
 | 
			
		||||
      def self.parse_boundary(content_type)
 | 
			
		||||
        return unless content_type
 | 
			
		||||
        data = content_type.match(MULTIPART)
 | 
			
		||||
        return unless data
 | 
			
		||||
        data[1]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def self.parse(io, content_length, content_type, tmpfile, bufsize, qp)
 | 
			
		||||
        return EMPTY if 0 == content_length
 | 
			
		||||
 | 
			
		||||
        boundary = parse_boundary content_type
 | 
			
		||||
        return EMPTY unless boundary
 | 
			
		||||
 | 
			
		||||
        if boundary.length > 70
 | 
			
		||||
          # RFC 1521 Section 7.2.1 imposes a 70 character maximum for the boundary.
 | 
			
		||||
          # Most clients use no more than 55 characters.
 | 
			
		||||
          raise Error, "multipart boundary size too large (#{boundary.length} characters)"
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        io = BoundedIO.new(io, content_length) if content_length
 | 
			
		||||
 | 
			
		||||
        parser = new(boundary, tmpfile, bufsize, qp)
 | 
			
		||||
        parser.parse(io)
 | 
			
		||||
 | 
			
		||||
        parser.result
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      class Collector
 | 
			
		||||
        class MimePart < Struct.new(:body, :head, :filename, :content_type, :name)
 | 
			
		||||
          def get_data
 | 
			
		||||
            data = body
 | 
			
		||||
            if filename == ""
 | 
			
		||||
              # filename is blank which means no file has been selected
 | 
			
		||||
              return
 | 
			
		||||
            elsif filename
 | 
			
		||||
              body.rewind if body.respond_to?(:rewind)
 | 
			
		||||
 | 
			
		||||
              # Take the basename of the upload's original filename.
 | 
			
		||||
              # This handles the full Windows paths given by Internet Explorer
 | 
			
		||||
              # (and perhaps other broken user agents) without affecting
 | 
			
		||||
              # those which give the lone filename.
 | 
			
		||||
              fn = filename.split(/[\/\\]/).last
 | 
			
		||||
 | 
			
		||||
              data = { filename: fn, type: content_type,
 | 
			
		||||
                      name: name, tempfile: body, head: head }
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
            yield data
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        class BufferPart < MimePart
 | 
			
		||||
          def file?; false; end
 | 
			
		||||
          def close; end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        class TempfilePart < MimePart
 | 
			
		||||
          def file?; true; end
 | 
			
		||||
          def close; body.close; end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        include Enumerable
 | 
			
		||||
 | 
			
		||||
        def initialize(tempfile)
 | 
			
		||||
          @tempfile = tempfile
 | 
			
		||||
          @mime_parts = []
 | 
			
		||||
          @open_files = 0
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def each
 | 
			
		||||
          @mime_parts.each { |part| yield part }
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def on_mime_head(mime_index, head, filename, content_type, name)
 | 
			
		||||
          if filename
 | 
			
		||||
            body = @tempfile.call(filename, content_type)
 | 
			
		||||
            body.binmode if body.respond_to?(:binmode)
 | 
			
		||||
            klass = TempfilePart
 | 
			
		||||
            @open_files += 1
 | 
			
		||||
          else
 | 
			
		||||
            body = String.new
 | 
			
		||||
            klass = BufferPart
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
 | 
			
		||||
 | 
			
		||||
          check_part_limits
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def on_mime_body(mime_index, content)
 | 
			
		||||
          @mime_parts[mime_index].body << content
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def on_mime_finish(mime_index)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        private
 | 
			
		||||
 | 
			
		||||
        def check_part_limits
 | 
			
		||||
          file_limit = Utils.multipart_file_limit
 | 
			
		||||
          part_limit = Utils.multipart_total_part_limit
 | 
			
		||||
 | 
			
		||||
          if file_limit && file_limit > 0
 | 
			
		||||
            if @open_files >= file_limit
 | 
			
		||||
              @mime_parts.each(&:close)
 | 
			
		||||
              raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          if part_limit && part_limit > 0
 | 
			
		||||
            if @mime_parts.size >= part_limit
 | 
			
		||||
              @mime_parts.each(&:close)
 | 
			
		||||
              raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      attr_reader :state
 | 
			
		||||
 | 
			
		||||
      def initialize(boundary, tempfile, bufsize, query_parser)
 | 
			
		||||
        @query_parser   = query_parser
 | 
			
		||||
        @params         = query_parser.make_params
 | 
			
		||||
        @bufsize        = bufsize
 | 
			
		||||
 | 
			
		||||
        @state = :FAST_FORWARD
 | 
			
		||||
        @mime_index = 0
 | 
			
		||||
        @collector = Collector.new tempfile
 | 
			
		||||
 | 
			
		||||
        @sbuf = StringScanner.new("".dup)
 | 
			
		||||
        @body_regex = /(?:#{EOL}|\A)--#{Regexp.quote(boundary)}(?:#{EOL}|--)/m
 | 
			
		||||
        @rx_max_size = boundary.bytesize + 6 # (\r\n-- at start, either \r\n or -- at finish)
 | 
			
		||||
        @head_regex = /(.*?#{EOL})#{EOL}/m
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def parse(io)
 | 
			
		||||
        outbuf = String.new
 | 
			
		||||
        read_data(io, outbuf)
 | 
			
		||||
 | 
			
		||||
        loop do
 | 
			
		||||
          status =
 | 
			
		||||
            case @state
 | 
			
		||||
            when :FAST_FORWARD
 | 
			
		||||
              handle_fast_forward
 | 
			
		||||
            when :CONSUME_TOKEN
 | 
			
		||||
              handle_consume_token
 | 
			
		||||
            when :MIME_HEAD
 | 
			
		||||
              handle_mime_head
 | 
			
		||||
            when :MIME_BODY
 | 
			
		||||
              handle_mime_body
 | 
			
		||||
            else # when :DONE
 | 
			
		||||
              return
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
          read_data(io, outbuf) if status == :want_read
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def result
 | 
			
		||||
        @collector.each do |part|
 | 
			
		||||
          part.get_data do |data|
 | 
			
		||||
            tag_multipart_encoding(part.filename, part.content_type, part.name, data)
 | 
			
		||||
            @query_parser.normalize_params(@params, part.name, data)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
        MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      private
 | 
			
		||||
 | 
			
		||||
      def dequote(str) # From WEBrick::HTTPUtils
 | 
			
		||||
        ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
 | 
			
		||||
        ret.gsub!(/\\(.)/, "\\1")
 | 
			
		||||
        ret
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def read_data(io, outbuf)
 | 
			
		||||
        content = io.read(@bufsize, outbuf)
 | 
			
		||||
        handle_empty_content!(content)
 | 
			
		||||
        @sbuf.concat(content)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # This handles the initial parser state.  We read until we find the starting
 | 
			
		||||
      # boundary, then we can transition to the next state. If we find the ending
 | 
			
		||||
      # boundary, this is an invalid multipart upload, but keep scanning for opening
 | 
			
		||||
      # boundary in that case. If no boundary found, we need to keep reading data
 | 
			
		||||
      # and retry. It's highly unlikely the initial read will not consume the
 | 
			
		||||
      # boundary.  The client would have to deliberately craft a response
 | 
			
		||||
      # with the opening boundary beyond the buffer size for that to happen.
 | 
			
		||||
      def handle_fast_forward
 | 
			
		||||
        while true
 | 
			
		||||
          case consume_boundary
 | 
			
		||||
          when :BOUNDARY
 | 
			
		||||
            # found opening boundary, transition to next state
 | 
			
		||||
            @state = :MIME_HEAD
 | 
			
		||||
            return
 | 
			
		||||
          when :END_BOUNDARY
 | 
			
		||||
            # invalid multipart upload, but retry for opening boundary
 | 
			
		||||
          else
 | 
			
		||||
            # no boundary found, keep reading data
 | 
			
		||||
            return :want_read
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def handle_consume_token
 | 
			
		||||
        tok = consume_boundary
 | 
			
		||||
        # break if we're at the end of a buffer, but not if it is the end of a field
 | 
			
		||||
        @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
 | 
			
		||||
          :DONE
 | 
			
		||||
        else
 | 
			
		||||
          :MIME_HEAD
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def handle_mime_head
 | 
			
		||||
        if @sbuf.scan_until(@head_regex)
 | 
			
		||||
          head = @sbuf[1]
 | 
			
		||||
          content_type = head[MULTIPART_CONTENT_TYPE, 1]
 | 
			
		||||
          if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
 | 
			
		||||
            name = dequote(name)
 | 
			
		||||
          else
 | 
			
		||||
            name = head[MULTIPART_CONTENT_ID, 1]
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          filename = get_filename(head)
 | 
			
		||||
 | 
			
		||||
          if name.nil? || name.empty?
 | 
			
		||||
            name = filename || "#{content_type || TEXT_PLAIN}[]".dup
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          @collector.on_mime_head @mime_index, head, filename, content_type, name
 | 
			
		||||
          @state = :MIME_BODY
 | 
			
		||||
        else
 | 
			
		||||
          :want_read
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def handle_mime_body
 | 
			
		||||
        if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
 | 
			
		||||
          body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
 | 
			
		||||
          @collector.on_mime_body @mime_index, body
 | 
			
		||||
          @sbuf.pos += body.length + 2 # skip \r\n after the content
 | 
			
		||||
          @state = :CONSUME_TOKEN
 | 
			
		||||
          @mime_index += 1
 | 
			
		||||
        else
 | 
			
		||||
          # Save what we have so far
 | 
			
		||||
          if @rx_max_size < @sbuf.rest_size
 | 
			
		||||
            delta = @sbuf.rest_size - @rx_max_size
 | 
			
		||||
            @collector.on_mime_body @mime_index, @sbuf.peek(delta)
 | 
			
		||||
            @sbuf.pos += delta
 | 
			
		||||
            @sbuf.string = @sbuf.rest
 | 
			
		||||
          end
 | 
			
		||||
          :want_read
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Scan until the we find the start or end of the boundary.
 | 
			
		||||
      # If we find it, return the appropriate symbol for the start or
 | 
			
		||||
      # end of the boundary.  If we don't find the start or end of the
 | 
			
		||||
      # boundary, clear the buffer and return nil.
 | 
			
		||||
      def consume_boundary
 | 
			
		||||
        if read_buffer = @sbuf.scan_until(@body_regex)
 | 
			
		||||
          read_buffer.end_with?(EOL) ? :BOUNDARY : :END_BOUNDARY
 | 
			
		||||
        else
 | 
			
		||||
          @sbuf.terminate
 | 
			
		||||
          nil
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def get_filename(head)
 | 
			
		||||
        filename = nil
 | 
			
		||||
        case head
 | 
			
		||||
        when RFC2183
 | 
			
		||||
          params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
 | 
			
		||||
 | 
			
		||||
          if filename = params['filename*']
 | 
			
		||||
            encoding, _, filename = filename.split("'", 3)
 | 
			
		||||
          elsif filename = params['filename']
 | 
			
		||||
            filename = $1 if filename =~ /^"(.*)"$/
 | 
			
		||||
          end
 | 
			
		||||
        when BROKEN
 | 
			
		||||
          filename = $1
 | 
			
		||||
          filename = $1 if filename =~ /^"(.*)"$/
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        return unless filename
 | 
			
		||||
 | 
			
		||||
        if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
 | 
			
		||||
          filename = Utils.unescape_path(filename)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        filename.scrub!
 | 
			
		||||
 | 
			
		||||
        if filename !~ /\\[^\\"]/
 | 
			
		||||
          filename = filename.gsub(/\\(.)/, '\1')
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        if encoding
 | 
			
		||||
          filename.force_encoding ::Encoding.find(encoding)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        filename
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      CHARSET = "charset"
 | 
			
		||||
      deprecate_constant :CHARSET
 | 
			
		||||
 | 
			
		||||
      def tag_multipart_encoding(filename, content_type, name, body)
 | 
			
		||||
        name = name.to_s
 | 
			
		||||
        encoding = Encoding::UTF_8
 | 
			
		||||
 | 
			
		||||
        name.force_encoding(encoding)
 | 
			
		||||
 | 
			
		||||
        return if filename
 | 
			
		||||
 | 
			
		||||
        if content_type
 | 
			
		||||
          list         = content_type.split(';')
 | 
			
		||||
          type_subtype = list.first
 | 
			
		||||
          type_subtype.strip!
 | 
			
		||||
          if TEXT_PLAIN == type_subtype
 | 
			
		||||
            rest = list.drop 1
 | 
			
		||||
            rest.each do |param|
 | 
			
		||||
              k, v = param.split('=', 2)
 | 
			
		||||
              k.strip!
 | 
			
		||||
              v.strip!
 | 
			
		||||
              v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
 | 
			
		||||
              if k == "charset"
 | 
			
		||||
                encoding = begin
 | 
			
		||||
                  Encoding.find v
 | 
			
		||||
                rescue ArgumentError
 | 
			
		||||
                  Encoding::BINARY
 | 
			
		||||
                end
 | 
			
		||||
              end
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        name.force_encoding(encoding)
 | 
			
		||||
        body.force_encoding(encoding)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def handle_empty_content!(content)
 | 
			
		||||
        if content.nil? || content.empty?
 | 
			
		||||
          raise EmptyContentError
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,45 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,48 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,253 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'uri'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  class QueryParser
 | 
			
		||||
    DEFAULT_SEP = /[&] */n
 | 
			
		||||
    COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
 | 
			
		||||
 | 
			
		||||
    # ParameterTypeError is the error that is raised when incoming structural
 | 
			
		||||
    # parameters (parsed by parse_nested_query) contain conflicting types.
 | 
			
		||||
    class ParameterTypeError < TypeError; end
 | 
			
		||||
 | 
			
		||||
    # InvalidParameterError is the error that is raised when incoming structural
 | 
			
		||||
    # parameters (parsed by parse_nested_query) contain invalid format or byte
 | 
			
		||||
    # sequence.
 | 
			
		||||
    class InvalidParameterError < ArgumentError; end
 | 
			
		||||
 | 
			
		||||
    # ParamsTooDeepError is the error that is raised when params are recursively
 | 
			
		||||
    # nested over the specified limit.
 | 
			
		||||
    class ParamsTooDeepError < RangeError; end
 | 
			
		||||
 | 
			
		||||
    def self.make_default(_key_space_limit=(not_deprecated = true; nil), param_depth_limit)
 | 
			
		||||
      unless not_deprecated
 | 
			
		||||
        warn("`first argument `key_space limit` is deprecated and no longer has an effect. Please call with only one argument, which will be required in a future version of Rack", uplevel: 1)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      new Params, param_depth_limit
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    attr_reader :param_depth_limit
 | 
			
		||||
 | 
			
		||||
    def initialize(params_class, _key_space_limit=(not_deprecated = true; nil), param_depth_limit)
 | 
			
		||||
      unless not_deprecated
 | 
			
		||||
        warn("`second argument `key_space limit` is deprecated and no longer has an effect. Please call with only two arguments, which will be required in a future version of Rack", uplevel: 1)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      @params_class = params_class
 | 
			
		||||
      @param_depth_limit = param_depth_limit
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Stolen from Mongrel, with some small modifications:
 | 
			
		||||
    # Parses a query string by breaking it up at the '&'.  You can also use this
 | 
			
		||||
    # to parse cookies by changing the characters used in the second parameter
 | 
			
		||||
    # (which defaults to '&').
 | 
			
		||||
    def parse_query(qs, separator = nil, &unescaper)
 | 
			
		||||
      unescaper ||= method(:unescape)
 | 
			
		||||
 | 
			
		||||
      params = make_params
 | 
			
		||||
 | 
			
		||||
      (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
 | 
			
		||||
        next if p.empty?
 | 
			
		||||
        k, v = p.split('=', 2).map!(&unescaper)
 | 
			
		||||
 | 
			
		||||
        if cur = params[k]
 | 
			
		||||
          if cur.class == Array
 | 
			
		||||
            params[k] << v
 | 
			
		||||
          else
 | 
			
		||||
            params[k] = [cur, v]
 | 
			
		||||
          end
 | 
			
		||||
        else
 | 
			
		||||
          params[k] = v
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      return params.to_h
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # parse_nested_query expands a query string into structural types. Supported
 | 
			
		||||
    # types are Arrays, Hashes and basic value types. It is possible to supply
 | 
			
		||||
    # query strings with parameters of conflicting types, in this case a
 | 
			
		||||
    # ParameterTypeError is raised. Users are encouraged to return a 400 in this
 | 
			
		||||
    # case.
 | 
			
		||||
    def parse_nested_query(qs, separator = nil)
 | 
			
		||||
      params = make_params
 | 
			
		||||
 | 
			
		||||
      unless qs.nil? || qs.empty?
 | 
			
		||||
        (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
 | 
			
		||||
          k, v = p.split('=', 2).map! { |s| unescape(s) }
 | 
			
		||||
 | 
			
		||||
          _normalize_params(params, k, v, 0)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      return params.to_h
 | 
			
		||||
    rescue ArgumentError => e
 | 
			
		||||
      raise InvalidParameterError, e.message, e.backtrace
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # normalize_params recursively expands parameters into structural types. If
 | 
			
		||||
    # the structural types represented by two different parameter names are in
 | 
			
		||||
    # conflict, a ParameterTypeError is raised.  The depth argument is deprecated
 | 
			
		||||
    # and should no longer be used, it is kept for backwards compatibility with
 | 
			
		||||
    # earlier versions of rack.
 | 
			
		||||
    def normalize_params(params, name, v, _depth=nil)
 | 
			
		||||
      _normalize_params(params, name, v, 0)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private def _normalize_params(params, name, v, depth)
 | 
			
		||||
      raise ParamsTooDeepError if depth >= param_depth_limit
 | 
			
		||||
 | 
			
		||||
      if !name
 | 
			
		||||
        # nil name, treat same as empty string (required by tests)
 | 
			
		||||
        k = after = ''
 | 
			
		||||
      elsif depth == 0
 | 
			
		||||
        # Start of parsing, don't treat [] or [ at start of string specially
 | 
			
		||||
        if start = name.index('[', 1)
 | 
			
		||||
          # Start of parameter nesting, use part before brackets as key
 | 
			
		||||
          k = name[0, start]
 | 
			
		||||
          after = name[start, name.length]
 | 
			
		||||
        else
 | 
			
		||||
          # Plain parameter with no nesting
 | 
			
		||||
          k = name
 | 
			
		||||
          after = ''
 | 
			
		||||
        end
 | 
			
		||||
      elsif name.start_with?('[]')
 | 
			
		||||
        # Array nesting
 | 
			
		||||
        k = '[]'
 | 
			
		||||
        after = name[2, name.length]
 | 
			
		||||
      elsif name.start_with?('[') && (start = name.index(']', 1))
 | 
			
		||||
        # Hash nesting, use the part inside brackets as the key
 | 
			
		||||
        k = name[1, start-1]
 | 
			
		||||
        after = name[start+1, name.length]
 | 
			
		||||
      else
 | 
			
		||||
        # Probably malformed input, nested but not starting with [
 | 
			
		||||
        # treat full name as key for backwards compatibility.
 | 
			
		||||
        k = name
 | 
			
		||||
        after = ''
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      return if k.empty?
 | 
			
		||||
 | 
			
		||||
      if after == ''
 | 
			
		||||
        if k == '[]' && depth != 0
 | 
			
		||||
          return [v]
 | 
			
		||||
        else
 | 
			
		||||
          params[k] = v
 | 
			
		||||
        end
 | 
			
		||||
      elsif after == "["
 | 
			
		||||
        params[name] = v
 | 
			
		||||
      elsif after == "[]"
 | 
			
		||||
        params[k] ||= []
 | 
			
		||||
        raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
 | 
			
		||||
        params[k] << v
 | 
			
		||||
      elsif after.start_with?('[]')
 | 
			
		||||
        # Recognize x[][y] (hash inside array) parameters
 | 
			
		||||
        unless after[2] == '[' && after.end_with?(']') && (child_key = after[3, after.length-4]) && !child_key.empty? && !child_key.index('[') && !child_key.index(']')
 | 
			
		||||
          # Handle other nested array parameters
 | 
			
		||||
          child_key = after[2, after.length]
 | 
			
		||||
        end
 | 
			
		||||
        params[k] ||= []
 | 
			
		||||
        raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
 | 
			
		||||
        if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
 | 
			
		||||
          _normalize_params(params[k].last, child_key, v, depth + 1)
 | 
			
		||||
        else
 | 
			
		||||
          params[k] << _normalize_params(make_params, child_key, v, depth + 1)
 | 
			
		||||
        end
 | 
			
		||||
      else
 | 
			
		||||
        params[k] ||= make_params
 | 
			
		||||
        raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
 | 
			
		||||
        params[k] = _normalize_params(params[k], after, v, depth + 1)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      params
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def make_params
 | 
			
		||||
      @params_class.new
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def new_depth_limit(param_depth_limit)
 | 
			
		||||
      self.class.new @params_class, param_depth_limit
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    def params_hash_type?(obj)
 | 
			
		||||
      obj.kind_of?(@params_class)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def params_hash_has_key?(hash, key)
 | 
			
		||||
      return false if /\[\]/.match?(key)
 | 
			
		||||
 | 
			
		||||
      key.split(/[\[\]]+/).inject(hash) do |h, part|
 | 
			
		||||
        next h if part == ''
 | 
			
		||||
        return false unless params_hash_type?(h) && h.key?(part)
 | 
			
		||||
        h[part]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      true
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def unescape(string, encoding = Encoding::UTF_8)
 | 
			
		||||
      URI.decode_www_form_component(string, encoding)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    class Params
 | 
			
		||||
      def initialize
 | 
			
		||||
        @size   = 0
 | 
			
		||||
        @params = {}
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def [](key)
 | 
			
		||||
        @params[key]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def []=(key, value)
 | 
			
		||||
        @params[key] = value
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def key?(key)
 | 
			
		||||
        @params.key?(key)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Recursively unwraps nested `Params` objects and constructs an object
 | 
			
		||||
      # of the same shape, but using the objects' internal representations
 | 
			
		||||
      # (Ruby hashes) in place of the objects. The result is a hash consisting
 | 
			
		||||
      # purely of Ruby primitives.
 | 
			
		||||
      #
 | 
			
		||||
      #   Mutation warning!
 | 
			
		||||
      #
 | 
			
		||||
      #   1. This method mutates the internal representation of the `Params`
 | 
			
		||||
      #      objects in order to save object allocations.
 | 
			
		||||
      #
 | 
			
		||||
      #   2. The value you get back is a reference to the internal hash
 | 
			
		||||
      #      representation, not a copy.
 | 
			
		||||
      #
 | 
			
		||||
      #   3. Because the `Params` object's internal representation is mutable
 | 
			
		||||
      #      through the `#[]=` method, it is not thread safe. The result of
 | 
			
		||||
      #      getting the hash representation while another thread is adding a
 | 
			
		||||
      #      key to it is non-deterministic.
 | 
			
		||||
      #
 | 
			
		||||
      def to_h
 | 
			
		||||
        @params.each do |key, value|
 | 
			
		||||
          case value
 | 
			
		||||
          when self
 | 
			
		||||
            # Handle circular references gracefully.
 | 
			
		||||
            @params[key] = @params
 | 
			
		||||
          when Params
 | 
			
		||||
            @params[key] = value.to_h
 | 
			
		||||
          when Array
 | 
			
		||||
            value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
 | 
			
		||||
          else
 | 
			
		||||
            # Ignore anything that is not a `Params` object or
 | 
			
		||||
            # a collection that can contain one.
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
        @params
 | 
			
		||||
      end
 | 
			
		||||
      alias_method :to_params_hash, :to_h
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,66 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
  # <tt>rack['rack.recursive.include'][...]</tt> 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
 | 
			
		||||
@ -1,112 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
# Copyright (C) 2009-2018 Michael Fellinger <m.fellinger@gmail.com>
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,777 +0,0 @@
 | 
			
		||||
# 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 <tt>[:forwarded, :x_forwarded]</tt>, which means, check the
 | 
			
		||||
      # +Forwarded+ header first, followed by the appropriate
 | 
			
		||||
      # <tt>X-Forwarded-*</tt> 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 <tt>X-Forwarded-Proto</tt>
 | 
			
		||||
      # or <tt>X-Forwarded-Scheme</tt> header for the forwarded protocol.
 | 
			
		||||
      # The default is <tt>[:proto, :scheme]</tt>, to try the
 | 
			
		||||
      # <tt>X-Forwarded-Proto</tt> header before the
 | 
			
		||||
      # <tt>X-Forwarded-Scheme</tt> header.  Rack 2 had behavior
 | 
			
		||||
      # similar to <tt>[:scheme, :proto]</tt>.  You can remove either or
 | 
			
		||||
      # both of the entries in array to ignore that respective header.
 | 
			
		||||
      attr_accessor :x_forwarded_proto_priority
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    @forwarded_priority = [:forwarded, :x_forwarded]
 | 
			
		||||
    @x_forwarded_proto_priority = [:proto, :scheme]
 | 
			
		||||
 | 
			
		||||
    valid_ipv4_octet = /\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])/
 | 
			
		||||
 | 
			
		||||
    trusted_proxies = Regexp.union(
 | 
			
		||||
      /\A127#{valid_ipv4_octet}{3}\z/,                          # localhost IPv4 range 127.x.x.x, per RFC-3330
 | 
			
		||||
      /\A::1\z/,                                                # localhost IPv6 ::1
 | 
			
		||||
      /\Af[cd][0-9a-f]{2}(?::[0-9a-f]{0,4}){0,7}\z/i,           # private IPv6 range fc00 .. fdff
 | 
			
		||||
      /\A10#{valid_ipv4_octet}{3}\z/,                           # private IPv4 range 10.x.x.x
 | 
			
		||||
      /\A172\.(1[6-9]|2[0-9]|3[01])#{valid_ipv4_octet}{2}\z/,   # private IPv4 range 172.16.0.0 .. 172.31.255.255
 | 
			
		||||
      /\A192\.168#{valid_ipv4_octet}{2}\z/,                     # private IPv4 range 192.168.x.x
 | 
			
		||||
      /\Alocalhost\z|\Aunix(\z|:)/i,                            # localhost hostname, and unix domain sockets
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    self.ip_filter = lambda { |ip| trusted_proxies.match?(ip) }
 | 
			
		||||
 | 
			
		||||
    ALLOWED_SCHEMES = %w(https http wss ws).freeze
 | 
			
		||||
 | 
			
		||||
    def initialize(env)
 | 
			
		||||
      @env = env
 | 
			
		||||
      @params = nil
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def params
 | 
			
		||||
      @params ||= super
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def update_param(k, v)
 | 
			
		||||
      super
 | 
			
		||||
      @params = nil
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def delete_param(k)
 | 
			
		||||
      v = super
 | 
			
		||||
      @params = nil
 | 
			
		||||
      v
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    module Env
 | 
			
		||||
      # The environment of the request.
 | 
			
		||||
      attr_reader :env
 | 
			
		||||
 | 
			
		||||
      def initialize(env)
 | 
			
		||||
        @env = env
 | 
			
		||||
        # This module is included at least in `ActionDispatch::Request`
 | 
			
		||||
        # The call to `super()` allows additional mixed-in initializers are called
 | 
			
		||||
        super()
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Predicate method to test to see if `name` has been set as request
 | 
			
		||||
      # specific data
 | 
			
		||||
      def has_header?(name)
 | 
			
		||||
        @env.key? name
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Get a request specific value for `name`.
 | 
			
		||||
      def get_header(name)
 | 
			
		||||
        @env[name]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # If a block is given, it yields to the block if the value hasn't been set
 | 
			
		||||
      # on the request.
 | 
			
		||||
      def fetch_header(name, &block)
 | 
			
		||||
        @env.fetch(name, &block)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Loops through each key / value pair in the request specific data.
 | 
			
		||||
      def each_header(&block)
 | 
			
		||||
        @env.each(&block)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Set a request specific value for `name` to `v`
 | 
			
		||||
      def set_header(name, v)
 | 
			
		||||
        @env[name] = v
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Add a header that may have multiple values.
 | 
			
		||||
      #
 | 
			
		||||
      # Example:
 | 
			
		||||
      #   request.add_header 'Accept', 'image/png'
 | 
			
		||||
      #   request.add_header 'Accept', '*/*'
 | 
			
		||||
      #
 | 
			
		||||
      #   assert_equal 'image/png,*/*', request.get_header('Accept')
 | 
			
		||||
      #
 | 
			
		||||
      # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
 | 
			
		||||
      def add_header(key, v)
 | 
			
		||||
        if v.nil?
 | 
			
		||||
          get_header key
 | 
			
		||||
        elsif has_header? key
 | 
			
		||||
          set_header key, "#{get_header key},#{v}"
 | 
			
		||||
        else
 | 
			
		||||
          set_header key, v
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Delete a request specific value for `name`.
 | 
			
		||||
      def delete_header(name)
 | 
			
		||||
        @env.delete name
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def initialize_copy(other)
 | 
			
		||||
        @env = other.env.dup
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    module Helpers
 | 
			
		||||
      # The set of form-data media-types. Requests that do not indicate
 | 
			
		||||
      # one of the media types present in this list will not be eligible
 | 
			
		||||
      # for form-data / param parsing.
 | 
			
		||||
      FORM_DATA_MEDIA_TYPES = [
 | 
			
		||||
        'application/x-www-form-urlencoded',
 | 
			
		||||
        'multipart/form-data'
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
      # The set of media-types. Requests that do not indicate
 | 
			
		||||
      # one of the media types present in this list will not be eligible
 | 
			
		||||
      # for param parsing like soap attachments or generic multiparts
 | 
			
		||||
      PARSEABLE_DATA_MEDIA_TYPES = [
 | 
			
		||||
        'multipart/related',
 | 
			
		||||
        'multipart/mixed'
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
      # Default ports depending on scheme. Used to decide whether or not
 | 
			
		||||
      # to include the port in a generated URI.
 | 
			
		||||
      DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
 | 
			
		||||
 | 
			
		||||
      # The address of the client which connected to the proxy.
 | 
			
		||||
      HTTP_X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'
 | 
			
		||||
 | 
			
		||||
      # The contents of the host/:authority header sent to the proxy.
 | 
			
		||||
      HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
 | 
			
		||||
 | 
			
		||||
      HTTP_FORWARDED          = 'HTTP_FORWARDED'
 | 
			
		||||
 | 
			
		||||
      # The value of the scheme sent to the proxy.
 | 
			
		||||
      HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
 | 
			
		||||
 | 
			
		||||
      # The protocol used to connect to the proxy.
 | 
			
		||||
      HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
 | 
			
		||||
 | 
			
		||||
      # The port used to connect to the proxy.
 | 
			
		||||
      HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
 | 
			
		||||
 | 
			
		||||
      # Another way for specifying https scheme was used.
 | 
			
		||||
      HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
 | 
			
		||||
 | 
			
		||||
      def body;            get_header(RACK_INPUT)                         end
 | 
			
		||||
      def script_name;     get_header(SCRIPT_NAME).to_s                   end
 | 
			
		||||
      def script_name=(s); set_header(SCRIPT_NAME, s.to_s)                end
 | 
			
		||||
 | 
			
		||||
      def path_info;       get_header(PATH_INFO).to_s                     end
 | 
			
		||||
      def path_info=(s);   set_header(PATH_INFO, s.to_s)                  end
 | 
			
		||||
 | 
			
		||||
      def request_method;  get_header(REQUEST_METHOD)                     end
 | 
			
		||||
      def query_string;    get_header(QUERY_STRING).to_s                  end
 | 
			
		||||
      def content_length;  get_header('CONTENT_LENGTH')                   end
 | 
			
		||||
      def logger;          get_header(RACK_LOGGER)                        end
 | 
			
		||||
      def user_agent;      get_header('HTTP_USER_AGENT')                  end
 | 
			
		||||
 | 
			
		||||
      # the referer of the client
 | 
			
		||||
      def referer;         get_header('HTTP_REFERER')                     end
 | 
			
		||||
      alias referrer referer
 | 
			
		||||
 | 
			
		||||
      def session
 | 
			
		||||
        fetch_header(RACK_SESSION) do |k|
 | 
			
		||||
          set_header RACK_SESSION, default_session
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def session_options
 | 
			
		||||
        fetch_header(RACK_SESSION_OPTIONS) do |k|
 | 
			
		||||
          set_header RACK_SESSION_OPTIONS, {}
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Checks the HTTP request method (or verb) to see if it was of type DELETE
 | 
			
		||||
      def delete?;  request_method == DELETE  end
 | 
			
		||||
 | 
			
		||||
      # Checks the HTTP request method (or verb) to see if it was of type GET
 | 
			
		||||
      def get?;     request_method == GET     end
 | 
			
		||||
 | 
			
		||||
      # Checks the HTTP request method (or verb) to see if it was of type HEAD
 | 
			
		||||
      def head?;    request_method == HEAD    end
 | 
			
		||||
 | 
			
		||||
      # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
 | 
			
		||||
      def options?; request_method == OPTIONS end
 | 
			
		||||
 | 
			
		||||
      # Checks the HTTP request method (or verb) to see if it was of type LINK
 | 
			
		||||
      def link?;    request_method == LINK    end
 | 
			
		||||
 | 
			
		||||
      # Checks the HTTP request method (or verb) to see if it was of type PATCH
 | 
			
		||||
      def patch?;   request_method == PATCH   end
 | 
			
		||||
 | 
			
		||||
      # Checks the HTTP request method (or verb) to see if it was of type POST
 | 
			
		||||
      def post?;    request_method == POST    end
 | 
			
		||||
 | 
			
		||||
      # Checks the HTTP request method (or verb) to see if it was of type PUT
 | 
			
		||||
      def put?;     request_method == PUT     end
 | 
			
		||||
 | 
			
		||||
      # Checks the HTTP request method (or verb) to see if it was of type TRACE
 | 
			
		||||
      def trace?;   request_method == TRACE   end
 | 
			
		||||
 | 
			
		||||
      # Checks the HTTP request method (or verb) to see if it was of type UNLINK
 | 
			
		||||
      def unlink?;  request_method == UNLINK  end
 | 
			
		||||
 | 
			
		||||
      def scheme
 | 
			
		||||
        if get_header(HTTPS) == 'on'
 | 
			
		||||
          'https'
 | 
			
		||||
        elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
 | 
			
		||||
          'https'
 | 
			
		||||
        elsif forwarded_scheme
 | 
			
		||||
          forwarded_scheme
 | 
			
		||||
        else
 | 
			
		||||
          get_header(RACK_URL_SCHEME)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # The authority of the incoming request as defined by RFC3976.
 | 
			
		||||
      # https://tools.ietf.org/html/rfc3986#section-3.2
 | 
			
		||||
      #
 | 
			
		||||
      # In HTTP/1, this is the `host` header.
 | 
			
		||||
      # In HTTP/2, this is the `:authority` pseudo-header.
 | 
			
		||||
      def authority
 | 
			
		||||
        forwarded_authority || host_authority || server_authority
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # The authority as defined by the `SERVER_NAME` and `SERVER_PORT`
 | 
			
		||||
      # variables.
 | 
			
		||||
      def server_authority
 | 
			
		||||
        host = self.server_name
 | 
			
		||||
        port = self.server_port
 | 
			
		||||
 | 
			
		||||
        if host
 | 
			
		||||
          if port
 | 
			
		||||
            "#{host}:#{port}"
 | 
			
		||||
          else
 | 
			
		||||
            host
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def server_name
 | 
			
		||||
        get_header(SERVER_NAME)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def server_port
 | 
			
		||||
        get_header(SERVER_PORT)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def cookies
 | 
			
		||||
        hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |key|
 | 
			
		||||
          set_header(key, {})
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        string = get_header(HTTP_COOKIE)
 | 
			
		||||
 | 
			
		||||
        unless string == get_header(RACK_REQUEST_COOKIE_STRING)
 | 
			
		||||
          hash.replace Utils.parse_cookies_header(string)
 | 
			
		||||
          set_header(RACK_REQUEST_COOKIE_STRING, string)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        hash
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def content_type
 | 
			
		||||
        content_type = get_header('CONTENT_TYPE')
 | 
			
		||||
        content_type.nil? || content_type.empty? ? nil : content_type
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def xhr?
 | 
			
		||||
        get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # The `HTTP_HOST` header.
 | 
			
		||||
      def host_authority
 | 
			
		||||
        get_header(HTTP_HOST)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def host_with_port(authority = self.authority)
 | 
			
		||||
        host, _, port = split_authority(authority)
 | 
			
		||||
 | 
			
		||||
        if port == DEFAULT_PORTS[self.scheme]
 | 
			
		||||
          host
 | 
			
		||||
        else
 | 
			
		||||
          authority
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Returns a formatted host, suitable for being used in a URI.
 | 
			
		||||
      def host
 | 
			
		||||
        split_authority(self.authority)[0]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Returns an address suitable for being to resolve to an address.
 | 
			
		||||
      # In the case of a domain name or IPv4 address, the result is the same
 | 
			
		||||
      # as +host+. In the case of IPv6 or future address formats, the square
 | 
			
		||||
      # brackets are removed.
 | 
			
		||||
      def hostname
 | 
			
		||||
        split_authority(self.authority)[1]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def port
 | 
			
		||||
        if authority = self.authority
 | 
			
		||||
          _, _, port = split_authority(authority)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        port || forwarded_port&.last || DEFAULT_PORTS[scheme] || server_port
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def forwarded_for
 | 
			
		||||
        forwarded_priority.each do |type|
 | 
			
		||||
          case type
 | 
			
		||||
          when :forwarded
 | 
			
		||||
            if forwarded_for = get_http_forwarded(:for)
 | 
			
		||||
              return(forwarded_for.map! do |authority|
 | 
			
		||||
                split_authority(authority)[1]
 | 
			
		||||
              end)
 | 
			
		||||
            end
 | 
			
		||||
          when :x_forwarded
 | 
			
		||||
            if value = get_header(HTTP_X_FORWARDED_FOR)
 | 
			
		||||
              return(split_header(value).map do |authority|
 | 
			
		||||
                split_authority(wrap_ipv6(authority))[1]
 | 
			
		||||
              end)
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        nil
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def forwarded_port
 | 
			
		||||
        forwarded_priority.each do |type|
 | 
			
		||||
          case type
 | 
			
		||||
          when :forwarded
 | 
			
		||||
            if forwarded = get_http_forwarded(:for)
 | 
			
		||||
              return(forwarded.map do |authority|
 | 
			
		||||
                split_authority(authority)[2]
 | 
			
		||||
              end.compact)
 | 
			
		||||
            end
 | 
			
		||||
          when :x_forwarded
 | 
			
		||||
            if value = get_header(HTTP_X_FORWARDED_PORT)
 | 
			
		||||
              return split_header(value).map(&:to_i)
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        nil
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def forwarded_authority
 | 
			
		||||
        forwarded_priority.each do |type|
 | 
			
		||||
          case type
 | 
			
		||||
          when :forwarded
 | 
			
		||||
            if forwarded = get_http_forwarded(:host)
 | 
			
		||||
              return forwarded.last
 | 
			
		||||
            end
 | 
			
		||||
          when :x_forwarded
 | 
			
		||||
            if value = get_header(HTTP_X_FORWARDED_HOST)
 | 
			
		||||
              return wrap_ipv6(split_header(value).last)
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        nil
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def ssl?
 | 
			
		||||
        scheme == 'https' || scheme == 'wss'
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def ip
 | 
			
		||||
        remote_addresses = split_header(get_header('REMOTE_ADDR'))
 | 
			
		||||
        external_addresses = reject_trusted_ip_addresses(remote_addresses)
 | 
			
		||||
 | 
			
		||||
        unless external_addresses.empty?
 | 
			
		||||
          return external_addresses.last
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        if (forwarded_for = self.forwarded_for) && !forwarded_for.empty?
 | 
			
		||||
          # The forwarded for addresses are ordered: client, proxy1, proxy2.
 | 
			
		||||
          # So we reject all the trusted addresses (proxy*) and return the
 | 
			
		||||
          # last client. Or if we trust everyone, we just return the first
 | 
			
		||||
          # address.
 | 
			
		||||
          return reject_trusted_ip_addresses(forwarded_for).last || forwarded_for.first
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # If all the addresses are trusted, and we aren't forwarded, just return
 | 
			
		||||
        # the first remote address, which represents the source of the request.
 | 
			
		||||
        remote_addresses.first
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # The media type (type/subtype) portion of the CONTENT_TYPE header
 | 
			
		||||
      # without any media type parameters. e.g., when CONTENT_TYPE is
 | 
			
		||||
      # "text/plain;charset=utf-8", the media-type is "text/plain".
 | 
			
		||||
      #
 | 
			
		||||
      # For more information on the use of media types in HTTP, see:
 | 
			
		||||
      # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
 | 
			
		||||
      def media_type
 | 
			
		||||
        MediaType.type(content_type)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # The media type parameters provided in CONTENT_TYPE as a Hash, or
 | 
			
		||||
      # an empty Hash if no CONTENT_TYPE or media-type parameters were
 | 
			
		||||
      # provided.  e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
 | 
			
		||||
      # this method responds with the following Hash:
 | 
			
		||||
      #   { 'charset' => 'utf-8' }
 | 
			
		||||
      def media_type_params
 | 
			
		||||
        MediaType.params(content_type)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # The character set of the request body if a "charset" media type
 | 
			
		||||
      # parameter was given, or nil if no "charset" was specified. Note
 | 
			
		||||
      # that, per RFC2616, text/* media types that specify no explicit
 | 
			
		||||
      # charset are to be considered ISO-8859-1.
 | 
			
		||||
      def content_charset
 | 
			
		||||
        media_type_params['charset']
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Determine whether the request body contains form-data by checking
 | 
			
		||||
      # the request content-type for one of the media-types:
 | 
			
		||||
      # "application/x-www-form-urlencoded" or "multipart/form-data". The
 | 
			
		||||
      # list of form-data media types can be modified through the
 | 
			
		||||
      # +FORM_DATA_MEDIA_TYPES+ array.
 | 
			
		||||
      #
 | 
			
		||||
      # A request body is also assumed to contain form-data when no
 | 
			
		||||
      # content-type header is provided and the request_method is POST.
 | 
			
		||||
      def form_data?
 | 
			
		||||
        type = media_type
 | 
			
		||||
        meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
 | 
			
		||||
 | 
			
		||||
        (meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Determine whether the request body contains data by checking
 | 
			
		||||
      # the request media_type against registered parse-data media-types
 | 
			
		||||
      def parseable_data?
 | 
			
		||||
        PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Returns the data received in the query string.
 | 
			
		||||
      def GET
 | 
			
		||||
        if get_header(RACK_REQUEST_QUERY_STRING) == query_string
 | 
			
		||||
          get_header(RACK_REQUEST_QUERY_HASH)
 | 
			
		||||
        else
 | 
			
		||||
          query_hash = parse_query(query_string, '&')
 | 
			
		||||
          set_header(RACK_REQUEST_QUERY_STRING, query_string)
 | 
			
		||||
          set_header(RACK_REQUEST_QUERY_HASH, query_hash)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Returns the data received in the request body.
 | 
			
		||||
      #
 | 
			
		||||
      # This method support both application/x-www-form-urlencoded and
 | 
			
		||||
      # multipart/form-data.
 | 
			
		||||
      def POST
 | 
			
		||||
        if error = get_header(RACK_REQUEST_FORM_ERROR)
 | 
			
		||||
          raise error.class, error.message, cause: error.cause
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        begin
 | 
			
		||||
          rack_input = get_header(RACK_INPUT)
 | 
			
		||||
 | 
			
		||||
          # If the form hash was already memoized:
 | 
			
		||||
          if form_hash = get_header(RACK_REQUEST_FORM_HASH)
 | 
			
		||||
            # And it was memoized from the same input:
 | 
			
		||||
            if get_header(RACK_REQUEST_FORM_INPUT).equal?(rack_input)
 | 
			
		||||
              return form_hash
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          # Otherwise, figure out how to parse the input:
 | 
			
		||||
          if rack_input.nil?
 | 
			
		||||
            set_header RACK_REQUEST_FORM_INPUT, nil
 | 
			
		||||
            set_header(RACK_REQUEST_FORM_HASH, {})
 | 
			
		||||
          elsif form_data? || parseable_data?
 | 
			
		||||
            unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
 | 
			
		||||
              form_vars = get_header(RACK_INPUT).read
 | 
			
		||||
 | 
			
		||||
              # Fix for Safari Ajax postings that always append \0
 | 
			
		||||
              # form_vars.sub!(/\0\z/, '') # performance replacement:
 | 
			
		||||
              form_vars.slice!(-1) if form_vars.end_with?("\0")
 | 
			
		||||
 | 
			
		||||
              set_header RACK_REQUEST_FORM_VARS, form_vars
 | 
			
		||||
              set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
            set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
 | 
			
		||||
            get_header RACK_REQUEST_FORM_HASH
 | 
			
		||||
          else
 | 
			
		||||
            set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
 | 
			
		||||
            set_header(RACK_REQUEST_FORM_HASH, {})
 | 
			
		||||
          end
 | 
			
		||||
        rescue => error
 | 
			
		||||
          set_header(RACK_REQUEST_FORM_ERROR, error)
 | 
			
		||||
          raise
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # The union of GET and POST data.
 | 
			
		||||
      #
 | 
			
		||||
      # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
 | 
			
		||||
      def params
 | 
			
		||||
        self.GET.merge(self.POST)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
 | 
			
		||||
      #
 | 
			
		||||
      # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
 | 
			
		||||
      #
 | 
			
		||||
      # <tt>env['rack.input']</tt> 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.
 | 
			
		||||
      #
 | 
			
		||||
      # <tt>env['rack.input']</tt> is not touched.
 | 
			
		||||
      def delete_param(k)
 | 
			
		||||
        post_value, get_value = self.POST.delete(k), self.GET.delete(k)
 | 
			
		||||
        post_value || get_value
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def base_url
 | 
			
		||||
        "#{scheme}://#{host_with_port}"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Tries to return a remake of the original request URL as a string.
 | 
			
		||||
      def url
 | 
			
		||||
        base_url + fullpath
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def path
 | 
			
		||||
        script_name + path_info
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def fullpath
 | 
			
		||||
        query_string.empty? ? path : "#{path}?#{query_string}"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def accept_encoding
 | 
			
		||||
        parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING"))
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def accept_language
 | 
			
		||||
        parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE"))
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def trusted_proxy?(ip)
 | 
			
		||||
        Rack::Request.ip_filter.call(ip)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # shortcut for <tt>request.params[key]</tt>
 | 
			
		||||
      def [](key)
 | 
			
		||||
        warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead", uplevel: 1)
 | 
			
		||||
 | 
			
		||||
        params[key.to_s]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # shortcut for <tt>request.params[key] = value</tt>
 | 
			
		||||
      #
 | 
			
		||||
      # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
 | 
			
		||||
      def []=(key, value)
 | 
			
		||||
        warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead", uplevel: 1)
 | 
			
		||||
 | 
			
		||||
        params[key.to_s] = value
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # like Hash#values_at
 | 
			
		||||
      def values_at(*keys)
 | 
			
		||||
        keys.map { |key| params[key] }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      private
 | 
			
		||||
 | 
			
		||||
      def default_session; {}; end
 | 
			
		||||
 | 
			
		||||
      # Assist with compatibility when processing `X-Forwarded-For`.
 | 
			
		||||
      def wrap_ipv6(host)
 | 
			
		||||
        # Even thought IPv6 addresses should be wrapped in square brackets,
 | 
			
		||||
        # sometimes this is not done in various legacy/underspecified headers.
 | 
			
		||||
        # So we try to fix this situation for compatibility reasons.
 | 
			
		||||
 | 
			
		||||
        # Try to detect IPv6 addresses which aren't escaped yet:
 | 
			
		||||
        if !host.start_with?('[') && host.count(':') > 1
 | 
			
		||||
          "[#{host}]"
 | 
			
		||||
        else
 | 
			
		||||
          host
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def parse_http_accept_header(header)
 | 
			
		||||
        header.to_s.split(",").each(&:strip!).map do |part|
 | 
			
		||||
          attribute, parameters = part.split(";", 2).each(&:strip!)
 | 
			
		||||
          quality = 1.0
 | 
			
		||||
          if parameters and /\Aq=([\d.]+)/ =~ parameters
 | 
			
		||||
            quality = $1.to_f
 | 
			
		||||
          end
 | 
			
		||||
          [attribute, quality]
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Get an array of values set in the RFC 7239 `Forwarded` request header.
 | 
			
		||||
      def get_http_forwarded(token)
 | 
			
		||||
        Utils.forwarded_values(get_header(HTTP_FORWARDED))&.[](token)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def query_parser
 | 
			
		||||
        Utils.default_query_parser
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def parse_query(qs, d = '&')
 | 
			
		||||
        query_parser.parse_nested_query(qs, d)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def parse_multipart
 | 
			
		||||
        Rack::Multipart.extract_multipart(self, query_parser)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def split_header(value)
 | 
			
		||||
        value ? value.strip.split(/[,\s]+/) : []
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # ipv6 extracted from resolv stdlib, simplified
 | 
			
		||||
      # to remove numbered match group creation.
 | 
			
		||||
      ipv6 = Regexp.union(
 | 
			
		||||
        /(?:[0-9A-Fa-f]{1,4}:){7}
 | 
			
		||||
         [0-9A-Fa-f]{1,4}/x,
 | 
			
		||||
        /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
 | 
			
		||||
         (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?/x,
 | 
			
		||||
        /(?:[0-9A-Fa-f]{1,4}:){6,6}
 | 
			
		||||
         \d+\.\d+\.\d+\.\d+/x,
 | 
			
		||||
        /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
 | 
			
		||||
         (?:[0-9A-Fa-f]{1,4}:)*
 | 
			
		||||
         \d+\.\d+\.\d+\.\d+/x,
 | 
			
		||||
        /[Ff][Ee]80
 | 
			
		||||
         (?::[0-9A-Fa-f]{1,4}){7}
 | 
			
		||||
         %[-0-9A-Za-z._~]+/x,
 | 
			
		||||
        /[Ff][Ee]80:
 | 
			
		||||
         (?:
 | 
			
		||||
           (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
 | 
			
		||||
           (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?
 | 
			
		||||
           |
 | 
			
		||||
           :(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?
 | 
			
		||||
         )?
 | 
			
		||||
         :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+/x)
 | 
			
		||||
 | 
			
		||||
      AUTHORITY = /
 | 
			
		||||
        \A
 | 
			
		||||
        (?<host>
 | 
			
		||||
          # Match IPv6 as a string of hex digits and colons in square brackets
 | 
			
		||||
          \[(?<address>#{ipv6})\]
 | 
			
		||||
          |
 | 
			
		||||
          # Match any other printable string (except square brackets) as a hostname
 | 
			
		||||
          (?<address>[[[:graph:]&&[^\[\]]]]*?)
 | 
			
		||||
        )
 | 
			
		||||
        (:(?<port>\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:
 | 
			
		||||
@ -1,393 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'time'
 | 
			
		||||
 | 
			
		||||
require_relative 'constants'
 | 
			
		||||
require_relative 'utils'
 | 
			
		||||
require_relative 'media_type'
 | 
			
		||||
require_relative 'headers'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Rack::Response provides a convenient interface to create a Rack
 | 
			
		||||
  # response.
 | 
			
		||||
  #
 | 
			
		||||
  # It allows setting of headers and cookies, and provides useful
 | 
			
		||||
  # defaults (an OK response with empty headers and body).
 | 
			
		||||
  #
 | 
			
		||||
  # You can use Response#write to iteratively generate your response,
 | 
			
		||||
  # but note that this is buffered by Rack::Response until you call
 | 
			
		||||
  # +finish+.  +finish+ however can take a block inside which calls to
 | 
			
		||||
  # +write+ are synchronous with the Rack response.
 | 
			
		||||
  #
 | 
			
		||||
  # Your application's +call+ should end returning Response#finish.
 | 
			
		||||
  class Response
 | 
			
		||||
    def self.[](status, headers, body)
 | 
			
		||||
      self.new(body, status, headers)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    CHUNKED = 'chunked'
 | 
			
		||||
    STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
 | 
			
		||||
 | 
			
		||||
    attr_accessor :length, :status, :body
 | 
			
		||||
    attr_reader :headers
 | 
			
		||||
 | 
			
		||||
    # Deprecated, use headers instead.
 | 
			
		||||
    def header
 | 
			
		||||
      warn 'Rack::Response#header is deprecated and will be removed in Rack 3.1', uplevel: 1
 | 
			
		||||
 | 
			
		||||
      headers
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Initialize the response object with the specified +body+, +status+
 | 
			
		||||
    # and +headers+.
 | 
			
		||||
    #
 | 
			
		||||
    # If the +body+ is +nil+, construct an empty response object with internal
 | 
			
		||||
    # buffering.
 | 
			
		||||
    #
 | 
			
		||||
    # If the +body+ responds to +to_str+, assume it's a string-like object and
 | 
			
		||||
    # construct a buffered response object containing using that string as the
 | 
			
		||||
    # initial contents of the buffer.
 | 
			
		||||
    #
 | 
			
		||||
    # Otherwise it is expected +body+ conforms to the normal requirements of a
 | 
			
		||||
    # Rack response body, typically implementing one of +each+ (enumerable
 | 
			
		||||
    # body) or +call+ (streaming body).
 | 
			
		||||
    #
 | 
			
		||||
    # The +status+ defaults to +200+ which is the "OK" HTTP status code. You
 | 
			
		||||
    # can provide any other valid status code.
 | 
			
		||||
    #
 | 
			
		||||
    # The +headers+ must be a +Hash+ of key-value header pairs which conform to
 | 
			
		||||
    # the Rack specification for response headers. The key must be a +String+
 | 
			
		||||
    # instance and the value can be either a +String+ or +Array+ instance.
 | 
			
		||||
    def initialize(body = nil, status = 200, headers = {})
 | 
			
		||||
      @status = status.to_i
 | 
			
		||||
 | 
			
		||||
      unless headers.is_a?(Hash)
 | 
			
		||||
        warn "Providing non-hash headers to Rack::Response is deprecated and will be removed in Rack 3.1", uplevel: 1
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      @headers = Headers.new
 | 
			
		||||
      # Convert headers input to a plain hash with lowercase keys.
 | 
			
		||||
      headers.each do |k, v|
 | 
			
		||||
        @headers[k] = v
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      @writer = self.method(:append)
 | 
			
		||||
 | 
			
		||||
      @block = nil
 | 
			
		||||
 | 
			
		||||
      # Keep track of whether we have expanded the user supplied body.
 | 
			
		||||
      if body.nil?
 | 
			
		||||
        @body = []
 | 
			
		||||
        @buffered = true
 | 
			
		||||
        @length = 0
 | 
			
		||||
      elsif body.respond_to?(:to_str)
 | 
			
		||||
        @body = [body]
 | 
			
		||||
        @buffered = true
 | 
			
		||||
        @length = body.to_str.bytesize
 | 
			
		||||
      else
 | 
			
		||||
        @body = body
 | 
			
		||||
        @buffered = nil # undetermined as of yet.
 | 
			
		||||
        @length = 0
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      yield self if block_given?
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def redirect(target, status = 302)
 | 
			
		||||
      self.status = status
 | 
			
		||||
      self.location = target
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def chunked?
 | 
			
		||||
      CHUNKED == get_header(TRANSFER_ENCODING)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def no_entity_body?
 | 
			
		||||
      # The response body is an enumerable body and it is not allowed to have an entity body.
 | 
			
		||||
      @body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
 | 
			
		||||
    end
 | 
			
		||||
    
 | 
			
		||||
    # Generate a response array consistent with the requirements of the SPEC.
 | 
			
		||||
    # @return [Array] a 3-tuple suitable of `[status, headers, body]`
 | 
			
		||||
    # which is suitable to be returned from the middleware `#call(env)` method.
 | 
			
		||||
    def finish(&block)
 | 
			
		||||
      if no_entity_body?
 | 
			
		||||
        delete_header CONTENT_TYPE
 | 
			
		||||
        delete_header CONTENT_LENGTH
 | 
			
		||||
        close
 | 
			
		||||
        return [@status, @headers, []]
 | 
			
		||||
      else
 | 
			
		||||
        if block_given?
 | 
			
		||||
          @block = block
 | 
			
		||||
          return [@status, @headers, self]
 | 
			
		||||
        else
 | 
			
		||||
          return [@status, @headers, @body]
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    alias to_a finish           # For *response
 | 
			
		||||
 | 
			
		||||
    def each(&callback)
 | 
			
		||||
      @body.each(&callback)
 | 
			
		||||
      @buffered = true
 | 
			
		||||
 | 
			
		||||
      if @block
 | 
			
		||||
        @writer = callback
 | 
			
		||||
        @block.call(self)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Append to body and update content-length.
 | 
			
		||||
    #
 | 
			
		||||
    # NOTE: Do not mix #write and direct #body access!
 | 
			
		||||
    #
 | 
			
		||||
    def write(chunk)
 | 
			
		||||
      buffered_body!
 | 
			
		||||
 | 
			
		||||
      @writer.call(chunk.to_s)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def close
 | 
			
		||||
      @body.close if @body.respond_to?(:close)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def empty?
 | 
			
		||||
      @block == nil && @body.empty?
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def has_header?(key)
 | 
			
		||||
      raise ArgumentError unless key.is_a?(String)
 | 
			
		||||
      @headers.key?(key)
 | 
			
		||||
    end
 | 
			
		||||
    def get_header(key)
 | 
			
		||||
      raise ArgumentError unless key.is_a?(String)
 | 
			
		||||
      @headers[key]
 | 
			
		||||
    end
 | 
			
		||||
    def set_header(key, value)
 | 
			
		||||
      raise ArgumentError unless key.is_a?(String)
 | 
			
		||||
      @headers[key] = value
 | 
			
		||||
    end
 | 
			
		||||
    def delete_header(key)
 | 
			
		||||
      raise ArgumentError unless key.is_a?(String)
 | 
			
		||||
      @headers.delete key
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    alias :[] :get_header
 | 
			
		||||
    alias :[]= :set_header
 | 
			
		||||
 | 
			
		||||
    module Helpers
 | 
			
		||||
      def invalid?;             status < 100 || status >= 600;        end
 | 
			
		||||
 | 
			
		||||
      def informational?;       status >= 100 && status < 200;        end
 | 
			
		||||
      def successful?;          status >= 200 && status < 300;        end
 | 
			
		||||
      def redirection?;         status >= 300 && status < 400;        end
 | 
			
		||||
      def client_error?;        status >= 400 && status < 500;        end
 | 
			
		||||
      def server_error?;        status >= 500 && status < 600;        end
 | 
			
		||||
 | 
			
		||||
      def ok?;                  status == 200;                        end
 | 
			
		||||
      def created?;             status == 201;                        end
 | 
			
		||||
      def accepted?;            status == 202;                        end
 | 
			
		||||
      def no_content?;          status == 204;                        end
 | 
			
		||||
      def moved_permanently?;   status == 301;                        end
 | 
			
		||||
      def bad_request?;         status == 400;                        end
 | 
			
		||||
      def unauthorized?;        status == 401;                        end
 | 
			
		||||
      def forbidden?;           status == 403;                        end
 | 
			
		||||
      def not_found?;           status == 404;                        end
 | 
			
		||||
      def method_not_allowed?;  status == 405;                        end
 | 
			
		||||
      def not_acceptable?;      status == 406;                        end
 | 
			
		||||
      def request_timeout?;     status == 408;                        end
 | 
			
		||||
      def precondition_failed?; status == 412;                        end
 | 
			
		||||
      def unprocessable?;       status == 422;                        end
 | 
			
		||||
 | 
			
		||||
      def redirect?;            [301, 302, 303, 307, 308].include? status; end
 | 
			
		||||
 | 
			
		||||
      def include?(header)
 | 
			
		||||
        has_header?(header)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Add a header that may have multiple values.
 | 
			
		||||
      #
 | 
			
		||||
      # Example:
 | 
			
		||||
      #   response.add_header 'vary', 'accept-encoding'
 | 
			
		||||
      #   response.add_header 'vary', 'cookie'
 | 
			
		||||
      #
 | 
			
		||||
      #   assert_equal 'accept-encoding,cookie', response.get_header('vary')
 | 
			
		||||
      #
 | 
			
		||||
      # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
 | 
			
		||||
      def add_header(key, value)
 | 
			
		||||
        raise ArgumentError unless key.is_a?(String)
 | 
			
		||||
 | 
			
		||||
        if value.nil?
 | 
			
		||||
          return get_header(key)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        value = value.to_s
 | 
			
		||||
 | 
			
		||||
        if header = get_header(key)
 | 
			
		||||
          if header.is_a?(Array)
 | 
			
		||||
            header << value
 | 
			
		||||
          else
 | 
			
		||||
            set_header(key, [header, value])
 | 
			
		||||
          end
 | 
			
		||||
        else
 | 
			
		||||
          set_header(key, value)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Get the content type of the response.
 | 
			
		||||
      def content_type
 | 
			
		||||
        get_header CONTENT_TYPE
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Set the content type of the response.
 | 
			
		||||
      def content_type=(content_type)
 | 
			
		||||
        set_header CONTENT_TYPE, content_type
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def media_type
 | 
			
		||||
        MediaType.type(content_type)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def media_type_params
 | 
			
		||||
        MediaType.params(content_type)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def content_length
 | 
			
		||||
        cl = get_header CONTENT_LENGTH
 | 
			
		||||
        cl ? cl.to_i : cl
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def location
 | 
			
		||||
        get_header "location"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def location=(location)
 | 
			
		||||
        set_header "location", location
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def set_cookie(key, value)
 | 
			
		||||
        add_header SET_COOKIE, Utils.set_cookie_header(key, value)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def delete_cookie(key, value = {})
 | 
			
		||||
        set_header(SET_COOKIE,
 | 
			
		||||
          Utils.delete_set_cookie_header!(
 | 
			
		||||
            get_header(SET_COOKIE), key, value
 | 
			
		||||
          )
 | 
			
		||||
        )
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def set_cookie_header
 | 
			
		||||
        get_header SET_COOKIE
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def set_cookie_header=(value)
 | 
			
		||||
        set_header SET_COOKIE, value
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def cache_control
 | 
			
		||||
        get_header CACHE_CONTROL
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def cache_control=(value)
 | 
			
		||||
        set_header CACHE_CONTROL, value
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
 | 
			
		||||
      def do_not_cache!
 | 
			
		||||
        set_header CACHE_CONTROL, "no-cache, must-revalidate"
 | 
			
		||||
        set_header EXPIRES, Time.now.httpdate
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Specify that the content should be cached.
 | 
			
		||||
      # @param duration [Integer] The number of seconds until the cache expires.
 | 
			
		||||
      # @option directive [String] The cache control directive, one of "public", "private", "no-cache" or "no-store".
 | 
			
		||||
      def cache!(duration = 3600, directive: "public")
 | 
			
		||||
        unless headers[CACHE_CONTROL] =~ /no-cache/
 | 
			
		||||
          set_header CACHE_CONTROL, "#{directive}, max-age=#{duration}"
 | 
			
		||||
          set_header EXPIRES, (Time.now + duration).httpdate
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def etag
 | 
			
		||||
        get_header ETAG
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def etag=(value)
 | 
			
		||||
        set_header ETAG, value
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    protected
 | 
			
		||||
 | 
			
		||||
      def buffered_body!
 | 
			
		||||
        if @buffered.nil?
 | 
			
		||||
          if @body.is_a?(Array)
 | 
			
		||||
            # The user supplied body was an array:
 | 
			
		||||
            @body = @body.compact
 | 
			
		||||
            @body.each do |part|
 | 
			
		||||
              @length += part.to_s.bytesize
 | 
			
		||||
            end
 | 
			
		||||
          elsif @body.respond_to?(:each)
 | 
			
		||||
            # Turn the user supplied body into a buffered array:
 | 
			
		||||
            body = @body
 | 
			
		||||
            @body = Array.new
 | 
			
		||||
 | 
			
		||||
            body.each do |part|
 | 
			
		||||
              @writer.call(part.to_s)
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
            body.close if body.respond_to?(:close)
 | 
			
		||||
 | 
			
		||||
            @buffered = true
 | 
			
		||||
          else
 | 
			
		||||
            @buffered = false
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        return @buffered
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def append(chunk)
 | 
			
		||||
        @body << chunk
 | 
			
		||||
 | 
			
		||||
        unless chunked?
 | 
			
		||||
          @length += chunk.bytesize
 | 
			
		||||
          set_header(CONTENT_LENGTH, @length.to_s)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        return chunk
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    include Helpers
 | 
			
		||||
 | 
			
		||||
    class Raw
 | 
			
		||||
      include Helpers
 | 
			
		||||
 | 
			
		||||
      attr_reader :headers
 | 
			
		||||
      attr_accessor :status
 | 
			
		||||
 | 
			
		||||
      def initialize(status, headers)
 | 
			
		||||
        @status = status
 | 
			
		||||
        @headers = headers
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def has_header?(key)
 | 
			
		||||
        headers.key?(key)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def get_header(key)
 | 
			
		||||
        headers[key]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def set_header(key, value)
 | 
			
		||||
        headers[key] = value
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def delete_header(key)
 | 
			
		||||
        headers.delete(key)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,113 +0,0 @@
 | 
			
		||||
# -*- 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
 | 
			
		||||
@ -1,35 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,167 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require_relative 'constants'
 | 
			
		||||
require_relative 'utils'
 | 
			
		||||
require_relative 'body_proxy'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
 | 
			
		||||
  # = Sendfile
 | 
			
		||||
  #
 | 
			
		||||
  # The Sendfile middleware intercepts responses whose body is being
 | 
			
		||||
  # served from a file and replaces it with a server specific x-sendfile
 | 
			
		||||
  # header. The web server is then responsible for writing the file contents
 | 
			
		||||
  # to the client. This can dramatically reduce the amount of work required
 | 
			
		||||
  # by the Ruby backend and takes advantage of the web server's optimized file
 | 
			
		||||
  # delivery code.
 | 
			
		||||
  #
 | 
			
		||||
  # In order to take advantage of this middleware, the response body must
 | 
			
		||||
  # respond to +to_path+ and the request must include an x-sendfile-type
 | 
			
		||||
  # header. Rack::Files and other components implement +to_path+ so there's
 | 
			
		||||
  # rarely anything you need to do in your application. The x-sendfile-type
 | 
			
		||||
  # header is typically set in your web servers configuration. The following
 | 
			
		||||
  # sections attempt to document
 | 
			
		||||
  #
 | 
			
		||||
  # === Nginx
 | 
			
		||||
  #
 | 
			
		||||
  # Nginx supports the x-accel-redirect header. This is similar to x-sendfile
 | 
			
		||||
  # but requires parts of the filesystem to be mapped into a private URL
 | 
			
		||||
  # hierarchy.
 | 
			
		||||
  #
 | 
			
		||||
  # The following example shows the Nginx configuration required to create
 | 
			
		||||
  # a private "/files/" area, enable x-accel-redirect, and pass the special
 | 
			
		||||
  # x-sendfile-type and x-accel-mapping headers to the backend:
 | 
			
		||||
  #
 | 
			
		||||
  #   location ~ /files/(.*) {
 | 
			
		||||
  #     internal;
 | 
			
		||||
  #     alias /var/www/$1;
 | 
			
		||||
  #   }
 | 
			
		||||
  #
 | 
			
		||||
  #   location / {
 | 
			
		||||
  #     proxy_redirect     off;
 | 
			
		||||
  #
 | 
			
		||||
  #     proxy_set_header   Host                $host;
 | 
			
		||||
  #     proxy_set_header   X-Real-IP           $remote_addr;
 | 
			
		||||
  #     proxy_set_header   X-Forwarded-For     $proxy_add_x_forwarded_for;
 | 
			
		||||
  #
 | 
			
		||||
  #     proxy_set_header   x-sendfile-type     x-accel-redirect;
 | 
			
		||||
  #     proxy_set_header   x-accel-mapping     /var/www/=/files/;
 | 
			
		||||
  #
 | 
			
		||||
  #     proxy_pass         http://127.0.0.1:8080/;
 | 
			
		||||
  #   }
 | 
			
		||||
  #
 | 
			
		||||
  # Note that the x-sendfile-type header must be set exactly as shown above.
 | 
			
		||||
  # The x-accel-mapping header should specify the location on the file system,
 | 
			
		||||
  # followed by an equals sign (=), followed name of the private URL pattern
 | 
			
		||||
  # that it maps to. The middleware performs a simple substitution on the
 | 
			
		||||
  # resulting path.
 | 
			
		||||
  #
 | 
			
		||||
  # See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
 | 
			
		||||
  #
 | 
			
		||||
  # === lighttpd
 | 
			
		||||
  #
 | 
			
		||||
  # Lighttpd has supported some variation of the x-sendfile header for some
 | 
			
		||||
  # time, although only recent version support x-sendfile in a reverse proxy
 | 
			
		||||
  # configuration.
 | 
			
		||||
  #
 | 
			
		||||
  #   $HTTP["host"] == "example.com" {
 | 
			
		||||
  #      proxy-core.protocol = "http"
 | 
			
		||||
  #      proxy-core.balancer = "round-robin"
 | 
			
		||||
  #      proxy-core.backends = (
 | 
			
		||||
  #        "127.0.0.1:8000",
 | 
			
		||||
  #        "127.0.0.1:8001",
 | 
			
		||||
  #        ...
 | 
			
		||||
  #      )
 | 
			
		||||
  #
 | 
			
		||||
  #      proxy-core.allow-x-sendfile = "enable"
 | 
			
		||||
  #      proxy-core.rewrite-request = (
 | 
			
		||||
  #        "x-sendfile-type" => (".*" => "x-sendfile")
 | 
			
		||||
  #      )
 | 
			
		||||
  #    }
 | 
			
		||||
  #
 | 
			
		||||
  # See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore
 | 
			
		||||
  #
 | 
			
		||||
  # === Apache
 | 
			
		||||
  #
 | 
			
		||||
  # x-sendfile is supported under Apache 2.x using a separate module:
 | 
			
		||||
  #
 | 
			
		||||
  # https://tn123.org/mod_xsendfile/
 | 
			
		||||
  #
 | 
			
		||||
  # Once the module is compiled and installed, you can enable it using
 | 
			
		||||
  # XSendFile config directive:
 | 
			
		||||
  #
 | 
			
		||||
  #   RequestHeader Set x-sendfile-type x-sendfile
 | 
			
		||||
  #   ProxyPassReverse / http://localhost:8001/
 | 
			
		||||
  #   XSendFile on
 | 
			
		||||
  #
 | 
			
		||||
  # === Mapping parameter
 | 
			
		||||
  #
 | 
			
		||||
  # The third parameter allows for an overriding extension of the
 | 
			
		||||
  # x-accel-mapping header. Mappings should be provided in tuples of internal to
 | 
			
		||||
  # external. The internal values may contain regular expression syntax, they
 | 
			
		||||
  # will be matched with case indifference.
 | 
			
		||||
 | 
			
		||||
  class Sendfile
 | 
			
		||||
    def initialize(app, variation = nil, mappings = [])
 | 
			
		||||
      @app = app
 | 
			
		||||
      @variation = variation
 | 
			
		||||
      @mappings = mappings.map do |internal, external|
 | 
			
		||||
        [/^#{internal}/i, external]
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def call(env)
 | 
			
		||||
      _, headers, body = response = @app.call(env)
 | 
			
		||||
 | 
			
		||||
      if body.respond_to?(:to_path)
 | 
			
		||||
        case type = variation(env)
 | 
			
		||||
        when /x-accel-redirect/i
 | 
			
		||||
          path = ::File.expand_path(body.to_path)
 | 
			
		||||
          if url = map_accel_path(env, path)
 | 
			
		||||
            headers[CONTENT_LENGTH] = '0'
 | 
			
		||||
            # '?' must be percent-encoded because it is not query string but a part of path
 | 
			
		||||
            headers[type.downcase] = ::Rack::Utils.escape_path(url).gsub('?', '%3F')
 | 
			
		||||
            obody = body
 | 
			
		||||
            response[2] = Rack::BodyProxy.new([]) do
 | 
			
		||||
              obody.close if obody.respond_to?(:close)
 | 
			
		||||
            end
 | 
			
		||||
          else
 | 
			
		||||
            env[RACK_ERRORS].puts "x-accel-mapping header missing"
 | 
			
		||||
          end
 | 
			
		||||
        when /x-sendfile|x-lighttpd-send-file/i
 | 
			
		||||
          path = ::File.expand_path(body.to_path)
 | 
			
		||||
          headers[CONTENT_LENGTH] = '0'
 | 
			
		||||
          headers[type.downcase] = path
 | 
			
		||||
          obody = body
 | 
			
		||||
          response[2] = Rack::BodyProxy.new([]) do
 | 
			
		||||
            obody.close if obody.respond_to?(:close)
 | 
			
		||||
          end
 | 
			
		||||
        when '', nil
 | 
			
		||||
        else
 | 
			
		||||
          env[RACK_ERRORS].puts "Unknown x-sendfile variation: '#{type}'.\n"
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      response
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
    def variation(env)
 | 
			
		||||
      @variation ||
 | 
			
		||||
        env['sendfile.type'] ||
 | 
			
		||||
        env['HTTP_X_SENDFILE_TYPE']
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def map_accel_path(env, path)
 | 
			
		||||
      if mapping = @mappings.find { |internal, _| internal =~ path }
 | 
			
		||||
        path.sub(*mapping)
 | 
			
		||||
      elsif mapping = env['HTTP_X_ACCEL_MAPPING']
 | 
			
		||||
        mapping.split(',').map(&:strip).each do |m|
 | 
			
		||||
          internal, external = m.split('=', 2).map(&:strip)
 | 
			
		||||
          new_path = path.sub(/^#{internal}/i, external)
 | 
			
		||||
          return new_path unless path == new_path
 | 
			
		||||
        end
 | 
			
		||||
        path
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,403 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'ostruct'
 | 
			
		||||
require 'erb'
 | 
			
		||||
 | 
			
		||||
require_relative 'constants'
 | 
			
		||||
require_relative 'utils'
 | 
			
		||||
require_relative 'request'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Rack::ShowExceptions catches all exceptions raised from the app it
 | 
			
		||||
  # wraps.  It shows a useful backtrace with the sourcefile and
 | 
			
		||||
  # clickable context, the whole Rack environment and the request
 | 
			
		||||
  # data.
 | 
			
		||||
  #
 | 
			
		||||
  # Be careful when you use this on public-facing sites as it could
 | 
			
		||||
  # reveal information helpful to attackers.
 | 
			
		||||
 | 
			
		||||
  class ShowExceptions
 | 
			
		||||
    CONTEXT = 7
 | 
			
		||||
 | 
			
		||||
    def initialize(app)
 | 
			
		||||
      @app = app
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def call(env)
 | 
			
		||||
      @app.call(env)
 | 
			
		||||
    rescue StandardError, LoadError, SyntaxError => e
 | 
			
		||||
      exception_string = dump_exception(e)
 | 
			
		||||
 | 
			
		||||
      env[RACK_ERRORS].puts(exception_string)
 | 
			
		||||
      env[RACK_ERRORS].flush
 | 
			
		||||
 | 
			
		||||
      if accepts_html?(env)
 | 
			
		||||
        content_type = "text/html"
 | 
			
		||||
        body = pretty(env, e)
 | 
			
		||||
      else
 | 
			
		||||
        content_type = "text/plain"
 | 
			
		||||
        body = exception_string
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      [
 | 
			
		||||
        500,
 | 
			
		||||
        {
 | 
			
		||||
          CONTENT_TYPE => content_type,
 | 
			
		||||
          CONTENT_LENGTH => body.bytesize.to_s,
 | 
			
		||||
        },
 | 
			
		||||
        [body],
 | 
			
		||||
      ]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def prefers_plaintext?(env)
 | 
			
		||||
      !accepts_html?(env)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def accepts_html?(env)
 | 
			
		||||
      Rack::Utils.best_q_match(env["HTTP_ACCEPT"], %w[text/html])
 | 
			
		||||
    end
 | 
			
		||||
    private :accepts_html?
 | 
			
		||||
 | 
			
		||||
    def dump_exception(exception)
 | 
			
		||||
      if exception.respond_to?(:detailed_message)
 | 
			
		||||
        message = exception.detailed_message(highlight: false)
 | 
			
		||||
      else
 | 
			
		||||
        message = exception.message
 | 
			
		||||
      end
 | 
			
		||||
      string = "#{exception.class}: #{message}\n".dup
 | 
			
		||||
      string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
 | 
			
		||||
      string
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def pretty(env, exception)
 | 
			
		||||
      req = Rack::Request.new(env)
 | 
			
		||||
 | 
			
		||||
      # This double assignment is to prevent an "unused variable" warning.
 | 
			
		||||
      # Yes, it is dumb, but I don't like Ruby yelling at me.
 | 
			
		||||
      path = path = (req.script_name + req.path_info).squeeze("/")
 | 
			
		||||
 | 
			
		||||
      # This double assignment is to prevent an "unused variable" warning.
 | 
			
		||||
      # Yes, it is dumb, but I don't like Ruby yelling at me.
 | 
			
		||||
      frames = frames = exception.backtrace.map { |line|
 | 
			
		||||
        frame = OpenStruct.new
 | 
			
		||||
        if line =~ /(.*?):(\d+)(:in `(.*)')?/
 | 
			
		||||
          frame.filename = $1
 | 
			
		||||
          frame.lineno = $2.to_i
 | 
			
		||||
          frame.function = $4
 | 
			
		||||
 | 
			
		||||
          begin
 | 
			
		||||
            lineno = frame.lineno - 1
 | 
			
		||||
            lines = ::File.readlines(frame.filename)
 | 
			
		||||
            frame.pre_context_lineno = [lineno - CONTEXT, 0].max
 | 
			
		||||
            frame.pre_context = lines[frame.pre_context_lineno...lineno]
 | 
			
		||||
            frame.context_line = lines[lineno].chomp
 | 
			
		||||
            frame.post_context_lineno = [lineno + CONTEXT, lines.size].min
 | 
			
		||||
            frame.post_context = lines[lineno + 1..frame.post_context_lineno]
 | 
			
		||||
          rescue
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          frame
 | 
			
		||||
        else
 | 
			
		||||
          nil
 | 
			
		||||
        end
 | 
			
		||||
      }.compact
 | 
			
		||||
 | 
			
		||||
      template.result(binding)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def template
 | 
			
		||||
      TEMPLATE
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def h(obj)                  # :nodoc:
 | 
			
		||||
      case obj
 | 
			
		||||
      when String
 | 
			
		||||
        Utils.escape_html(obj)
 | 
			
		||||
      else
 | 
			
		||||
        Utils.escape_html(obj.inspect)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # :stopdoc:
 | 
			
		||||
 | 
			
		||||
    # adapted from Django <www.djangoproject.com>
 | 
			
		||||
    # 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(/^      /, ''))
 | 
			
		||||
      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 | 
			
		||||
      <html lang="en">
 | 
			
		||||
      <head>
 | 
			
		||||
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
 | 
			
		||||
        <meta name="robots" content="NONE,NOARCHIVE" />
 | 
			
		||||
        <title><%=h exception.class %> at <%=h path %></title>
 | 
			
		||||
        <style type="text/css">
 | 
			
		||||
          html * { padding:0; margin:0; }
 | 
			
		||||
          body * { padding:10px 20px; }
 | 
			
		||||
          body * * { padding:0; }
 | 
			
		||||
          body { font:small sans-serif; }
 | 
			
		||||
          body>div { border-bottom:1px solid #ddd; }
 | 
			
		||||
          h1 { font-weight:normal; }
 | 
			
		||||
          h2 { margin-bottom:.8em; }
 | 
			
		||||
          h2 span { font-size:80%; color:#666; font-weight:normal; }
 | 
			
		||||
          h3 { margin:1em 0 .5em 0; }
 | 
			
		||||
          h4 { margin:0 0 .5em 0; font-weight: normal; }
 | 
			
		||||
          table {
 | 
			
		||||
              border:1px solid #ccc; border-collapse: collapse; background:white; }
 | 
			
		||||
          tbody td, tbody th { vertical-align:top; padding:2px 3px; }
 | 
			
		||||
          thead th {
 | 
			
		||||
              padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
 | 
			
		||||
              font-weight:normal; font-size:11px; border:1px solid #ddd; }
 | 
			
		||||
          tbody th { text-align:right; color:#666; padding-right:.5em; }
 | 
			
		||||
          table.vars { margin:5px 0 2px 40px; }
 | 
			
		||||
          table.vars td, table.req td { font-family:monospace; }
 | 
			
		||||
          table td.code { width:100%;}
 | 
			
		||||
          table td.code div { overflow:hidden; }
 | 
			
		||||
          table.source th { color:#666; }
 | 
			
		||||
          table.source td {
 | 
			
		||||
              font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
 | 
			
		||||
          ul.traceback { list-style-type:none; }
 | 
			
		||||
          ul.traceback li.frame { margin-bottom:1em; }
 | 
			
		||||
          div.context { margin: 10px 0; }
 | 
			
		||||
          div.context ol {
 | 
			
		||||
              padding-left:30px; margin:0 10px; list-style-position: inside; }
 | 
			
		||||
          div.context ol li {
 | 
			
		||||
              font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
 | 
			
		||||
          div.context ol.context-line li { color:black; background-color:#ccc; }
 | 
			
		||||
          div.context ol.context-line li span { float: right; }
 | 
			
		||||
          div.commands { margin-left: 40px; }
 | 
			
		||||
          div.commands a { color:black; text-decoration:none; }
 | 
			
		||||
          #summary { background: #ffc; }
 | 
			
		||||
          #summary h2 { font-family: monospace; font-weight: normal; color: #666; white-space: pre-wrap; }
 | 
			
		||||
          #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
 | 
			
		||||
          #summary ul#quicklinks li { float: left; padding: 0 1em; }
 | 
			
		||||
          #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
 | 
			
		||||
          #explanation { background:#eee; }
 | 
			
		||||
          #template, #template-not-exist { background:#f6f6f6; }
 | 
			
		||||
          #template-not-exist ul { margin: 0 0 0 20px; }
 | 
			
		||||
          #traceback { background:#eee; }
 | 
			
		||||
          #requestinfo { background:#f6f6f6; padding-left:120px; }
 | 
			
		||||
          #summary table { border:none; background:transparent; }
 | 
			
		||||
          #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
 | 
			
		||||
          #requestinfo h3 { margin-bottom:-1em; }
 | 
			
		||||
          .error { background: #ffc; }
 | 
			
		||||
          .specific { color:#cc3300; font-weight:bold; }
 | 
			
		||||
        </style>
 | 
			
		||||
        <script type="text/javascript">
 | 
			
		||||
        //<!--
 | 
			
		||||
          function getElementsByClassName(oElm, strTagName, strClassName){
 | 
			
		||||
              // Written by Jonathan Snook, http://www.snook.ca/jon;
 | 
			
		||||
              // Add-ons by Robert Nyman, http://www.robertnyman.com
 | 
			
		||||
              var arrElements = (strTagName == "*" && document.all)? document.all :
 | 
			
		||||
              oElm.getElementsByTagName(strTagName);
 | 
			
		||||
              var arrReturnElements = new Array();
 | 
			
		||||
              strClassName = strClassName.replace(/\-/g, "\\-");
 | 
			
		||||
              var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
 | 
			
		||||
              var oElement;
 | 
			
		||||
              for(var i=0; i<arrElements.length; i++){
 | 
			
		||||
                  oElement = arrElements[i];
 | 
			
		||||
                  if(oRegExp.test(oElement.className)){
 | 
			
		||||
                      arrReturnElements.push(oElement);
 | 
			
		||||
                  }
 | 
			
		||||
              }
 | 
			
		||||
              return (arrReturnElements)
 | 
			
		||||
          }
 | 
			
		||||
          function hideAll(elems) {
 | 
			
		||||
            for (var e = 0; e < elems.length; e++) {
 | 
			
		||||
              elems[e].style.display = 'none';
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          window.onload = function() {
 | 
			
		||||
            hideAll(getElementsByClassName(document, 'table', 'vars'));
 | 
			
		||||
            hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
 | 
			
		||||
            hideAll(getElementsByClassName(document, 'ol', 'post-context'));
 | 
			
		||||
          }
 | 
			
		||||
          function toggle() {
 | 
			
		||||
            for (var i = 0; i < arguments.length; i++) {
 | 
			
		||||
              var e = document.getElementById(arguments[i]);
 | 
			
		||||
              if (e) {
 | 
			
		||||
                e.style.display = e.style.display == 'none' ? 'block' : 'none';
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
          }
 | 
			
		||||
          function varToggle(link, id) {
 | 
			
		||||
            toggle('v' + id);
 | 
			
		||||
            var s = link.getElementsByTagName('span')[0];
 | 
			
		||||
            var uarr = String.fromCharCode(0x25b6);
 | 
			
		||||
            var darr = String.fromCharCode(0x25bc);
 | 
			
		||||
            s.innerHTML = s.innerHTML == uarr ? darr : uarr;
 | 
			
		||||
            return false;
 | 
			
		||||
          }
 | 
			
		||||
          //-->
 | 
			
		||||
        </script>
 | 
			
		||||
      </head>
 | 
			
		||||
      <body>
 | 
			
		||||
 | 
			
		||||
      <div id="summary">
 | 
			
		||||
        <h1><%=h exception.class %> at <%=h path %></h1>
 | 
			
		||||
      <% if  exception.respond_to?(:detailed_message) %>
 | 
			
		||||
        <h2><%=h exception.detailed_message(highlight: false) %></h2>
 | 
			
		||||
      <% else  %>
 | 
			
		||||
        <h2><%=h exception.message %></h2>
 | 
			
		||||
      <% end  %>
 | 
			
		||||
        <table><tr>
 | 
			
		||||
          <th>Ruby</th>
 | 
			
		||||
          <td>
 | 
			
		||||
      <% if  first = frames.first %>
 | 
			
		||||
            <code><%=h first.filename %></code>: in <code><%=h first.function %></code>, line <%=h frames.first.lineno %>
 | 
			
		||||
      <% else  %>
 | 
			
		||||
            unknown location
 | 
			
		||||
      <% end  %>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr><tr>
 | 
			
		||||
          <th>Web</th>
 | 
			
		||||
          <td><code><%=h req.request_method %> <%=h(req.host + path)%></code></td>
 | 
			
		||||
        </tr></table>
 | 
			
		||||
 | 
			
		||||
        <h3>Jump to:</h3>
 | 
			
		||||
        <ul id="quicklinks">
 | 
			
		||||
          <li><a href="#get-info">GET</a></li>
 | 
			
		||||
          <li><a href="#post-info">POST</a></li>
 | 
			
		||||
          <li><a href="#cookie-info">Cookies</a></li>
 | 
			
		||||
          <li><a href="#env-info">ENV</a></li>
 | 
			
		||||
        </ul>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div id="traceback">
 | 
			
		||||
        <h2>Traceback <span>(innermost first)</span></h2>
 | 
			
		||||
        <ul class="traceback">
 | 
			
		||||
      <% frames.each  { |frame| %>
 | 
			
		||||
            <li class="frame">
 | 
			
		||||
              <code><%=h frame.filename %></code>: in <code><%=h frame.function %></code>
 | 
			
		||||
 | 
			
		||||
                <% if  frame.context_line %>
 | 
			
		||||
                <div class="context" id="c<%=h frame.object_id %>">
 | 
			
		||||
                    <% if  frame.pre_context %>
 | 
			
		||||
                    <ol start="<%=h frame.pre_context_lineno+1 %>" class="pre-context" id="pre<%=h frame.object_id %>">
 | 
			
		||||
                      <% frame.pre_context.each  { |line| %>
 | 
			
		||||
                      <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
 | 
			
		||||
                      <% }  %>
 | 
			
		||||
                    </ol>
 | 
			
		||||
                    <% end  %>
 | 
			
		||||
 | 
			
		||||
                  <ol start="<%=h frame.lineno %>" class="context-line">
 | 
			
		||||
                    <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h frame.context_line %><span>...</span></li></ol>
 | 
			
		||||
 | 
			
		||||
                    <% if  frame.post_context %>
 | 
			
		||||
                    <ol start='<%=h frame.lineno+1 %>' class="post-context" id="post<%=h frame.object_id %>">
 | 
			
		||||
                      <% frame.post_context.each  { |line| %>
 | 
			
		||||
                      <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
 | 
			
		||||
                      <% }  %>
 | 
			
		||||
                    </ol>
 | 
			
		||||
                    <% end  %>
 | 
			
		||||
                </div>
 | 
			
		||||
                <% end  %>
 | 
			
		||||
            </li>
 | 
			
		||||
      <% }  %>
 | 
			
		||||
        </ul>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div id="requestinfo">
 | 
			
		||||
        <h2>Request information</h2>
 | 
			
		||||
 | 
			
		||||
        <h3 id="get-info">GET</h3>
 | 
			
		||||
        <% if  req.GET and not req.GET.empty? %>
 | 
			
		||||
          <table class="req">
 | 
			
		||||
            <thead>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <th>Variable</th>
 | 
			
		||||
                <th>Value</th>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
                <% req.GET.sort_by  { |k, v| k.to_s }.each { |key, val| %>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><%=h key %></td>
 | 
			
		||||
                  <td class="code"><div><%=h val.inspect %></div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <% }  %>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        <% else  %>
 | 
			
		||||
          <p>No GET data.</p>
 | 
			
		||||
        <% end  %>
 | 
			
		||||
 | 
			
		||||
        <h3 id="post-info">POST</h3>
 | 
			
		||||
        <% if  ((req.POST and not req.POST.empty?) rescue (no_post_data = "Invalid POST data"; nil)) %>
 | 
			
		||||
          <table class="req">
 | 
			
		||||
            <thead>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <th>Variable</th>
 | 
			
		||||
                <th>Value</th>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
                <% req.POST.sort_by  { |k, v| k.to_s }.each { |key, val| %>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><%=h key %></td>
 | 
			
		||||
                  <td class="code"><div><%=h val.inspect %></div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <% }  %>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        <% else  %>
 | 
			
		||||
          <p><%= no_post_data || "No POST data" %>.</p>
 | 
			
		||||
        <% end  %>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        <h3 id="cookie-info">COOKIES</h3>
 | 
			
		||||
        <% unless  req.cookies.empty? %>
 | 
			
		||||
          <table class="req">
 | 
			
		||||
            <thead>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <th>Variable</th>
 | 
			
		||||
                <th>Value</th>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <% req.cookies.each  { |key, val| %>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><%=h key %></td>
 | 
			
		||||
                  <td class="code"><div><%=h val.inspect %></div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              <% }  %>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        <% else  %>
 | 
			
		||||
          <p>No cookie data.</p>
 | 
			
		||||
        <% end  %>
 | 
			
		||||
 | 
			
		||||
        <h3 id="env-info">Rack ENV</h3>
 | 
			
		||||
          <table class="req">
 | 
			
		||||
            <thead>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <th>Variable</th>
 | 
			
		||||
                <th>Value</th>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
                <% env.sort_by  { |k, v| k.to_s }.each { |key, val| %>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><%=h key %></td>
 | 
			
		||||
                  <td class="code"><div><%=h val.inspect %></div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <% }  %>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div id="explanation">
 | 
			
		||||
        <p>
 | 
			
		||||
          You're seeing this error because you use <code>Rack::ShowExceptions</code>.
 | 
			
		||||
        </p>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      </body>
 | 
			
		||||
      </html>
 | 
			
		||||
    HTML
 | 
			
		||||
 | 
			
		||||
    # :startdoc:
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,123 +0,0 @@
 | 
			
		||||
# 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 <tt>rack.showstatus.detail</tt>
 | 
			
		||||
  # 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 <www.djangoproject.com>
 | 
			
		||||
# 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'
 | 
			
		||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
 | 
			
		||||
  <title><%=h message %> at <%=h req.script_name + req.path_info %></title>
 | 
			
		||||
  <meta name="robots" content="NONE,NOARCHIVE" />
 | 
			
		||||
  <style type="text/css">
 | 
			
		||||
    html * { padding:0; margin:0; }
 | 
			
		||||
    body * { padding:10px 20px; }
 | 
			
		||||
    body * * { padding:0; }
 | 
			
		||||
    body { font:small sans-serif; background:#eee; }
 | 
			
		||||
    body>div { border-bottom:1px solid #ddd; }
 | 
			
		||||
    h1 { font-weight:normal; margin-bottom:.4em; }
 | 
			
		||||
    h1 span { font-size:60%; color:#666; font-weight:normal; }
 | 
			
		||||
    table { border:none; border-collapse: collapse; width:100%; }
 | 
			
		||||
    td, th { vertical-align:top; padding:2px 3px; }
 | 
			
		||||
    th { width:12em; text-align:right; color:#666; padding-right:.5em; }
 | 
			
		||||
    #info { background:#f6f6f6; }
 | 
			
		||||
    #info ol { margin: 0.5em 4em; }
 | 
			
		||||
    #info ol li { font-family: monospace; }
 | 
			
		||||
    #summary { background: #ffc; }
 | 
			
		||||
    #explanation { background:#eee; border-bottom: 0px none; }
 | 
			
		||||
  </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <div id="summary">
 | 
			
		||||
    <h1><%=h message %> <span>(<%= status.to_i %>)</span></h1>
 | 
			
		||||
    <table class="meta">
 | 
			
		||||
      <tr>
 | 
			
		||||
        <th>Request Method:</th>
 | 
			
		||||
        <td><%=h req.request_method %></td>
 | 
			
		||||
      </tr>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <th>Request URL:</th>
 | 
			
		||||
      <td><%=h req.url %></td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </table>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div id="info">
 | 
			
		||||
    <p><%=h detail %></p>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div id="explanation">
 | 
			
		||||
    <p>
 | 
			
		||||
    You're seeing this error because you use <code>Rack::ShowStatus</code>.
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
HTML
 | 
			
		||||
 | 
			
		||||
    # :startdoc:
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,187 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require_relative 'constants'
 | 
			
		||||
require_relative 'files'
 | 
			
		||||
require_relative 'mime'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
 | 
			
		||||
  # The Rack::Static middleware intercepts requests for static files
 | 
			
		||||
  # (javascript files, images, stylesheets, etc) based on the url prefixes or
 | 
			
		||||
  # route mappings passed in the options, and serves them using a Rack::Files
 | 
			
		||||
  # object. This allows a Rack stack to serve both static and dynamic content.
 | 
			
		||||
  #
 | 
			
		||||
  # Examples:
 | 
			
		||||
  #
 | 
			
		||||
  # Serve all requests beginning with /media from the "media" folder located
 | 
			
		||||
  # in the current directory (ie media/*):
 | 
			
		||||
  #
 | 
			
		||||
  #     use Rack::Static, :urls => ["/media"]
 | 
			
		||||
  #
 | 
			
		||||
  # Same as previous, but instead of returning 404 for missing files under
 | 
			
		||||
  # /media, call the next middleware:
 | 
			
		||||
  #
 | 
			
		||||
  #     use Rack::Static, :urls => ["/media"], :cascade => true
 | 
			
		||||
  #
 | 
			
		||||
  # Serve all requests beginning with /css or /images from the folder "public"
 | 
			
		||||
  # in the current directory (ie public/css/* and public/images/*):
 | 
			
		||||
  #
 | 
			
		||||
  #     use Rack::Static, :urls => ["/css", "/images"], :root => "public"
 | 
			
		||||
  #
 | 
			
		||||
  # Serve all requests to / with "index.html" from the folder "public" in the
 | 
			
		||||
  # current directory (ie public/index.html):
 | 
			
		||||
  #
 | 
			
		||||
  #     use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public'
 | 
			
		||||
  #
 | 
			
		||||
  # Serve all requests normally from the folder "public" in the current
 | 
			
		||||
  # directory but uses index.html as default route for "/"
 | 
			
		||||
  #
 | 
			
		||||
  #     use Rack::Static, :urls => [""], :root => 'public', :index =>
 | 
			
		||||
  #     'index.html'
 | 
			
		||||
  #
 | 
			
		||||
  # Set custom HTTP Headers for based on rules:
 | 
			
		||||
  #
 | 
			
		||||
  #     use Rack::Static, :root => 'public',
 | 
			
		||||
  #         :header_rules => [
 | 
			
		||||
  #           [rule, {header_field => content, header_field => content}],
 | 
			
		||||
  #           [rule, {header_field => content}]
 | 
			
		||||
  #         ]
 | 
			
		||||
  #
 | 
			
		||||
  #  Rules for selecting files:
 | 
			
		||||
  #
 | 
			
		||||
  #  1) All files
 | 
			
		||||
  #     Provide the :all symbol
 | 
			
		||||
  #     :all => Matches every file
 | 
			
		||||
  #
 | 
			
		||||
  #  2) Folders
 | 
			
		||||
  #     Provide the folder path as a string
 | 
			
		||||
  #     '/folder' or '/folder/subfolder' => Matches files in a certain folder
 | 
			
		||||
  #
 | 
			
		||||
  #  3) File Extensions
 | 
			
		||||
  #     Provide the file extensions as an array
 | 
			
		||||
  #     ['css', 'js'] or %w(css js) => Matches files ending in .css or .js
 | 
			
		||||
  #
 | 
			
		||||
  #  4) Regular Expressions / Regexp
 | 
			
		||||
  #     Provide a regular expression
 | 
			
		||||
  #     %r{\.(?:css|js)\z} => Matches files ending in .css or .js
 | 
			
		||||
  #     /\.(?:eot|ttf|otf|woff2|woff|svg)\z/ => Matches files ending in
 | 
			
		||||
  #       the most common web font formats (.eot, .ttf, .otf, .woff2, .woff, .svg)
 | 
			
		||||
  #       Note: This Regexp is available as a shortcut, using the :fonts rule
 | 
			
		||||
  #
 | 
			
		||||
  #  5) Font Shortcut
 | 
			
		||||
  #     Provide the :fonts symbol
 | 
			
		||||
  #     :fonts => Uses the Regexp rule stated right above to match all common web font endings
 | 
			
		||||
  #
 | 
			
		||||
  #  Rule Ordering:
 | 
			
		||||
  #    Rules are applied in the order that they are provided.
 | 
			
		||||
  #    List rather general rules above special ones.
 | 
			
		||||
  #
 | 
			
		||||
  #  Complete example use case including HTTP header rules:
 | 
			
		||||
  #
 | 
			
		||||
  #     use Rack::Static, :root => 'public',
 | 
			
		||||
  #         :header_rules => [
 | 
			
		||||
  #           # Cache all static files in public caches (e.g. Rack::Cache)
 | 
			
		||||
  #           #  as well as in the browser
 | 
			
		||||
  #           [:all, {'cache-control' => 'public, max-age=31536000'}],
 | 
			
		||||
  #
 | 
			
		||||
  #           # Provide web fonts with cross-origin access-control-headers
 | 
			
		||||
  #           #  Firefox requires this when serving assets using a Content Delivery Network
 | 
			
		||||
  #           [:fonts, {'access-control-allow-origin' => '*'}]
 | 
			
		||||
  #         ]
 | 
			
		||||
  #
 | 
			
		||||
  class Static
 | 
			
		||||
    def initialize(app, options = {})
 | 
			
		||||
      @app = app
 | 
			
		||||
      @urls = options[:urls] || ["/favicon.ico"]
 | 
			
		||||
      @index = options[:index]
 | 
			
		||||
      @gzip = options[:gzip]
 | 
			
		||||
      @cascade = options[:cascade]
 | 
			
		||||
      root = options[:root] || Dir.pwd
 | 
			
		||||
 | 
			
		||||
      # HTTP Headers
 | 
			
		||||
      @header_rules = options[:header_rules] || []
 | 
			
		||||
      # Allow for legacy :cache_control option while prioritizing global header_rules setting
 | 
			
		||||
      @header_rules.unshift([:all, { CACHE_CONTROL => options[:cache_control] }]) if options[:cache_control]
 | 
			
		||||
 | 
			
		||||
      @file_server = Rack::Files.new(root)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def add_index_root?(path)
 | 
			
		||||
      @index && route_file(path) && path.end_with?('/')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def overwrite_file_path(path)
 | 
			
		||||
      @urls.kind_of?(Hash) && @urls.key?(path) || add_index_root?(path)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def route_file(path)
 | 
			
		||||
      @urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def can_serve(path)
 | 
			
		||||
      route_file(path) || overwrite_file_path(path)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def call(env)
 | 
			
		||||
      path = env[PATH_INFO]
 | 
			
		||||
 | 
			
		||||
      if can_serve(path)
 | 
			
		||||
        if overwrite_file_path(path)
 | 
			
		||||
          env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path])
 | 
			
		||||
        elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING'])
 | 
			
		||||
          path = env[PATH_INFO]
 | 
			
		||||
          env[PATH_INFO] += '.gz'
 | 
			
		||||
          response = @file_server.call(env)
 | 
			
		||||
          env[PATH_INFO] = path
 | 
			
		||||
 | 
			
		||||
          if response[0] == 404
 | 
			
		||||
            response = nil
 | 
			
		||||
          elsif response[0] == 304
 | 
			
		||||
            # Do nothing, leave headers as is
 | 
			
		||||
          else
 | 
			
		||||
            response[1][CONTENT_TYPE] = Mime.mime_type(::File.extname(path), 'text/plain')
 | 
			
		||||
            response[1]['content-encoding'] = 'gzip'
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        path = env[PATH_INFO]
 | 
			
		||||
        response ||= @file_server.call(env)
 | 
			
		||||
 | 
			
		||||
        if @cascade && response[0] == 404
 | 
			
		||||
          return @app.call(env)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        headers = response[1]
 | 
			
		||||
        applicable_rules(path).each do |rule, new_headers|
 | 
			
		||||
          new_headers.each { |field, content| headers[field] = content }
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        response
 | 
			
		||||
      else
 | 
			
		||||
        @app.call(env)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Convert HTTP header rules to HTTP headers
 | 
			
		||||
    def applicable_rules(path)
 | 
			
		||||
      @header_rules.find_all do |rule, new_headers|
 | 
			
		||||
        case rule
 | 
			
		||||
        when :all
 | 
			
		||||
          true
 | 
			
		||||
        when :fonts
 | 
			
		||||
          /\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
 | 
			
		||||
        when String
 | 
			
		||||
          path = ::Rack::Utils.unescape(path)
 | 
			
		||||
          path.start_with?(rule) || path.start_with?('/' + rule)
 | 
			
		||||
        when Array
 | 
			
		||||
          /\.(#{rule.join('|')})\z/.match?(path)
 | 
			
		||||
        when Regexp
 | 
			
		||||
          rule.match?(path)
 | 
			
		||||
        else
 | 
			
		||||
          false
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,33 +0,0 @@
 | 
			
		||||
# 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
 | 
			
		||||
@ -1,99 +0,0 @@
 | 
			
		||||
# 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 <tt>http://</tt> or <tt>https://</tt>.
 | 
			
		||||
  #
 | 
			
		||||
  # 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
 | 
			
		||||
@ -1,654 +0,0 @@
 | 
			
		||||
# -*- encoding: binary -*-
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'uri'
 | 
			
		||||
require 'fileutils'
 | 
			
		||||
require 'set'
 | 
			
		||||
require 'tempfile'
 | 
			
		||||
require 'time'
 | 
			
		||||
 | 
			
		||||
require_relative 'query_parser'
 | 
			
		||||
require_relative 'mime'
 | 
			
		||||
require_relative 'headers'
 | 
			
		||||
require_relative 'constants'
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # Rack::Utils contains a grab-bag of useful methods for writing web
 | 
			
		||||
  # applications adopted from all kinds of Ruby libraries.
 | 
			
		||||
 | 
			
		||||
  module Utils
 | 
			
		||||
    ParameterTypeError = QueryParser::ParameterTypeError
 | 
			
		||||
    InvalidParameterError = QueryParser::InvalidParameterError
 | 
			
		||||
    ParamsTooDeepError = QueryParser::ParamsTooDeepError
 | 
			
		||||
    DEFAULT_SEP = QueryParser::DEFAULT_SEP
 | 
			
		||||
    COMMON_SEP = QueryParser::COMMON_SEP
 | 
			
		||||
    KeySpaceConstrainedParams = QueryParser::Params
 | 
			
		||||
 | 
			
		||||
    class << self
 | 
			
		||||
      attr_accessor :default_query_parser
 | 
			
		||||
    end
 | 
			
		||||
    # The default amount of nesting to allowed by hash parameters.
 | 
			
		||||
    # This helps prevent a rogue client from triggering a possible stack overflow
 | 
			
		||||
    # when parsing parameters.
 | 
			
		||||
    self.default_query_parser = QueryParser.make_default(32)
 | 
			
		||||
 | 
			
		||||
    module_function
 | 
			
		||||
 | 
			
		||||
    # URI escapes. (CGI style space to +)
 | 
			
		||||
    def escape(s)
 | 
			
		||||
      URI.encode_www_form_component(s)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Like URI escaping, but with %20 instead of +. Strictly speaking this is
 | 
			
		||||
    # true URI escaping.
 | 
			
		||||
    def escape_path(s)
 | 
			
		||||
      ::URI::DEFAULT_PARSER.escape s
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Unescapes the **path** component of a URI.  See Rack::Utils.unescape for
 | 
			
		||||
    # unescaping query parameters or form components.
 | 
			
		||||
    def unescape_path(s)
 | 
			
		||||
      ::URI::DEFAULT_PARSER.unescape s
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
 | 
			
		||||
    # target encoding of the string returned, and it defaults to UTF-8
 | 
			
		||||
    def unescape(s, encoding = Encoding::UTF_8)
 | 
			
		||||
      URI.decode_www_form_component(s, encoding)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    class << self
 | 
			
		||||
      attr_accessor :multipart_total_part_limit
 | 
			
		||||
 | 
			
		||||
      attr_accessor :multipart_file_limit
 | 
			
		||||
 | 
			
		||||
      # multipart_part_limit is the original name of multipart_file_limit, but
 | 
			
		||||
      # the limit only counts parts with filenames.
 | 
			
		||||
      alias multipart_part_limit multipart_file_limit
 | 
			
		||||
      alias multipart_part_limit= multipart_file_limit=
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # The maximum number of file parts a request can contain. Accepting too
 | 
			
		||||
    # many parts can lead to the server running out of file handles.
 | 
			
		||||
    # Set to `0` for no limit.
 | 
			
		||||
    self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i
 | 
			
		||||
 | 
			
		||||
    # The maximum total number of parts a request can contain. Accepting too
 | 
			
		||||
    # many can lead to excessive memory use and parsing time.
 | 
			
		||||
    self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
 | 
			
		||||
 | 
			
		||||
    def self.param_depth_limit
 | 
			
		||||
      default_query_parser.param_depth_limit
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def self.param_depth_limit=(v)
 | 
			
		||||
      self.default_query_parser = self.default_query_parser.new_depth_limit(v)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def self.key_space_limit
 | 
			
		||||
      warn("`Rack::Utils.key_space_limit` is deprecated as this value no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
 | 
			
		||||
      65536
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def self.key_space_limit=(v)
 | 
			
		||||
      warn("`Rack::Utils.key_space_limit=` is deprecated and no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    if defined?(Process::CLOCK_MONOTONIC)
 | 
			
		||||
      def clock_time
 | 
			
		||||
        Process.clock_gettime(Process::CLOCK_MONOTONIC)
 | 
			
		||||
      end
 | 
			
		||||
    else
 | 
			
		||||
      # :nocov:
 | 
			
		||||
      def clock_time
 | 
			
		||||
        Time.now.to_f
 | 
			
		||||
      end
 | 
			
		||||
      # :nocov:
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def parse_query(qs, d = nil, &unescaper)
 | 
			
		||||
      Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def parse_nested_query(qs, d = nil)
 | 
			
		||||
      Rack::Utils.default_query_parser.parse_nested_query(qs, d)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def build_query(params)
 | 
			
		||||
      params.map { |k, v|
 | 
			
		||||
        if v.class == Array
 | 
			
		||||
          build_query(v.map { |x| [k, x] })
 | 
			
		||||
        else
 | 
			
		||||
          v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
 | 
			
		||||
        end
 | 
			
		||||
      }.join("&")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def build_nested_query(value, prefix = nil)
 | 
			
		||||
      case value
 | 
			
		||||
      when Array
 | 
			
		||||
        value.map { |v|
 | 
			
		||||
          build_nested_query(v, "#{prefix}[]")
 | 
			
		||||
        }.join("&")
 | 
			
		||||
      when Hash
 | 
			
		||||
        value.map { |k, v|
 | 
			
		||||
          build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
 | 
			
		||||
        }.delete_if(&:empty?).join('&')
 | 
			
		||||
      when nil
 | 
			
		||||
        escape(prefix)
 | 
			
		||||
      else
 | 
			
		||||
        raise ArgumentError, "value must be a Hash" if prefix.nil?
 | 
			
		||||
        "#{escape(prefix)}=#{escape(value)}"
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def q_values(q_value_header)
 | 
			
		||||
      q_value_header.to_s.split(/\s*,\s*/).map do |part|
 | 
			
		||||
        value, parameters = part.split(/\s*;\s*/, 2)
 | 
			
		||||
        quality = 1.0
 | 
			
		||||
        if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
 | 
			
		||||
          quality = md[1].to_f
 | 
			
		||||
        end
 | 
			
		||||
        [value, quality]
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def forwarded_values(forwarded_header)
 | 
			
		||||
      return nil unless forwarded_header
 | 
			
		||||
      forwarded_header = forwarded_header.to_s.gsub("\n", ";")
 | 
			
		||||
 | 
			
		||||
      forwarded_header.split(/\s*;\s*/).each_with_object({}) do |field, values|
 | 
			
		||||
        field.split(/\s*,\s*/).each do |pair|
 | 
			
		||||
          return nil unless pair =~ /\A\s*(by|for|host|proto)\s*=\s*"?([^"]+)"?\s*\Z/i
 | 
			
		||||
          (values[$1.downcase.to_sym] ||= []) << $2
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    module_function :forwarded_values
 | 
			
		||||
 | 
			
		||||
    # Return best accept value to use, based on the algorithm
 | 
			
		||||
    # in RFC 2616 Section 14.  If there are multiple best
 | 
			
		||||
    # matches (same specificity and quality), the value returned
 | 
			
		||||
    # is arbitrary.
 | 
			
		||||
    def best_q_match(q_value_header, available_mimes)
 | 
			
		||||
      values = q_values(q_value_header)
 | 
			
		||||
 | 
			
		||||
      matches = values.map do |req_mime, quality|
 | 
			
		||||
        match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) }
 | 
			
		||||
        next unless match
 | 
			
		||||
        [match, quality]
 | 
			
		||||
      end.compact.sort_by do |match, quality|
 | 
			
		||||
        (match.split('/', 2).count('*') * -10) + quality
 | 
			
		||||
      end.last
 | 
			
		||||
      matches&.first
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    ESCAPE_HTML = {
 | 
			
		||||
      "&" => "&",
 | 
			
		||||
      "<" => "<",
 | 
			
		||||
      ">" => ">",
 | 
			
		||||
      "'" => "'",
 | 
			
		||||
      '"' => """,
 | 
			
		||||
      "/" => "/"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
 | 
			
		||||
 | 
			
		||||
    # Escape ampersands, brackets and quotes to their HTML/XML entities.
 | 
			
		||||
    def escape_html(string)
 | 
			
		||||
      string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def select_best_encoding(available_encodings, accept_encoding)
 | 
			
		||||
      # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 | 
			
		||||
 | 
			
		||||
      expanded_accept_encoding = []
 | 
			
		||||
 | 
			
		||||
      accept_encoding.each do |m, q|
 | 
			
		||||
        preference = available_encodings.index(m) || available_encodings.size
 | 
			
		||||
 | 
			
		||||
        if m == "*"
 | 
			
		||||
          (available_encodings - accept_encoding.map(&:first)).each do |m2|
 | 
			
		||||
            expanded_accept_encoding << [m2, q, preference]
 | 
			
		||||
          end
 | 
			
		||||
        else
 | 
			
		||||
          expanded_accept_encoding << [m, q, preference]
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      encoding_candidates = expanded_accept_encoding
 | 
			
		||||
        .sort_by { |_, q, p| [-q, p] }
 | 
			
		||||
        .map!(&:first)
 | 
			
		||||
 | 
			
		||||
      unless encoding_candidates.include?("identity")
 | 
			
		||||
        encoding_candidates.push("identity")
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      expanded_accept_encoding.each do |m, q|
 | 
			
		||||
        encoding_candidates.delete(m) if q == 0.0
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      (encoding_candidates & available_encodings)[0]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # :call-seq:
 | 
			
		||||
    #   parse_cookies_header(value) -> hash
 | 
			
		||||
    #
 | 
			
		||||
    # Parse cookies from the provided header +value+ according to RFC6265. The
 | 
			
		||||
    # syntax for cookie headers only supports semicolons. Returns a map of
 | 
			
		||||
    # cookie +key+ to cookie +value+.
 | 
			
		||||
    #
 | 
			
		||||
    #   parse_cookies_header('myname=myvalue; max-age=0')
 | 
			
		||||
    #   # => {"myname"=>"myvalue", "max-age"=>"0"}
 | 
			
		||||
    #
 | 
			
		||||
    def parse_cookies_header(value)
 | 
			
		||||
      return {} unless value
 | 
			
		||||
 | 
			
		||||
      value.split(/; */n).each_with_object({}) do |cookie, cookies|
 | 
			
		||||
        next if cookie.empty?
 | 
			
		||||
        key, value = cookie.split('=', 2)
 | 
			
		||||
        cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def add_cookie_to_header(header, key, value)
 | 
			
		||||
      warn("add_cookie_to_header is deprecated and will be removed in Rack 3.1", uplevel: 1)
 | 
			
		||||
 | 
			
		||||
      case header
 | 
			
		||||
      when nil, ''
 | 
			
		||||
        return set_cookie_header(key, value)
 | 
			
		||||
      when String
 | 
			
		||||
        [header, set_cookie_header(key, value)]
 | 
			
		||||
      when Array
 | 
			
		||||
        header + [set_cookie_header(key, value)]
 | 
			
		||||
      else
 | 
			
		||||
        raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # :call-seq:
 | 
			
		||||
    #   parse_cookies(env) -> hash
 | 
			
		||||
    #
 | 
			
		||||
    # Parse cookies from the provided request environment using
 | 
			
		||||
    # parse_cookies_header. Returns a map of cookie +key+ to cookie +value+.
 | 
			
		||||
    #
 | 
			
		||||
    #   parse_cookies({'HTTP_COOKIE' => 'myname=myvalue'})
 | 
			
		||||
    #   # => {'myname' => 'myvalue'}
 | 
			
		||||
    #
 | 
			
		||||
    def parse_cookies(env)
 | 
			
		||||
      parse_cookies_header env[HTTP_COOKIE]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # :call-seq:
 | 
			
		||||
    #   set_cookie_header(key, value) -> encoded string
 | 
			
		||||
    #
 | 
			
		||||
    # Generate an encoded string using the provided +key+ and +value+ suitable
 | 
			
		||||
    # for the +set-cookie+ header according to RFC6265. The +value+ may be an
 | 
			
		||||
    # instance of either +String+ or +Hash+.
 | 
			
		||||
    #
 | 
			
		||||
    # If the cookie +value+ is an instance of +Hash+, it considers the following
 | 
			
		||||
    # cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
 | 
			
		||||
    # of +Time+), +secure+, +http_only+, +same_site+ and +value+. For more
 | 
			
		||||
    # details about the interpretation of these fields, consult
 | 
			
		||||
    # [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
 | 
			
		||||
    #
 | 
			
		||||
    # An extra cookie attribute +escape_key+ can be provided to control whether
 | 
			
		||||
    # or not the cookie key is URL encoded. If explicitly set to +false+, the
 | 
			
		||||
    # cookie key name will not be url encoded (escaped). The default is +true+.
 | 
			
		||||
    #
 | 
			
		||||
    #   set_cookie_header("myname", "myvalue")
 | 
			
		||||
    #   # => "myname=myvalue"
 | 
			
		||||
    #
 | 
			
		||||
    #   set_cookie_header("myname", {value: "myvalue", max_age: 10})
 | 
			
		||||
    #   # => "myname=myvalue; max-age=10"
 | 
			
		||||
    #
 | 
			
		||||
    def set_cookie_header(key, value)
 | 
			
		||||
      case value
 | 
			
		||||
      when Hash
 | 
			
		||||
        key = escape(key) unless value[:escape_key] == false
 | 
			
		||||
        domain  = "; domain=#{value[:domain]}"   if value[:domain]
 | 
			
		||||
        path    = "; path=#{value[:path]}"       if value[:path]
 | 
			
		||||
        max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
 | 
			
		||||
        expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
 | 
			
		||||
        secure = "; secure"  if value[:secure]
 | 
			
		||||
        httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
 | 
			
		||||
        same_site =
 | 
			
		||||
          case value[:same_site]
 | 
			
		||||
          when false, nil
 | 
			
		||||
            nil
 | 
			
		||||
          when :none, 'None', :None
 | 
			
		||||
            '; SameSite=None'
 | 
			
		||||
          when :lax, 'Lax', :Lax
 | 
			
		||||
            '; SameSite=Lax'
 | 
			
		||||
          when true, :strict, 'Strict', :Strict
 | 
			
		||||
            '; SameSite=Strict'
 | 
			
		||||
          else
 | 
			
		||||
            raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
 | 
			
		||||
          end
 | 
			
		||||
        value = value[:value]
 | 
			
		||||
      else
 | 
			
		||||
        key = escape(key)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      value = [value] unless Array === value
 | 
			
		||||
 | 
			
		||||
      return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
 | 
			
		||||
        "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # :call-seq:
 | 
			
		||||
    #   set_cookie_header!(headers, key, value) -> header value
 | 
			
		||||
    #
 | 
			
		||||
    # Append a cookie in the specified headers with the given cookie +key+ and
 | 
			
		||||
    # +value+ using set_cookie_header.
 | 
			
		||||
    #
 | 
			
		||||
    # If the headers already contains a +set-cookie+ key, it will be converted
 | 
			
		||||
    # to an +Array+ if not already, and appended to.
 | 
			
		||||
    def set_cookie_header!(headers, key, value)
 | 
			
		||||
      if header = headers[SET_COOKIE]
 | 
			
		||||
        if header.is_a?(Array)
 | 
			
		||||
          header << set_cookie_header(key, value)
 | 
			
		||||
        else
 | 
			
		||||
          headers[SET_COOKIE] = [header, set_cookie_header(key, value)]
 | 
			
		||||
        end
 | 
			
		||||
      else
 | 
			
		||||
        headers[SET_COOKIE] = set_cookie_header(key, value)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # :call-seq:
 | 
			
		||||
    #   delete_set_cookie_header(key, value = {}) -> encoded string
 | 
			
		||||
    #
 | 
			
		||||
    # Generate an encoded string based on the given +key+ and +value+ using
 | 
			
		||||
    # set_cookie_header for the purpose of causing the specified cookie to be
 | 
			
		||||
    # deleted. The +value+ may be an instance of +Hash+ and can include
 | 
			
		||||
    # attributes as outlined by set_cookie_header. The encoded cookie will have
 | 
			
		||||
    # a +max_age+ of 0 seconds, an +expires+ date in the past and an empty
 | 
			
		||||
    # +value+. When used with the +set-cookie+ header, it will cause the client
 | 
			
		||||
    # to *remove* any matching cookie.
 | 
			
		||||
    #
 | 
			
		||||
    #   delete_set_cookie_header("myname")
 | 
			
		||||
    #   # => "myname=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
 | 
			
		||||
    #
 | 
			
		||||
    def delete_set_cookie_header(key, value = {})
 | 
			
		||||
      set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def make_delete_cookie_header(header, key, value)
 | 
			
		||||
      warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
 | 
			
		||||
 | 
			
		||||
      delete_set_cookie_header!(header, key, value)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def delete_cookie_header!(headers, key, value = {})
 | 
			
		||||
      headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
 | 
			
		||||
 | 
			
		||||
      return nil
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def add_remove_cookie_to_header(header, key, value = {})
 | 
			
		||||
      warn("add_remove_cookie_to_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
 | 
			
		||||
 | 
			
		||||
      delete_set_cookie_header!(header, key, value)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # :call-seq:
 | 
			
		||||
    #   delete_set_cookie_header!(header, key, value = {}) -> header value
 | 
			
		||||
    #
 | 
			
		||||
    # Set an expired cookie in the specified headers with the given cookie
 | 
			
		||||
    # +key+ and +value+ using delete_set_cookie_header. This causes
 | 
			
		||||
    # the client to immediately delete the specified cookie.
 | 
			
		||||
    #
 | 
			
		||||
    #   delete_set_cookie_header!(nil, "mycookie")
 | 
			
		||||
    #   # => "mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
 | 
			
		||||
    #
 | 
			
		||||
    # If the header is non-nil, it will be modified in place.
 | 
			
		||||
    #
 | 
			
		||||
    #   header = []
 | 
			
		||||
    #   delete_set_cookie_header!(header, "mycookie")
 | 
			
		||||
    #   # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
 | 
			
		||||
    #   header
 | 
			
		||||
    #   # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
 | 
			
		||||
    #
 | 
			
		||||
    def delete_set_cookie_header!(header, key, value = {})
 | 
			
		||||
      if header
 | 
			
		||||
        header = Array(header)
 | 
			
		||||
        header << delete_set_cookie_header(key, value)
 | 
			
		||||
      else
 | 
			
		||||
        header = delete_set_cookie_header(key, value)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      return header
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def rfc2822(time)
 | 
			
		||||
      time.rfc2822
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Parses the "Range:" header, if present, into an array of Range objects.
 | 
			
		||||
    # Returns nil if the header is missing or syntactically invalid.
 | 
			
		||||
    # Returns an empty array if none of the ranges are satisfiable.
 | 
			
		||||
    def byte_ranges(env, size)
 | 
			
		||||
      get_byte_ranges env['HTTP_RANGE'], size
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def get_byte_ranges(http_range, size)
 | 
			
		||||
      # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
 | 
			
		||||
      return nil unless http_range && http_range =~ /bytes=([^;]+)/
 | 
			
		||||
      ranges = []
 | 
			
		||||
      $1.split(/,\s*/).each do |range_spec|
 | 
			
		||||
        return nil unless range_spec.include?('-')
 | 
			
		||||
        range = range_spec.split('-')
 | 
			
		||||
        r0, r1 = range[0], range[1]
 | 
			
		||||
        if r0.nil? || r0.empty?
 | 
			
		||||
          return nil if r1.nil?
 | 
			
		||||
          # suffix-byte-range-spec, represents trailing suffix of file
 | 
			
		||||
          r0 = size - r1.to_i
 | 
			
		||||
          r0 = 0  if r0 < 0
 | 
			
		||||
          r1 = size - 1
 | 
			
		||||
        else
 | 
			
		||||
          r0 = r0.to_i
 | 
			
		||||
          if r1.nil?
 | 
			
		||||
            r1 = size - 1
 | 
			
		||||
          else
 | 
			
		||||
            r1 = r1.to_i
 | 
			
		||||
            return nil  if r1 < r0  # backwards range is syntactically invalid
 | 
			
		||||
            r1 = size - 1  if r1 >= size
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
        ranges << (r0..r1)  if r0 <= r1
 | 
			
		||||
      end
 | 
			
		||||
      ranges
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # :nocov:
 | 
			
		||||
    if defined?(OpenSSL.fixed_length_secure_compare)
 | 
			
		||||
      # Constant time string comparison.
 | 
			
		||||
      #
 | 
			
		||||
      # NOTE: the values compared should be of fixed length, such as strings
 | 
			
		||||
      # that have already been processed by HMAC. This should not be used
 | 
			
		||||
      # on variable length plaintext strings because it could leak length info
 | 
			
		||||
      # via timing attacks.
 | 
			
		||||
      def secure_compare(a, b)
 | 
			
		||||
        return false unless a.bytesize == b.bytesize
 | 
			
		||||
 | 
			
		||||
        OpenSSL.fixed_length_secure_compare(a, b)
 | 
			
		||||
      end
 | 
			
		||||
    # :nocov:
 | 
			
		||||
    else
 | 
			
		||||
      def secure_compare(a, b)
 | 
			
		||||
        return false unless a.bytesize == b.bytesize
 | 
			
		||||
 | 
			
		||||
        l = a.unpack("C*")
 | 
			
		||||
 | 
			
		||||
        r, i = 0, -1
 | 
			
		||||
        b.each_byte { |v| r |= v ^ l[i += 1] }
 | 
			
		||||
        r == 0
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Context allows the use of a compatible middleware at different points
 | 
			
		||||
    # in a request handling stack. A compatible middleware must define
 | 
			
		||||
    # #context which should take the arguments env and app. The first of which
 | 
			
		||||
    # would be the request environment. The second of which would be the rack
 | 
			
		||||
    # application that the request would be forwarded to.
 | 
			
		||||
    class Context
 | 
			
		||||
      attr_reader :for, :app
 | 
			
		||||
 | 
			
		||||
      def initialize(app_f, app_r)
 | 
			
		||||
        raise 'running context does not respond to #context' unless app_f.respond_to? :context
 | 
			
		||||
        @for, @app = app_f, app_r
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def call(env)
 | 
			
		||||
        @for.context(env, @app)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def recontext(app)
 | 
			
		||||
        self.class.new(@for, app)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def context(env, app = @app)
 | 
			
		||||
        recontext(app).call(env)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # A wrapper around Headers
 | 
			
		||||
    # header when set.
 | 
			
		||||
    #
 | 
			
		||||
    # @api private
 | 
			
		||||
    class HeaderHash < Hash # :nodoc:
 | 
			
		||||
      def self.[](headers)
 | 
			
		||||
        warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
 | 
			
		||||
        if headers.is_a?(Headers) && !headers.frozen?
 | 
			
		||||
          return headers
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        new_headers = Headers.new
 | 
			
		||||
        headers.each{|k,v| new_headers[k] = v}
 | 
			
		||||
        new_headers
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def self.new(hash = {})
 | 
			
		||||
        warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
 | 
			
		||||
        headers = Headers.new
 | 
			
		||||
        hash.each{|k,v| headers[k] = v}
 | 
			
		||||
        headers
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def self.allocate
 | 
			
		||||
        raise TypeError, "cannot allocate HeaderHash"
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Every standard HTTP code mapped to the appropriate message.
 | 
			
		||||
    # Generated with:
 | 
			
		||||
    #   curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
 | 
			
		||||
    #     ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
 | 
			
		||||
    #               puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
 | 
			
		||||
    HTTP_STATUS_CODES = {
 | 
			
		||||
      100 => 'Continue',
 | 
			
		||||
      101 => 'Switching Protocols',
 | 
			
		||||
      102 => 'Processing',
 | 
			
		||||
      103 => 'Early Hints',
 | 
			
		||||
      200 => 'OK',
 | 
			
		||||
      201 => 'Created',
 | 
			
		||||
      202 => 'Accepted',
 | 
			
		||||
      203 => 'Non-Authoritative Information',
 | 
			
		||||
      204 => 'No Content',
 | 
			
		||||
      205 => 'Reset Content',
 | 
			
		||||
      206 => 'Partial Content',
 | 
			
		||||
      207 => 'Multi-Status',
 | 
			
		||||
      208 => 'Already Reported',
 | 
			
		||||
      226 => 'IM Used',
 | 
			
		||||
      300 => 'Multiple Choices',
 | 
			
		||||
      301 => 'Moved Permanently',
 | 
			
		||||
      302 => 'Found',
 | 
			
		||||
      303 => 'See Other',
 | 
			
		||||
      304 => 'Not Modified',
 | 
			
		||||
      305 => 'Use Proxy',
 | 
			
		||||
      306 => '(Unused)',
 | 
			
		||||
      307 => 'Temporary Redirect',
 | 
			
		||||
      308 => 'Permanent Redirect',
 | 
			
		||||
      400 => 'Bad Request',
 | 
			
		||||
      401 => 'Unauthorized',
 | 
			
		||||
      402 => 'Payment Required',
 | 
			
		||||
      403 => 'Forbidden',
 | 
			
		||||
      404 => 'Not Found',
 | 
			
		||||
      405 => 'Method Not Allowed',
 | 
			
		||||
      406 => 'Not Acceptable',
 | 
			
		||||
      407 => 'Proxy Authentication Required',
 | 
			
		||||
      408 => 'Request Timeout',
 | 
			
		||||
      409 => 'Conflict',
 | 
			
		||||
      410 => 'Gone',
 | 
			
		||||
      411 => 'Length Required',
 | 
			
		||||
      412 => 'Precondition Failed',
 | 
			
		||||
      413 => 'Payload Too Large',
 | 
			
		||||
      414 => 'URI Too Long',
 | 
			
		||||
      415 => 'Unsupported Media Type',
 | 
			
		||||
      416 => 'Range Not Satisfiable',
 | 
			
		||||
      417 => 'Expectation Failed',
 | 
			
		||||
      421 => 'Misdirected Request',
 | 
			
		||||
      422 => 'Unprocessable Entity',
 | 
			
		||||
      423 => 'Locked',
 | 
			
		||||
      424 => 'Failed Dependency',
 | 
			
		||||
      425 => 'Too Early',
 | 
			
		||||
      426 => 'Upgrade Required',
 | 
			
		||||
      428 => 'Precondition Required',
 | 
			
		||||
      429 => 'Too Many Requests',
 | 
			
		||||
      431 => 'Request Header Fields Too Large',
 | 
			
		||||
      451 => 'Unavailable for Legal Reasons',
 | 
			
		||||
      500 => 'Internal Server Error',
 | 
			
		||||
      501 => 'Not Implemented',
 | 
			
		||||
      502 => 'Bad Gateway',
 | 
			
		||||
      503 => 'Service Unavailable',
 | 
			
		||||
      504 => 'Gateway Timeout',
 | 
			
		||||
      505 => 'HTTP Version Not Supported',
 | 
			
		||||
      506 => 'Variant Also Negotiates',
 | 
			
		||||
      507 => 'Insufficient Storage',
 | 
			
		||||
      508 => 'Loop Detected',
 | 
			
		||||
      509 => 'Bandwidth Limit Exceeded',
 | 
			
		||||
      510 => 'Not Extended',
 | 
			
		||||
      511 => 'Network Authentication Required'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # Responses with HTTP status codes that should not have an entity body
 | 
			
		||||
    STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
 | 
			
		||||
 | 
			
		||||
    SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
 | 
			
		||||
      [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
 | 
			
		||||
    }.flatten]
 | 
			
		||||
 | 
			
		||||
    def status_code(status)
 | 
			
		||||
      if status.is_a?(Symbol)
 | 
			
		||||
        SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
 | 
			
		||||
      else
 | 
			
		||||
        status.to_i
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
 | 
			
		||||
 | 
			
		||||
    def clean_path_info(path_info)
 | 
			
		||||
      parts = path_info.split PATH_SEPS
 | 
			
		||||
 | 
			
		||||
      clean = []
 | 
			
		||||
 | 
			
		||||
      parts.each do |part|
 | 
			
		||||
        next if part.empty? || part == '.'
 | 
			
		||||
        part == '..' ? clean.pop : clean << part
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      clean_path = clean.join(::File::SEPARATOR)
 | 
			
		||||
      clean_path.prepend("/") if parts.empty? || parts.first.empty?
 | 
			
		||||
      clean_path
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    NULL_BYTE = "\0"
 | 
			
		||||
 | 
			
		||||
    def valid_path?(path)
 | 
			
		||||
      path.valid_encoding? && !path.include?(NULL_BYTE)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,34 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
# Copyright (C) 2007-2019 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
 | 
			
		||||
#
 | 
			
		||||
# 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 <tt>autoload</tt>ed here,
 | 
			
		||||
# so it should be enough just to <tt>require 'rack'</tt> in your code.
 | 
			
		||||
 | 
			
		||||
module Rack
 | 
			
		||||
  # The Rack protocol version number implemented.
 | 
			
		||||
  VERSION = [1, 3].freeze
 | 
			
		||||
  deprecate_constant :VERSION
 | 
			
		||||
 | 
			
		||||
  VERSION_STRING = "1.3".freeze
 | 
			
		||||
  deprecate_constant :VERSION_STRING
 | 
			
		||||
 | 
			
		||||
  # The Rack protocol version number implemented.
 | 
			
		||||
  def self.version
 | 
			
		||||
    warn "Rack.version is deprecated and will be removed in Rack 3.1!", uplevel: 1
 | 
			
		||||
    VERSION
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  RELEASE = "3.0.8"
 | 
			
		||||
 | 
			
		||||
  # Return the Rack release as a dotted string.
 | 
			
		||||
  def self.release
 | 
			
		||||
    RELEASE
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,24 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'pathname'
 | 
			
		||||
require 'yaml'
 | 
			
		||||
 | 
			
		||||
require 'rubocop'
 | 
			
		||||
 | 
			
		||||
require_relative 'rubocop/cop/capybara/mixin/capybara_help'
 | 
			
		||||
require_relative 'rubocop/cop/capybara/mixin/css_selector'
 | 
			
		||||
 | 
			
		||||
require_relative 'rubocop/cop/capybara_cops'
 | 
			
		||||
 | 
			
		||||
project_root = File.join(__dir__, '..')
 | 
			
		||||
RuboCop::ConfigLoader.inject_defaults!(project_root)
 | 
			
		||||
obsoletion = File.join(project_root, 'config', 'obsoletion.yml')
 | 
			
		||||
RuboCop::ConfigObsoletion.files << obsoletion if File.exist?(obsoletion)
 | 
			
		||||
 | 
			
		||||
RuboCop::Cop::Style::TrailingCommaInArguments.singleton_class.prepend(
 | 
			
		||||
  Module.new do
 | 
			
		||||
    def autocorrect_incompatible_with
 | 
			
		||||
      super.push(RuboCop::Cop::Capybara::CurrentPathExpectation)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
)
 | 
			
		||||
@ -1,56 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'yaml'
 | 
			
		||||
 | 
			
		||||
module RuboCop
 | 
			
		||||
  module Capybara
 | 
			
		||||
    # Builds a YAML config file from two config hashes
 | 
			
		||||
    class ConfigFormatter
 | 
			
		||||
      EXTENSION_ROOT_DEPARTMENT = %r{^(Capybara/)}.freeze
 | 
			
		||||
      SUBDEPARTMENTS = [].freeze
 | 
			
		||||
      AMENDMENTS = [].freeze
 | 
			
		||||
      COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-capybara/RuboCop/Cop/'
 | 
			
		||||
 | 
			
		||||
      def initialize(config, descriptions)
 | 
			
		||||
        @config       = config
 | 
			
		||||
        @descriptions = descriptions
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def dump
 | 
			
		||||
        YAML.dump(unified_config)
 | 
			
		||||
          .gsub(EXTENSION_ROOT_DEPARTMENT, "\n\\1")
 | 
			
		||||
          .gsub(/^(\s+)- /, '\1  - ')
 | 
			
		||||
          .gsub('"~"', '~')
 | 
			
		||||
        # .gsub(*AMENDMENTS, "\n\\0")
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      private
 | 
			
		||||
 | 
			
		||||
      def unified_config
 | 
			
		||||
        cops.each_with_object(config.dup) do |cop, unified|
 | 
			
		||||
          next if SUBDEPARTMENTS.include?(cop) || AMENDMENTS.include?(cop)
 | 
			
		||||
 | 
			
		||||
          replace_nil(unified[cop])
 | 
			
		||||
          unified[cop].merge!(descriptions.fetch(cop))
 | 
			
		||||
          unified[cop]['Reference'] = reference(cop)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def cops
 | 
			
		||||
        (descriptions.keys | config.keys).grep(EXTENSION_ROOT_DEPARTMENT)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def replace_nil(config)
 | 
			
		||||
        config.each do |key, value|
 | 
			
		||||
          config[key] = '~' if value.nil?
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def reference(cop)
 | 
			
		||||
        COP_DOC_BASE_URL + cop
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      attr_reader :config, :descriptions
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,70 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module RuboCop
 | 
			
		||||
  module Capybara
 | 
			
		||||
    # Extracts cop descriptions from YARD docstrings
 | 
			
		||||
    class DescriptionExtractor
 | 
			
		||||
      def initialize(yardocs)
 | 
			
		||||
        @code_objects = yardocs.map(&CodeObject.public_method(:new))
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def to_h
 | 
			
		||||
        code_objects
 | 
			
		||||
          .select(&:cop?)
 | 
			
		||||
          .map(&:configuration)
 | 
			
		||||
          .reduce(:merge)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      private
 | 
			
		||||
 | 
			
		||||
      attr_reader :code_objects
 | 
			
		||||
 | 
			
		||||
      # Decorator of a YARD code object for working with documented cops
 | 
			
		||||
      class CodeObject
 | 
			
		||||
        RUBOCOP_COP_CLASS_NAME = 'RuboCop::Cop::Base'
 | 
			
		||||
 | 
			
		||||
        def initialize(yardoc)
 | 
			
		||||
          @yardoc = yardoc
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # Test if the YARD code object documents a concrete cop class
 | 
			
		||||
        #
 | 
			
		||||
        # @return [Boolean]
 | 
			
		||||
        def cop?
 | 
			
		||||
          cop_subclass? && !abstract?
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # Configuration for the documented cop that would live in default.yml
 | 
			
		||||
        #
 | 
			
		||||
        # @return [Hash]
 | 
			
		||||
        def configuration
 | 
			
		||||
          { cop_name => { 'Description' => description } }
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        private
 | 
			
		||||
 | 
			
		||||
        def cop_name
 | 
			
		||||
          Object.const_get(documented_constant).cop_name
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def description
 | 
			
		||||
          yardoc.docstring.split("\n\n").first.to_s
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def documented_constant
 | 
			
		||||
          yardoc.to_s
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def cop_subclass?
 | 
			
		||||
          yardoc.superclass.path == RUBOCOP_COP_CLASS_NAME
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def abstract?
 | 
			
		||||
          yardoc.tags.any? { |tag| tag.tag_name.eql?('abstract') }
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        attr_reader :yardoc
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module RuboCop
 | 
			
		||||
  module Capybara
 | 
			
		||||
    # Version information for the Capybara RuboCop plugin.
 | 
			
		||||
    module Version
 | 
			
		||||
      STRING = '2.18.0'
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,148 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module RuboCop
 | 
			
		||||
  module Cop
 | 
			
		||||
    module Capybara
 | 
			
		||||
      # Checks that no expectations are set on Capybara's `current_path`.
 | 
			
		||||
      #
 | 
			
		||||
      # The
 | 
			
		||||
      # https://www.rubydoc.info/github/teamcapybara/capybara/master/Capybara/RSpecMatchers#have_current_path-instance_method[`have_current_path` matcher]
 | 
			
		||||
      # should be used on `page` to set expectations on Capybara's
 | 
			
		||||
      # current path, since it uses
 | 
			
		||||
      # https://github.com/teamcapybara/capybara/blob/master/README.md#asynchronous-javascript-ajax-and-friends[Capybara's waiting functionality]
 | 
			
		||||
      # which ensures that preceding actions (like `click_link`) have
 | 
			
		||||
      # completed.
 | 
			
		||||
      #
 | 
			
		||||
      # This cop does not support autocorrection in some cases.
 | 
			
		||||
      #
 | 
			
		||||
      # @example
 | 
			
		||||
      #   # bad
 | 
			
		||||
      #   expect(current_path).to eq('/callback')
 | 
			
		||||
      #
 | 
			
		||||
      #   # good
 | 
			
		||||
      #   expect(page).to have_current_path('/callback')
 | 
			
		||||
      #
 | 
			
		||||
      #   # bad (does not support autocorrection)
 | 
			
		||||
      #   expect(page.current_path).to match(variable)
 | 
			
		||||
      #
 | 
			
		||||
      #   # good
 | 
			
		||||
      #   expect(page).to have_current_path('/callback')
 | 
			
		||||
      #
 | 
			
		||||
      class CurrentPathExpectation < ::RuboCop::Cop::Base
 | 
			
		||||
        extend AutoCorrector
 | 
			
		||||
        include RangeHelp
 | 
			
		||||
 | 
			
		||||
        MSG = 'Do not set an RSpec expectation on `current_path` in ' \
 | 
			
		||||
              'Capybara feature specs - instead, use the ' \
 | 
			
		||||
              '`have_current_path` matcher on `page`'
 | 
			
		||||
 | 
			
		||||
        RESTRICT_ON_SEND = %i[expect].freeze
 | 
			
		||||
 | 
			
		||||
        # @!method expectation_set_on_current_path(node)
 | 
			
		||||
        def_node_matcher :expectation_set_on_current_path, <<-PATTERN
 | 
			
		||||
          (send nil? :expect (send {(send nil? :page) nil?} :current_path))
 | 
			
		||||
        PATTERN
 | 
			
		||||
 | 
			
		||||
        # Supported matchers: eq(...) / match(/regexp/) / match('regexp')
 | 
			
		||||
        # @!method as_is_matcher(node)
 | 
			
		||||
        def_node_matcher :as_is_matcher, <<-PATTERN
 | 
			
		||||
          (send
 | 
			
		||||
            #expectation_set_on_current_path ${:to :to_not :not_to}
 | 
			
		||||
            ${(send nil? :eq ...) (send nil? :match (regexp ...))})
 | 
			
		||||
        PATTERN
 | 
			
		||||
 | 
			
		||||
        # @!method regexp_node_matcher(node)
 | 
			
		||||
        def_node_matcher :regexp_node_matcher, <<-PATTERN
 | 
			
		||||
          (send
 | 
			
		||||
            #expectation_set_on_current_path ${:to :to_not :not_to}
 | 
			
		||||
            $(send nil? :match ${str dstr xstr}))
 | 
			
		||||
        PATTERN
 | 
			
		||||
 | 
			
		||||
        def self.autocorrect_incompatible_with
 | 
			
		||||
          [Style::TrailingCommaInArguments]
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def on_send(node)
 | 
			
		||||
          expectation_set_on_current_path(node) do
 | 
			
		||||
            add_offense(node.loc.selector) do |corrector|
 | 
			
		||||
              next unless node.chained?
 | 
			
		||||
 | 
			
		||||
              autocorrect(corrector, node)
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        private
 | 
			
		||||
 | 
			
		||||
        def autocorrect(corrector, node)
 | 
			
		||||
          as_is_matcher(node.parent) do |to_sym, matcher_node|
 | 
			
		||||
            rewrite_expectation(corrector, node, to_sym, matcher_node)
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          regexp_node_matcher(node.parent) do |to_sym, matcher_node, regexp|
 | 
			
		||||
            rewrite_expectation(corrector, node, to_sym, matcher_node)
 | 
			
		||||
            convert_regexp_node_to_literal(corrector, matcher_node, regexp)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def rewrite_expectation(corrector, node, to_symbol, matcher_node)
 | 
			
		||||
          corrector.replace(node.first_argument, 'page')
 | 
			
		||||
          corrector.replace(node.parent.loc.selector, 'to')
 | 
			
		||||
          matcher_method = if to_symbol == :to
 | 
			
		||||
                             'have_current_path'
 | 
			
		||||
                           else
 | 
			
		||||
                             'have_no_current_path'
 | 
			
		||||
                           end
 | 
			
		||||
          corrector.replace(matcher_node.loc.selector, matcher_method)
 | 
			
		||||
          add_argument_parentheses(corrector, matcher_node.first_argument)
 | 
			
		||||
          add_ignore_query_options(corrector, node)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def convert_regexp_node_to_literal(corrector, matcher_node, regexp_node)
 | 
			
		||||
          str_node = matcher_node.first_argument
 | 
			
		||||
          regexp_expr = regexp_node_to_regexp_expr(regexp_node)
 | 
			
		||||
          corrector.replace(str_node, regexp_expr)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def regexp_node_to_regexp_expr(regexp_node)
 | 
			
		||||
          if regexp_node.xstr_type?
 | 
			
		||||
            "/\#{`#{regexp_node.value.value}`}/"
 | 
			
		||||
          else
 | 
			
		||||
            Regexp.new(regexp_node.value).inspect
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def add_argument_parentheses(corrector, arg_node)
 | 
			
		||||
          return unless method_call_with_no_parentheses?(arg_node)
 | 
			
		||||
 | 
			
		||||
          first_argument_range = range_with_surrounding_space(
 | 
			
		||||
            arg_node.first_argument.source_range, side: :left
 | 
			
		||||
          )
 | 
			
		||||
          corrector.insert_before(first_argument_range, '(')
 | 
			
		||||
          corrector.insert_after(arg_node.last_argument, ')')
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        def method_call_with_no_parentheses?(arg_node)
 | 
			
		||||
          arg_node.send_type? && arg_node.arguments? && !arg_node.parenthesized?
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # `have_current_path` with no options will include the querystring
 | 
			
		||||
        # while `page.current_path` does not.
 | 
			
		||||
        # This ensures the option `ignore_query: true` is added
 | 
			
		||||
        # except when the expectation is a regexp or string
 | 
			
		||||
        def add_ignore_query_options(corrector, node)
 | 
			
		||||
          expectation_node = node.parent.last_argument
 | 
			
		||||
          expectation_last_child = expectation_node.children.last
 | 
			
		||||
          return if %i[
 | 
			
		||||
            regexp str dstr xstr
 | 
			
		||||
          ].include?(expectation_last_child.type)
 | 
			
		||||
 | 
			
		||||
          corrector.insert_after(
 | 
			
		||||
            expectation_last_child,
 | 
			
		||||
            ', ignore_query: true'
 | 
			
		||||
          )
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,58 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module RuboCop
 | 
			
		||||
  module Cop
 | 
			
		||||
    module Capybara
 | 
			
		||||
      # Checks for usage of deprecated style methods.
 | 
			
		||||
      #
 | 
			
		||||
      # @example when using `assert_style`
 | 
			
		||||
      #   # bad
 | 
			
		||||
      #   page.find(:css, '#first').assert_style(display: 'block')
 | 
			
		||||
      #
 | 
			
		||||
      #   # good
 | 
			
		||||
      #   page.find(:css, '#first').assert_matches_style(display: 'block')
 | 
			
		||||
      #
 | 
			
		||||
      # @example when using `has_style?`
 | 
			
		||||
      #   # bad
 | 
			
		||||
      #   expect(page.find(:css, 'first')
 | 
			
		||||
      #     .has_style?(display: 'block')).to be true
 | 
			
		||||
      #
 | 
			
		||||
      #   # good
 | 
			
		||||
      #   expect(page.find(:css, 'first')
 | 
			
		||||
      #     .matches_style?(display: 'block')).to be true
 | 
			
		||||
      #
 | 
			
		||||
      # @example when using `have_style`
 | 
			
		||||
      #   # bad
 | 
			
		||||
      #   expect(page).to have_style(display: 'block')
 | 
			
		||||
      #
 | 
			
		||||
      #   # good
 | 
			
		||||
      #   expect(page).to match_style(display: 'block')
 | 
			
		||||
      #
 | 
			
		||||
      class MatchStyle < ::RuboCop::Cop::Base
 | 
			
		||||
        extend AutoCorrector
 | 
			
		||||
 | 
			
		||||
        MSG = 'Use `%<good>s` instead of `%<bad>s`.'
 | 
			
		||||
        RESTRICT_ON_SEND = %i[assert_style has_style? have_style].freeze
 | 
			
		||||
        PREFERRED_METHOD = {
 | 
			
		||||
          'assert_style' => 'assert_matches_style',
 | 
			
		||||
          'has_style?' => 'matches_style?',
 | 
			
		||||
          'have_style' => 'match_style'
 | 
			
		||||
        }.freeze
 | 
			
		||||
 | 
			
		||||
        def on_send(node)
 | 
			
		||||
          method_node = node.loc.selector
 | 
			
		||||
          add_offense(method_node) do |corrector|
 | 
			
		||||
            corrector.replace(method_node,
 | 
			
		||||
                              PREFERRED_METHOD[method_node.source])
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        private
 | 
			
		||||
 | 
			
		||||
        def message(node)
 | 
			
		||||
          format(MSG, good: PREFERRED_METHOD[node.source], bad: node.source)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -1,131 +0,0 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module RuboCop
 | 
			
		||||
  module Cop
 | 
			
		||||
    module Capybara
 | 
			
		||||
      # Help methods for capybara.
 | 
			
		||||
      # @api private
 | 
			
		||||
      module CapybaraHelp
 | 
			
		||||
        COMMON_OPTIONS = %w[
 | 
			
		||||
          id class style
 | 
			
		||||
        ].freeze
 | 
			
		||||
        SPECIFIC_OPTIONS = {
 | 
			
		||||
          'button' => (
 | 
			
		||||
            COMMON_OPTIONS + %w[disabled name value title type]
 | 
			
		||||
          ).freeze,
 | 
			
		||||
          'link' => (
 | 
			
		||||
            COMMON_OPTIONS + %w[href alt title download]
 | 
			
		||||
          ).freeze,
 | 
			
		||||
          'table' => (
 | 
			
		||||
            COMMON_OPTIONS + %w[cols rows]
 | 
			
		||||
          ).freeze,
 | 
			
		||||
          'select' => (
 | 
			
		||||
            COMMON_OPTIONS + %w[
 | 
			
		||||
              disabled name placeholder
 | 
			
		||||
              selected multiple
 | 
			
		||||
            ]
 | 
			
		||||
          ).freeze,
 | 
			
		||||
          'field' => (
 | 
			
		||||
            COMMON_OPTIONS + %w[
 | 
			
		||||
              checked disabled name placeholder
 | 
			
		||||
              readonly type multiple
 | 
			
		||||
            ]
 | 
			
		||||
          ).freeze
 | 
			
		||||
        }.freeze
 | 
			
		||||
        SPECIFIC_PSEUDO_CLASSES = %w[
 | 
			
		||||
          not() disabled enabled checked unchecked
 | 
			
		||||
        ].freeze
 | 
			
		||||
 | 
			
		||||
        module_function
 | 
			
		||||
 | 
			
		||||
        # @param node [RuboCop::AST::SendNode]
 | 
			
		||||
        # @param locator [String]
 | 
			
		||||
        # @param element [String]
 | 
			
		||||
        # @return [Boolean]
 | 
			
		||||
        def replaceable_option?(node, locator, element)
 | 
			
		||||
          attrs = CssSelector.attributes(locator).keys
 | 
			
		||||
          return false unless replaceable_element?(node, element, attrs)
 | 
			
		||||
 | 
			
		||||
          attrs.all? do |attr|
 | 
			
		||||
            SPECIFIC_OPTIONS.fetch(element, []).include?(attr)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # @param selector [String]
 | 
			
		||||
        # @return [Boolean]
 | 
			
		||||
        # @example
 | 
			
		||||
        #   common_attributes?('a[focused]') # => true
 | 
			
		||||
        #   common_attributes?('button[focused][visible]') # => true
 | 
			
		||||
        #   common_attributes?('table[id=some-id]') # => true
 | 
			
		||||
        #   common_attributes?('h1[invalid]') # => false
 | 
			
		||||
        def common_attributes?(selector)
 | 
			
		||||
          CssSelector.attributes(selector).keys.difference(COMMON_OPTIONS).none?
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # @param attrs [Array<String>]
 | 
			
		||||
        # @return [Boolean]
 | 
			
		||||
        # @example
 | 
			
		||||
        #   replaceable_attributes?('table[id=some-id]') # => true
 | 
			
		||||
        #   replaceable_attributes?('a[focused]') # => false
 | 
			
		||||
        def replaceable_attributes?(attrs)
 | 
			
		||||
          attrs.values.none?(&:nil?)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # @param locator [String]
 | 
			
		||||
        # @return [Boolean]
 | 
			
		||||
        def replaceable_pseudo_classes?(locator)
 | 
			
		||||
          CssSelector.pseudo_classes(locator).all? do |pseudo_class|
 | 
			
		||||
            replaceable_pseudo_class?(pseudo_class, locator)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # @param pseudo_class [String]
 | 
			
		||||
        # @param locator [String]
 | 
			
		||||
        # @return [Boolean]
 | 
			
		||||
        def replaceable_pseudo_class?(pseudo_class, locator)
 | 
			
		||||
          return false unless SPECIFIC_PSEUDO_CLASSES.include?(pseudo_class)
 | 
			
		||||
 | 
			
		||||
          case pseudo_class
 | 
			
		||||
          when 'not()' then replaceable_pseudo_class_not?(locator)
 | 
			
		||||
          else true
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # @param locator [String]
 | 
			
		||||
        # @return [Boolean]
 | 
			
		||||
        def replaceable_pseudo_class_not?(locator)
 | 
			
		||||
          locator.scan(/not\(.*?\)/).all? do |negation|
 | 
			
		||||
            CssSelector.attributes(negation).values.all? do |v|
 | 
			
		||||
              v.is_a?(TrueClass) || v.is_a?(FalseClass)
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # @param node [RuboCop::AST::SendNode]
 | 
			
		||||
        # @param element [String]
 | 
			
		||||
        # @param attrs [Array<String>]
 | 
			
		||||
        # @return [Boolean]
 | 
			
		||||
        def replaceable_element?(node, element, attrs)
 | 
			
		||||
          case element
 | 
			
		||||
          when 'link' then replaceable_to_link?(node, attrs)
 | 
			
		||||
          else true
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # @param node [RuboCop::AST::SendNode]
 | 
			
		||||
        # @param attrs [Array<String>]
 | 
			
		||||
        # @return [Boolean]
 | 
			
		||||
        def replaceable_to_link?(node, attrs)
 | 
			
		||||
          include_option?(node, :href) || attrs.include?('href')
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        # @param node [RuboCop::AST::SendNode]
 | 
			
		||||
        # @param option [Symbol]
 | 
			
		||||
        # @return [Boolean]
 | 
			
		||||
        def include_option?(node, option)
 | 
			
		||||
          node.each_descendant(:sym).find { |opt| opt.value == option }
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user