[
  {
    "path": ".editorconfig",
    "content": "# https://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.yml]\nindent_size = 2\n\n[Makefile]\nindent_style = tab\nindent_size = 8\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Report a possible bug in HTTPie\ntitle: ''\nlabels: \"new, bug\"\nassignees: ''\n\n---\n\n## Checklist\n\n- [ ] I've searched for similar issues.\n- [ ] I'm using the latest version of HTTPie.\n\n---\n\n## Minimal reproduction code and steps\n\n1.\n2.\n3.\n\n## Current result\n\n…\n\n## Expected result\n\n…\n\n---\n\n## Debug output\n\nPlease re-run the command with `--debug`, then copy the entire command & output and paste both below:\n\n```bash\n$ http --debug <COMPLETE ARGUMENT LIST THAT TRIGGERS THE ERROR>\n<COMPLETE OUTPUT>\n```\n\n## Additional information, screenshots, or code examples\n\n…\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an enhancement for HTTPie\ntitle: ''\nlabels: \"new, enhancement\"\nassignees: ''\n\n---\n\n## Checklist\n\n- [ ] I've searched for similar feature requests.\n\n---\n\n## Enhancement request\n\n…\n\n---\n\n## Problem it solves\n\nE.g. “I'm always frustrated when […]”, “I’m trying to do […] so that […]”.\n\n---\n\n## Additional information, screenshots, or code examples\n\n…\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/other.md",
    "content": "---\nname: Other\nabout: Anything else that isn't a feature or a bug\ntitle: ''\nlabels: \"new\"\nassignees: ''\n\n---\n\nIf you have a general question, please consider asking on Discord: https://httpie.io/chat\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # GitHub Actions\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: daily\n    assignees:\n      - BoboTiG\n"
  },
  {
    "path": ".github/workflows/benchmark.yml",
    "content": "name: Benchmark\n\non:\n  pull_request:\n    types: [ labeled ]\n\npermissions:\n  issues: write\n  pull-requests: write\n\njobs:\n  test:\n    if: github.event.label.name == 'benchmark'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-python@v4\n        with:\n          python-version: \"3.9\"\n\n      - id: benchmarks\n        name: Run Benchmarks\n        run: |\n          python -m pip install pyperf>=2.3.0\n          python extras/profiling/run.py --fresh --complex  --min-speed=6 --file output.txt\n          body=$(cat output.txt)\n          body=\"${body//'%'/'%25'}\"\n          body=\"${body//$'\\n'/'%0A'}\"\n          body=\"${body//$'\\r'/'%0D'}\"\n          echo \"::set-output name=body::$body\"\n\n      - name: Find Comment\n        uses: peter-evans/find-comment@v2\n        id: fc\n        with:\n          issue-number: ${{ github.event.pull_request.number }}\n          comment-author: 'github-actions[bot]'\n          body-includes: '# Benchmarks'\n\n      - name: Create or update comment\n        uses: peter-evans/create-or-update-comment@v2\n        with:\n          comment-id: ${{ steps.fc.outputs.comment-id }}\n          issue-number: ${{ github.event.pull_request.number }}\n          body: |\n            # Benchmarks\n            ${{ steps.benchmarks.outputs.body }}\n          edit-mode: replace\n\n      - uses: actions-ecosystem/action-remove-labels@v1\n        with:\n          labels: benchmark\n"
  },
  {
    "path": ".github/workflows/code-style.yml",
    "content": "name: Code Style Check\n\non:\n  pull_request:\n    paths:\n      - .github/workflows/code-style.yml\n      - extras/*.py\n      - httpie/**/*.py\n      - setup.py\n      - tests/**/*.py\n\njobs:\n  code-style:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-python@v4\n        with:\n          python-version: 3.9\n      - run: make venv\n      - run: make codestyle\n"
  },
  {
    "path": ".github/workflows/content.yml",
    "content": "name: Update Generated Content\non:\n  push:\n    branches:\n      - master\njobs:\n  update-content:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-python@v4\n        with:\n          python-version: 3.9\n      - run: make content\n      - name: Create Pull Request\n        id: cpr\n        uses: peter-evans/create-pull-request@v4\n        with:\n          commit-message: \"[automated] Update generated content\"\n          title: \"[automated] Update generated content\"\n          delete-branch: true\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/coverage.yml",
    "content": "name: Coverage\n\non:\n  pull_request:\n    paths:\n      - .github/workflows/coverage.yml\n      - httpie/**/*.py\n      - setup.*\n      - tests/**/*.py\n\njobs:\n  coverage:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-python@v4\n        with:\n          python-version: \"3.10\"\n      - run: make install\n      - run: make test-cover\n      - run: make codecov-upload\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_REPO_TOKEN }}\n      - run: make test-dist\n"
  },
  {
    "path": ".github/workflows/docs-check-markdown.yml",
    "content": "name: Check Markdown Style\n\non:\n  pull_request:\n    paths:\n      - \"*.md\"\n      - \"**/*.md\"\n\njobs:\n  doc:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Setup Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: 2.7\n      - name: Install the linter\n        run: sudo gem install mdl\n      - name: Check files\n        run: make doc-check\n"
  },
  {
    "path": ".github/workflows/docs-deploy.yml",
    "content": "name: Deploy Documentation\n\non:\n  push:\n    branches:\n      - master\n    paths:\n      - docs/README.md\n      - docs/config.json\n  release:\n    types:\n      - published\n      - unpublished\n      - deleted\njobs:\n  trigger-doc-build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install HTTPie\n        run: sudo pip install httpie\n      - name: Trigger new documentation build\n        run: http --ignore-stdin POST ${{ secrets.DOCS_UPDATE_VERCEL_HOOK }}\n"
  },
  {
    "path": ".github/workflows/release-brew.yml",
    "content": "name: Release on Homebrew\n\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: \"The branch, tag or SHA to release from\"\n        required: true\n        default: \"master\"\n\njobs:\n  brew-release:\n    name: Release the Homebrew Package\n    runs-on: macos-latest\n\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          ref: ${{ github.event.inputs.branch }}\n\n      - uses: mislav/bump-homebrew-formula-action@v2\n        with:\n          formula-name: httpie\n          tag-name: ${{ github.events.inputs.branch }}\n        env:\n          COMMITTER_TOKEN: ${{ secrets.BREW_UPDATE_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release-choco.yml",
    "content": "name: Release on Chocolatey\n\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: \"The branch, tag or SHA to release from\"\n        required: true\n        default: \"master\"\n\njobs:\n  brew-release:\n    name: Release the Chocolatey\n    runs-on: windows-2019\n    env:\n        package-dir: docs\\packaging\\windows-chocolatey\n\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          ref: ${{ github.event.inputs.branch }}\n\n      # Chocolatey comes already installed on the Windows GHA image\n      - name: Build the Choco package\n        shell: cmd\n        run: choco pack -v\n        working-directory: ${{ env.package-dir }}\n\n      - name: Check the Choco package\n        run: choco info httpie -s .\n        working-directory: ${{ env.package-dir }}\n\n      - name: Local installation\n        run: |\n          choco install httpie -y -dv -s \"'.;https://community.chocolatey.org/api/v2/'\"\n        working-directory: ${{ env.package-dir }}\n\n      - name: Test the locally installed binaries\n        run: |\n          # Source: https://stackoverflow.com/a/46760714/15330941\n\n          # Make `refreshenv` available right away, by defining the $env:ChocolateyInstall\n          # variable and importing the Chocolatey profile module.\n          $env:ChocolateyInstall = Convert-Path \"$((Get-Command choco).Path)\\..\\..\"\n          Import-Module \"$env:ChocolateyInstall\\helpers\\chocolateyProfile.psm1\"\n          refreshenv\n\n          http --version\n          https --version\n          httpie --version\n          choco uninstall -y httpie\n        working-directory: ${{ env.package-dir }}\n\n      - name: Publish on Chocolatey\n        shell: bash\n        env:\n          CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }}\n        run: |\n          choco apikey --key $CHOCO_API_KEY --source https://push.chocolatey.org/\n          choco push httpie*.nupkg --source https://push.chocolatey.org/\n        working-directory: ${{ env.package-dir }}\n"
  },
  {
    "path": ".github/workflows/release-linux-standalone.yml",
    "content": "name: Release as Standalone Linux Package\n\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: \"The branch, tag or SHA to release from\"\n        required: true\n        default: \"master\"\n      tag_name:\n        description: \"Which release to upload the artifacts to (e.g., 3.0)\"\n        required: true\n\n  release:\n    types: [released, prereleased]\n\n\njobs:\n  binary-build-and-release:\n    name: Build and Release\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          ref: ${{ github.event.inputs.branch }}\n\n      - uses: actions/setup-python@v4\n        with:\n          python-version: 3.9\n\n      - name: Build Artifacts\n        run: |\n          cd extras/packaging/linux\n          ./get_release_artifacts.sh\n\n      - uses: actions/upload-artifact@v3\n        with:\n          name: http\n          path: extras/packaging/linux/artifacts/dist/http\n\n      - uses: actions/upload-artifact@v3\n        with:\n          name: httpie.deb\n          path: extras/packaging/linux/artifacts/dist/*.deb\n\n      - uses: actions/upload-artifact@v3\n        with:\n          name: httpie.rpm\n          path: extras/packaging/linux/artifacts/dist/*.rpm\n\n      - name: Determine the release upload upload_url\n        id: release_id\n        run: |\n          pip install httpie\n          export API_URL=\"api.github.com/repos/httpie/cli/releases/tags/${{ github.event.inputs.tag_name }}\"\n          export UPLOAD_URL=`https --ignore-stdin GET $API_URL | jq -r \".upload_url\"`\n          echo \"::set-output name=UPLOAD_URL::$UPLOAD_URL\"\n\n      - name: Publish Debian Package\n        uses: actions/upload-release-asset@v1.0.2\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.release_id.outputs.UPLOAD_URL }}\n          asset_path: extras/packaging/linux/artifacts/dist/httpie_${{ github.event.inputs.tag_name }}_amd64.deb\n          asset_name: httpie-${{ github.event.inputs.tag_name }}.deb\n          asset_content_type: binary/octet-stream\n\n      - name: Publish Single Executable\n        uses: actions/upload-release-asset@v1.0.2\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.release_id.outputs.UPLOAD_URL }}\n          asset_path: extras/packaging/linux/artifacts/dist/http\n          asset_name: http\n          asset_content_type: binary/octet-stream\n"
  },
  {
    "path": ".github/workflows/release-pypi.yml",
    "content": "name: Release on PyPI\n\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: \"The branch, tag or SHA to release from\"\n        required: true\n        default: \"master\"\n\njobs:\n  pypi-build-and-release:\n    name: Build and Release\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          ref: ${{ github.event.inputs.branch }}\n\n      - uses: actions/setup-python@v4\n        with:\n          python-version: 3.9\n\n      - name: Build a binary wheel and a source tarball\n        run: make install && make build\n\n      - name: Release on PyPI\n        uses: pypa/gh-action-pypi-publish@master\n        with:\n          password: ${{ secrets.PYPI_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release-snap.yml",
    "content": "name: Release on Snap\n\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: \"The branch, tag or SHA to release from\"\n        required: true\n        default: \"master\"\n\njobs:\n  snap-build-and-release:\n    name: Build & Release the Snap Package\n    runs-on: ubuntu-latest\n\n    strategy:\n      # If any of the stages fail, then we'll stop the action\n      # to give release manager time to investigate the underlying\n      # issue.\n      fail-fast: true\n      matrix:\n        level: [edge, beta, candidate, stable]\n\n    # Set the concurrency level for this version, so\n    # that we'll release one by one.\n    concurrency: ${{ github.event.inputs.branch }}\n\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          ref: ${{ github.event.inputs.branch }}\n\n      - uses: snapcore/action-build@v1\n        id: build\n\n      - uses: snapcore/action-publish@v1\n        env:\n          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}\n        with:\n          snap: ${{ steps.build.outputs.snap }}\n          release: ${{ matrix.level }}\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Mark stale pull requests\n\non: workflow_dispatch\n\npermissions:\n  pull-requests: write\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/stale@v8\n      with:\n        close-pr-message: 'Thanks for the pull request, but since it was stale for more than a 30 days we are closing it. If you want to work back on it, feel free to re-open it or create a new one.'\n        stale-pr-label: 'stale'\n\n        days-before-stale: -1\n        days-before-issue-stale: -1\n        days-before-pr-stale: 30\n\n        days-before-close: -1\n        days-before-issue-close: -1\n        days-before-pr-close: 0\n        \n        operations-per-run: 300\n"
  },
  {
    "path": ".github/workflows/test-package-linux-snap.yml",
    "content": "name: Test Snap Package (Linux)\n\n\non:\n  pull_request:\n    paths:\n      - .github/workflows/test-package-linux-snap.yml\n      - snapcraft.yaml\n  workflow_dispatch:\n\njobs:\n  snap:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Build\n        uses: snapcore/action-build@v1\n        id: snapcraft\n      - name: Install\n        run: sudo snap install --dangerous ${{ steps.snapcraft.outputs.snap }}\n      - name: Test\n        run: |\n          httpie.http --version\n          httpie.https --version\n          httpie --version\n          # Auto-aliases cannot be tested when installing a snap outside the store.\n          # http --version\n          # https --version\n"
  },
  {
    "path": ".github/workflows/test-package-mac-brew.yml",
    "content": "name: Test Brew Package (MacOS)\n\non:\n  pull_request:\n    paths:\n      - .github/workflows/test-package-mac-brew.yml\n      - docs/packaging/brew/httpie.rb\n  workflow_dispatch:\n\njobs:\n  brew:\n    runs-on: macos-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Setup brew\n        run: |\n          brew developer on\n          brew update\n      - name: Build and test the formula\n        run: make brew-test\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\nconcurrency:\n  group: ${{ github.head_ref || github.run_id }}\n  cancel-in-progress: true\n\non:\n  push:\n    branches:\n      - master\n    paths:\n      - .github/workflows/tests.yml\n      - httpie/**/*.py\n      - setup.*\n      - tests/**/*.py\n  pull_request:\n    paths:\n      - .github/workflows/tests.yml\n      - httpie/**/*.py\n      - setup.*\n      - tests/**/*.py\n\njobs:\n  test:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, macos-13, windows-latest]\n        python-version:\n          - '3.12'\n          - '3.11'\n          - '3.10'\n          - '3.9'\n          - '3.8'\n          - '3.7'\n        pyopenssl: [0, 1]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v4\n        with:\n          python-version: ${{ matrix.python-version }}\n      - name: Windows setup\n        if: matrix.os == 'windows-latest'\n        run: |\n          python -m pip install --upgrade pip wheel\n          python -m pip install --upgrade '.[dev]'\n          python -m pytest --verbose ./httpie ./tests\n        env:\n          HTTPIE_TEST_WITH_PYOPENSSL: ${{ matrix.pyopenssl }}\n      - name: Linux & Mac setup\n        if: matrix.os != 'windows-latest'\n        run: |\n          make install\n          make test\n        env:\n          HTTPIE_TEST_WITH_PYOPENSSL: ${{ matrix.pyopenssl }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.idea/\n*.egg-info\n.cache/\n*.pyc\nhtmlcov\n\n\n##############################################################################\n# The below is GitHub template for Python project. gitignore.\n# <https://github.com/github/gitignore/blob/master/Python.gitignore>\n##############################################################################\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.spec\n*.manifest\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nvenv*/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# Packit\n/httpie.spec\n/httpie-*.rpm\n/httpie-*.tar.gz\n\n# VS Code\n.vscode/\n\n# Windows Chocolatey\n*.nupkg\n\nartifacts/\n"
  },
  {
    "path": ".packit.yaml",
    "content": "# See the documentation for more information:\n# https://packit.dev/docs/configuration/\nspecfile_path: httpie.spec\nactions:\n  # get the current Fedora Rawhide specfile:\n  post-upstream-clone: \"wget https://src.fedoraproject.org/rpms/httpie/raw/rawhide/f/httpie.spec -O httpie.spec\"\n  # Use this when the latest spec is not up-to-date.\n  # post-upstream-clone: \"cp docs/packaging/linux-fedora/httpie.spec.txt httpie.spec\"\njobs:\n- job: propose_downstream\n  trigger: release\n  metadata:\n    dist_git_branches:\n    - rawhide\n"
  },
  {
    "path": "AUTHORS.md",
    "content": "# HTTPie authors\n\n- [Jakub Roztocil](https://github.com/jakubroztocil)\n\n## Patches, features, ideas\n\n[Complete list of contributors on GitHub](https://github.com/httpie/cli/graphs/contributors)\n\n- [Cláudia T. Delgado](https://github.com/claudiatd)\n- [Hank Gay](https://github.com/gthank)\n- [Jake Basile](https://github.com/jakebasile)\n- [Vladimir Berkutov](https://github.com/dair-targ)\n- [Jakob Kramer](https://github.com/gandaro)\n- [Chris Faulkner](https://github.com/faulkner)\n- [Alen Mujezinovic](https://github.com/flashingpumpkin)\n- [Praful Mathur](https://github.com/tictactix)\n- [Marc Abramowitz](https://github.com/msabramo)\n- [Ismail Badawi](https://github.com/isbadawi)\n- [Laurent Bachelier](https://github.com/laurentb)\n- [Isman Firmansyah](https://github.com/iromli)\n- [Simon Olofsson](https://github.com/simono)\n- [Churkin Oleg](https://github.com/Bahus)\n- [Jökull Sólberg Auðunsson](https://github.com/jokull)\n- [Matthew M. Boedicker](https://github.com/mmb)\n- [marblar](https://github.com/marblar)\n- [Tomek Wójcik](https://github.com/tomekwojcik)\n- [Davey Shafik](https://github.com/dshafik)\n- [cido](https://github.com/cido)\n- [Justin Bonnar](https://github.com/jargonjustin)\n- [Nathan LaFreniere](https://github.com/nlf)\n- [Matthias Lehmann](https://github.com/matleh)\n- [Dennis Brakhane](https://github.com/brakhane)\n- [Matt Layman](https://github.com/mblayman)\n- [Edward Yang](https://github.com/honorabrutroll)\n- [Aleksandr Vinokurov](https://github.com/aleksandr-vin)\n- [Jeff Byrnes](https://github.com/jeffbyrnes)\n- [Denis Belavin](https://github.com/LuckyDenis)\n- [Mickaël Schoentgen](https://github.com/BoboTiG)\n- [Elena Lape](https://github.com/elenalape)\n- [Rohit Sehgal](https://github.com/r0hi7)\n- [Bartłomiej Jacak](https://github.com/bartekjacak)\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\nThis document records all notable changes to [HTTPie](https://httpie.io).\nThis project adheres to [Semantic Versioning](https://semver.org/).\n\n## [3.2.4](https://github.com/httpie/cli/compare/3.2.3...3.2.4) (2024-11-01)\n\n- Fix default certs loading and unpin `requests`. ([#1596](https://github.com/httpie/cli/issues/1596))\n\n## [3.2.3](https://github.com/httpie/cli/compare/3.2.2...3.2.3) (2024-07-10)\n\n- Fix SSL connections by pinning the `requests` version to `2.31.0`. (#1583, #1581)\n- Make it possible to [unset](https://httpie.io/docs/cli/default-request-headers) the `User-Agent` and `Accept-Encoding` request headers. ([#1502](https://github.com/httpie/cli/issues/1502))\n\n## [3.2.2](https://github.com/httpie/cli/compare/3.2.1...3.2.2) (2023-05-19)\n\n- Fixed compatibility with urllib3 2.0.0. ([#1499](https://github.com/httpie/cli/issues/1499))\n\n## [3.2.1](https://github.com/httpie/cli/compare/3.1.0...3.2.1) (2022-05-06)\n\n- Improved support for determining auto-streaming when the `Content-Type` header includes encoding information. ([#1383](https://github.com/httpie/cli/pull/1383))\n- Fixed the display of the crash happening in the secondary process for update checks. ([#1388](https://github.com/httpie/cli/issues/1388))\n\n## [3.2.0](https://github.com/httpie/cli/compare/3.1.0...3.2.0) (2022-05-05)\n\n- Added a warning for notifying the user about the new updates. ([#1336](https://github.com/httpie/cli/pull/1336))\n- Added support for single binary executables. ([#1330](https://github.com/httpie/cli/pull/1330))\n- Added support for man pages (and auto generation of them from the parser declaration). ([#1317](https://github.com/httpie/cli/pull/1317))\n- Added `http --manual` for man pages & regular manual with pager. ([#1343](https://github.com/httpie/cli/pull/1343))\n- Added support for session persistence of repeated headers with the same name. ([#1335](https://github.com/httpie/cli/pull/1335))\n- Added support for sending `Secure` cookies to the `localhost` (and `.local` suffixed domains). ([#1308](https://github.com/httpie/cli/issues/1308))\n- Improved UI for the progress bars. ([#1324](https://github.com/httpie/cli/pull/1324))\n- Fixed redundant creation of `Content-Length` header on `OPTIONS` requests. ([#1310](https://github.com/httpie/cli/issues/1310))\n- Fixed blocking of warning thread on some use cases. ([#1349](https://github.com/httpie/cli/issues/1349))\n- Changed `httpie plugins` to the new `httpie cli` namespace as `httpie cli plugins` (`httpie plugins` continues to work as a hidden alias). ([#1320](https://github.com/httpie/cli/issues/1320))\n- Soft deprecated the `--history-print`. ([#1380](https://github.com/httpie/cli/pull/1380))\n\n## [3.1.0](https://github.com/httpie/cli/compare/3.0.2...3.1.0) (2022-03-08)\n\n- **SECURITY** Fixed the [vulnerability](https://github.com/httpie/cli/security/advisories/GHSA-9w4w-cpc8-h2fq) that caused exposure of cookies on redirects to third party hosts. ([#1312](https://github.com/httpie/cli/pull/1312))\n- Fixed escaping of integer indexes with multiple backslashes in the nested JSON builder. ([#1285](https://github.com/httpie/cli/issues/1285))\n- Fixed displaying of status code without a status message on non-`auto` themes. ([#1300](https://github.com/httpie/cli/issues/1300))\n- Fixed redundant issuance of stdin detection warnings on some rare cases due to underlying implementation. ([#1303](https://github.com/httpie/cli/pull/1303))\n- Fixed double `--quiet` so that it will now suppress all python level warnings. ([#1271](https://github.com/httpie/cli/issues/1271))\n- Added support for specifying certificate private key passphrases through `--cert-key-pass` and prompts. ([#946](https://github.com/httpie/cli/issues/946))\n- Added `httpie cli export-args` command for exposing the parser specification for the `http`/`https` commands. ([#1293](https://github.com/httpie/cli/pull/1293))\n- Improved regulation of top-level arrays. ([#1292](https://github.com/httpie/cli/commit/225dccb2186f14f871695b6c4e0bfbcdb2e3aa28))\n- Improved UI layout for standalone invocations. ([#1296](https://github.com/httpie/cli/pull/1296))\n\n## [3.0.2](https://github.com/httpie/cli/compare/3.0.1...3.0.2) (2022-01-24)\n\n[What’s new in HTTPie for Terminal 3.0 →](https://httpie.io/blog/httpie-3.0.0)\n\n- Fixed usage of `httpie` when there is a presence of a config with `default_options`. ([#1280](https://github.com/httpie/cli/pull/1280))\n\n## [3.0.1](https://github.com/httpie/cli/compare/3.0.0...3.0.1) (2022-01-23)\n\n[What’s new in HTTPie for Terminal 3.0 →](https://httpie.io/blog/httpie-3.0.0)\n\n- Changed the value shown as time elapsed from time-to-read-headers to total exchange time. ([#1277](https://github.com/httpie/cli/issues/1277))\n\n## [3.0.0](https://github.com/httpie/cli/compare/2.6.0...3.0.0) (2022-01-21)\n\n[What’s new in HTTPie for Terminal 3.0 →](https://httpie.io/blog/httpie-3.0.0)\n\n- Dropped support for Python 3.6. ([#1177](https://github.com/httpie/cli/issues/1177))\n- Improved startup time by 40%. ([#1211](https://github.com/httpie/cli/pull/1211))\n- Added support for nested JSON syntax. ([#1169](https://github.com/httpie/cli/issues/1169))\n- Added `httpie plugins` interface for plugin management. ([#566](https://github.com/httpie/cli/issues/566))\n- Added support for Bearer authentication via `--auth-type=bearer` ([#1215](https://github.com/httpie/cli/issues/1215)).\n- Added support for quick conversions of pasted URLs into HTTPie calls by adding a space after the protocol name (`$ https ://pie.dev` → `https://pie.dev`). ([#1195](https://github.com/httpie/cli/issues/1195))\n- Added support for _sending_ multiple HTTP header lines with the same name. ([#130](https://github.com/httpie/cli/issues/130))\n- Added support for _receiving_ multiple HTTP headers lines with the same name. ([#1207](https://github.com/httpie/cli/issues/1207))\n- Added support for basic JSON types on `--form`/`--multipart` when using JSON only operators (`:=`/`:=@`). ([#1212](https://github.com/httpie/cli/issues/1212))\n- Added support for automatically enabling `--stream` when `Content-Type` is `text/event-stream`. ([#376](https://github.com/httpie/cli/issues/376))\n- Added support for displaying the total elapsed time through `--meta`/`-vv` or `--print=m`. ([#243](https://github.com/httpie/cli/issues/243))\n- Added new `pie-dark`/`pie-light` (and `pie`) styles that match with [HTTPie for Web and Desktop](https://httpie.io/product). ([#1237](https://github.com/httpie/cli/issues/1237))\n- Added support for better error handling on DNS failures. ([#1248](https://github.com/httpie/cli/issues/1248))\n- Added support for storing prompted passwords in the local sessions. ([#1098](https://github.com/httpie/cli/issues/1098))\n- Added warnings about the `--ignore-stdin`, when there is no incoming data from stdin. ([#1255](https://github.com/httpie/cli/issues/1255))\n- Fixed crashing due to broken plugins. ([#1204](https://github.com/httpie/cli/issues/1204))\n- Fixed auto addition of XML declaration to every formatted XML response. ([#1156](https://github.com/httpie/cli/issues/1156))\n- Fixed highlighting when `Content-Type` specifies `charset`. ([#1242](https://github.com/httpie/cli/issues/1242))\n- Fixed an unexpected crash when `--raw` is used with `--chunked`. ([#1253](https://github.com/httpie/cli/issues/1253))\n- Changed the default Windows theme from `fruity` to `auto`. ([#1266](https://github.com/httpie/cli/issues/1266))\n\n## [2.6.0](https://github.com/httpie/cli/compare/2.5.0...2.6.0) (2021-10-14)\n\n[What’s new in HTTPie for Terminal 2.6.0 →](https://httpie.io/blog/httpie-2.6.0)\n\n- Added support for formatting & coloring of JSON bodies preceded by non-JSON data (e.g., an XXSI prefix). ([#1130](https://github.com/httpie/cli/issues/1130))\n- Added charset auto-detection when `Content-Type` doesn’t include it. ([#1110](https://github.com/httpie/cli/issues/1110), [#1168](https://github.com/httpie/cli/issues/1168))\n- Added `--response-charset` to allow overriding the response encoding for terminal display purposes. ([#1168](https://github.com/httpie/cli/issues/1168))\n- Added `--response-mime` to allow overriding the response mime type for coloring and formatting for the terminal. ([#1168](https://github.com/httpie/cli/issues/1168))\n- Added the ability to silence warnings through using `-q` or `--quiet` twice (e.g. `-qq`) ([#1175](https://github.com/httpie/cli/issues/1175))\n- Added installed plugin list to `--debug` output. ([#1165](https://github.com/httpie/cli/issues/1165))\n- Fixed duplicate keys preservation in JSON data. ([#1163](https://github.com/httpie/cli/issues/1163))\n\n## [2.5.0](https://github.com/httpie/cli/compare/2.4.0...2.5.0) (2021-09-06)\n\n[What’s new in HTTPie for Terminal 2.5.0 →](https://httpie.io/blog/httpie-2.5.0)\n\n- Added `--raw` to allow specifying the raw request body without extra processing as\n  an alternative to `stdin`. ([#534](https://github.com/httpie/cli/issues/534))\n- Added support for XML formatting. ([#1129](https://github.com/httpie/cli/issues/1129))\n- Added internal support for file-like object responses to improve adapter plugin support. ([#1094](https://github.com/httpie/cli/issues/1094))\n- Fixed `--continue --download` with a single byte to be downloaded left. ([#1032](https://github.com/httpie/cli/issues/1032))\n- Fixed `--verbose` HTTP 307 redirects with streamed request body. ([#1088](https://github.com/httpie/cli/issues/1088))\n- Fixed handling of session files with `Cookie:` followed by other headers. ([#1126](https://github.com/httpie/cli/issues/1126))\n\n## [2.4.0](https://github.com/httpie/cli/compare/2.3.0...2.4.0) (2021-02-06)\n\n- Added support for `--session` cookie expiration based on `Set-Cookie: max-age=<n>`. ([#1029](https://github.com/httpie/cli/issues/1029))\n- Show a `--check-status` warning with `--quiet` as well, not only when the output is redirected. ([#1026](https://github.com/httpie/cli/issues/1026))\n- Fixed upload with `--session` ([#1020](https://github.com/httpie/cli/issues/1020)).\n- Fixed a missing blank line between request and response ([#1006](https://github.com/httpie/cli/issues/1006)).\n\n## [2.3.0](https://github.com/httpie/cli/compare/2.2.0...2.3.0) (2020-10-25)\n\n- Added support for streamed uploads ([#201](https://github.com/httpie/cli/issues/201)).\n- Added support for multipart upload streaming ([#684](https://github.com/httpie/cli/issues/684)).\n- Added support for body-from-file upload streaming (`http pie.dev/post @file`).\n- Added `--chunked` to enable chunked transfer encoding ([#753](https://github.com/httpie/cli/issues/753)).\n- Added `--multipart` to allow `multipart/form-data` encoding for non-file `--form` requests as well.\n- Added support for preserving field order in multipart requests ([#903](https://github.com/httpie/cli/issues/903)).\n- Added `--boundary` to allow a custom boundary string for `multipart/form-data` requests.\n- Added support for combining cookies specified on the CLI and in a session file ([#932](https://github.com/httpie/cli/issues/932)).\n- Added out of the box SOCKS support with no extra installation ([#904](https://github.com/httpie/cli/issues/904)).\n- Added `--quiet, -q` flag to enforce silent behaviour.\n- Fixed the handling of invalid `expires` dates in `Set-Cookie` headers ([#963](https://github.com/httpie/cli/issues/963)).\n- Removed Tox testing entirely ([#943](https://github.com/httpie/cli/issues/943)).\n\n## [2.2.0](https://github.com/httpie/cli/compare/2.1.0...2.2.0) (2020-06-18)\n\n- Added support for custom content types for uploaded files ([#668](https://github.com/httpie/cli/issues/668)).\n- Added support for `$XDG_CONFIG_HOME` ([#920](https://github.com/httpie/cli/issues/920)).\n- Added support for `Set-Cookie`-triggered cookie expiration ([#853](https://github.com/httpie/cli/issues/853)).\n- Added `--format-options` to allow disabling sorting, etc. ([#128](https://github.com/httpie/cli/issues/128))\n- Added `--sorted` and `--unsorted` shortcuts for (un)setting all sorting-related `--format-options`. ([#128](https://github.com/httpie/cli/issues/128))\n- Added `--ciphers` to allow configuring OpenSSL ciphers ([#870](https://github.com/httpie/cli/issues/870)).\n- Added `netrc` support for auth plugins. Enabled for `--auth-type=basic`\n  and `digest`, 3rd parties may opt in ([#718](https://github.com/httpie/cli/issues/718), [#719](https://github.com/httpie/cli/issues/719), [#852](https://github.com/httpie/cli/issues/852), [#934](https://github.com/httpie/cli/issues/934)).\n- Fixed built-in plugins-related circular imports ([#925](https://github.com/httpie/cli/issues/925)).\n\n## [2.1.0](https://github.com/httpie/cli/compare/2.0.0...2.1.0) (2020-04-18)\n\n- Added `--path-as-is` to bypass dot segment (`/../` or `/./`)\n  URL squashing ([#895](https://github.com/httpie/cli/issues/895)).\n- Changed the default `Accept` header value for JSON requests from\n  `application/json, */*` to `application/json, */*;q=0.5`\n  to clearly indicate preference ([#488](https://github.com/httpie/cli/issues/488)).\n- Fixed `--form` file upload mixed with redirected `stdin` error handling\n  ([#840](https://github.com/httpie/cli/issues/840)).\n\n## [2.0.0](https://github.com/httpie/cli/compare/1.0.3...2.0.0) (2020-01-12)\n\n- Removed Python 2.7 support ([EOL Jan 2020](https://www.python.org/doc/sunset-python-2/).\n- Added `--offline` to allow building an HTTP request and printing it but not\n  actually sending it over the network.\n- Replaced the old collect-all-then-process handling of HTTP communication\n  with one-by-one processing of each HTTP request or response as they become\n  available. This means that you can see headers immediately,\n  see what is being sent even if the request fails, etc.\n- Removed automatic config file creation to avoid concurrency issues.\n- Removed the default 30-second connection `--timeout` limit.\n- Removed Python’s default limit of 100 response headers.\n- Added `--max-headers` to allow setting the max header limit.\n- Added `--compress` to allow request body compression.\n- Added `--ignore-netrc` to allow bypassing credentials from `.netrc`.\n- Added `https` alias command with `https://` as the default scheme.\n- Added `$ALL_PROXY` documentation.\n- Added type annotations throughout the codebase.\n- Added `tests/` to the PyPi package for the convenience of\n  downstream package maintainers.\n- Fixed an error when `stdin` was a closed fd.\n- Improved `--debug` output formatting.\n\n## [1.0.3](https://github.com/httpie/cli/compare/1.0.2...1.0.3) (2019-08-26)\n\n- Fixed CVE-2019-10751 — the way the output filename is generated for\n  `--download` requests without `--output` resulting in a redirect has\n  been changed to only consider the initial URL as the base for the generated\n  filename, and not the final one. This fixes a potential security issue under\n  the following scenario:\n\n  1. A `--download` request with no explicit `--output` is made (e.g.,\n     `$ http -d example.org/file.txt`), instructing httpie to\n     [generate the output filename](https://httpie.org/doc#downloaded-filename)\n     from the `Content-Disposition` response header, or from the URL if the header\n     is not provided.\n  2. The server handling the request has been modified by an attacker and\n     instead of the expected response the URL returns a redirect to another\n     URL, e.g., `attacker.example.org/.bash_profile`, whose response does\n     not provide  a `Content-Disposition` header (i.e., the base for the\n     generated filename becomes `.bash_profile` instead of `file.txt`).\n  3. Your current directory doesn’t already contain `.bash_profile`\n     (i.e., no unique suffix is added to the generated filename).\n  4. You don’t notice the potentially unexpected output filename\n     as reported by httpie in the console output\n     (e.g., `Downloading 100.00 B to \".bash_profile\"`).\n\n  Reported by Raul Onitza and Giulio Comi.\n\n## [1.0.2](https://github.com/httpie/cli/compare/1.0.1...1.0.2) (2018-11-14)\n\n- Fixed tests for installation with pyOpenSSL.\n\n## [1.0.1](https://github.com/httpie/cli/compare/1.0.0...1.0.1) (2018-11-14)\n\n- Removed external URL calls from tests.\n\n## [1.0.0](https://github.com/httpie/cli/compare/0.9.9...1.0.0) (2018-11-02)\n\n- Added `--style=auto` which follows the terminal ANSI color styles.\n- Added support for selecting TLS 1.3 via `--ssl=tls1.3`\n  (available once implemented in upstream libraries).\n- Added `true`/`false` as valid values for `--verify`\n  (in addition to `yes`/`no`) and the boolean value is case-insensitive.\n- Changed the default `--style` from `solarized` to `auto` (on Windows it stays `fruity`).\n- Fixed default headers being incorrectly case-sensitive.\n- Removed Python 2.6 support.\n\n## [0.9.9](https://github.com/httpie/cli/compare/0.9.8...0.9.9) (2016-12-08)\n\n- Fixed README.\n\n## [0.9.8](https://github.com/httpie/cli/compare/0.9.6...0.9.8) (2016-12-08)\n\n- Extended auth plugin API.\n- Added exit status code `7` for plugin errors.\n- Added support for `curses`-less Python installations.\n- Fixed `REQUEST_ITEM` arg incorrectly being reported as required.\n- Improved `CTRL-C` interrupt handling.\n- Added the standard exit status code `130` for keyboard interrupts.\n\n## [0.9.6](https://github.com/httpie/cli/compare/0.9.4...0.9.6) (2016-08-13)\n\n- Added Python 3 as a dependency for Homebrew installations\n  to ensure some of the newer HTTP features work out of the box\n  for macOS users (starting with HTTPie 0.9.4.).\n- Added the ability to unset a request header with `Header:`, and send an\n  empty value with `Header;`.\n- Added `--default-scheme <URL_SCHEME>` to enable things like\n  `$ alias https='http --default-scheme=https`.\n- Added `-I` as a shortcut for `--ignore-stdin`.\n- Added fish shell completion (located in `extras/httpie-completion.fish`\n  in the GitHub repo).\n- Updated `requests` to 2.10.0 so that SOCKS support can be added via\n  `pip install requests[socks]`.\n- Changed the default JSON `Accept` header from `application/json`\n  to `application/json, */*`.\n- Changed the pre-processing of request HTTP headers so that any leading\n  and trailing whitespace is removed.\n\n## [0.9.4](https://github.com/httpie/cli/compare/0.9.3...0.9.4) (2016-07-01)\n\n- Added `Content-Type` of files uploaded in `multipart/form-data` requests\n- Added `--ssl=<PROTOCOL>` to specify the desired SSL/TLS protocol version\n  to use for HTTPS requests.\n- Added JSON detection with `--json, -j` to work around incorrect\n  `Content-Type`\n- Added `--all` to show intermediate responses such as redirects (with `--follow`)\n- Added `--history-print, -P WHAT` to specify formatting of intermediate responses\n- Added `--max-redirects=N` (default 30)\n- Added `-A` as short name for `--auth-type`\n- Added `-F` as short name for `--follow`\n- Removed the `implicit_content_type` config option\n  (use `\"default_options\": [\"--form\"]` instead)\n- Redirected `stdout` doesn't trigger an error anymore when `--output FILE`\n  is set\n- Changed the default `--style` back to `solarized` for better support\n  of light and dark terminals\n- Improved `--debug` output\n- Fixed `--session` when used with `--download`\n- Fixed `--download` to trim too long filenames before saving the file\n- Fixed the handling of `Content-Type` with multiple `+subtype` parts\n- Removed the XML formatter as the implementation suffered from multiple issues\n\n## [0.9.3](https://github.com/httpie/cli/compare/0.9.2...0.9.3) (2016-01-01)\n\n- Changed the default color `--style` from `solarized` to `monokai`\n- Added basic Bash autocomplete support (need to be installed manually)\n- Added request details to connection error messages\n- Fixed `'requests.packages.urllib3' has no attribute 'disable_warnings'`\n  errors that occurred in some installations\n- Fixed colors and formatting on Windows\n- Fixed `--auth` prompt on Windows\n\n## [0.9.2](https://github.com/httpie/cli/compare/0.9.1...0.9.2) (2015-02-24)\n\n- Fixed compatibility with Requests 2.5.1\n- Changed the default JSON `Content-Type` to `application/json` as UTF-8\n  is the default JSON encoding\n\n## [0.9.1](https://github.com/httpie/cli/compare/0.9.0...0.9.1) (2015-02-07)\n\n- Added support for Requests transport adapter plugins\n  (see [httpie-unixsocket](https://github.com/httpie/httpie-unixsocket)\n  and [httpie-http2](https://github.com/httpie/httpie-http2))\n\n## [0.9.0](https://github.com/httpie/cli/compare/0.8.0...0.9.0) (2015-01-31)\n\n- Added `--cert` and `--cert-key` parameters to specify a client side\n  certificate and private key for SSL\n- Improved unicode support\n- Improved terminal color depth detection via `curses`\n- To make it easier to deal with Windows paths in request items, `\\`\n  now only escapes special characters (the ones that are used as key-value\n  separators by HTTPie)\n- Switched from `unittest` to `pytest`\n- Added Python `wheel` support\n- Various test suite improvements\n- Added `CONTRIBUTING`\n- Fixed `User-Agent` overwriting when used within a session\n- Fixed handling of empty passwords in URL credentials\n- Fixed multiple file uploads with the same form field name\n- Fixed `--output=/dev/null` on Linux\n- Miscellaneous bugfixes\n\n## [0.8.0](https://github.com/httpie/cli/compare/0.7.1...0.8.0) (2014-01-25)\n\n- Added `field=@file.txt` and `field:=@file.json` for embedding\n  the contents of text and JSON files into request data\n- Added curl-style shorthand for localhost\n- Fixed request `Host` header value output so that it doesn't contain\n  credentials, if included in the URL\n\n## [0.7.1](https://github.com/httpie/cli/compare/0.6.0...0.7.1) (2013-09-24)\n\n- Added `--ignore-stdin`\n- Added support for auth plugins\n- Improved `--help` output\n- Improved `Content-Disposition` parsing for `--download` mode\n- Update to Requests 2.0.0\n\n## [0.6.0](https://github.com/httpie/cli/compare/0.5.1...0.6.0) (2013-06-03)\n\n- XML data is now formatted\n- `--session` and `--session-read-only` now also accept paths to\n  session files (eg. `http --session=/tmp/session.json example.org`)\n\n## [0.5.1](https://github.com/httpie/cli/compare/0.5.0...0.5.1) (2013-05-13)\n\n- `Content-*` and `If-*` request headers are not stored in sessions\n  anymore as they are request-specific\n\n## [0.5.0](https://github.com/httpie/cli/compare/0.4.1...0.5.0) (2013-04-27)\n\n- Added a download mode via `--download`\n- Fixes miscellaneous bugs\n\n## [0.4.1](https://github.com/httpie/cli/compare/0.4.0...0.4.1) (2013-02-26)\n\n- Fixed `setup.py`\n\n## [0.4.0](https://github.com/httpie/cli/compare/0.3.0...0.4.0) (2013-02-22)\n\n- Added Python 3.3 compatibility\n- Added Requests >= v1.0.4 compatibility\n- Added support for credentials in URL\n- Added `--no-option` for every `--option` to be config-friendly\n- Mutually exclusive arguments can be specified multiple times. The\n  last value is used\n\n## [0.3.0](https://github.com/httpie/cli/compare/0.2.7...0.3.0) (2012-09-21)\n\n- Allow output redirection on Windows\n- Added configuration file\n- Added persistent session support\n- Renamed `--allow-redirects` to `--follow`\n- Improved the usability of `http --help`\n- Fixed installation on Windows with Python 3\n- Fixed colorized output on Windows with Python 3\n- CRLF HTTP header field separation in the output\n- Added exit status code `2` for timed-out requests\n- Added the option to separate colorizing and formatting\n  (`--pretty=all`, `--pretty=colors` and `--pretty=format`)\n  `--ugly` has bee removed in favor of `--pretty=none`\n\n## [0.2.7](https://github.com/httpie/cli/compare/0.2.5...0.2.7) (2012-08-07)\n\n- Added compatibility with Requests 0.13.6\n- Added streamed terminal output. `--stream, -S` can be used to enable\n  streaming also with `--pretty` and to ensure a more frequent output\n  flushing\n- Added support for efficient large file downloads\n- Sort headers by name (unless `--pretty=none`)\n- Response body is fetched only when needed (e.g., not with `--headers`)\n- Improved content type matching\n- Updated Solarized color scheme\n- Windows: Added `--output FILE` to store output into a file\n  (piping results in corrupted data on Windows)\n- Proper handling of binary requests and responses\n- Fixed printing of `multipart/form-data` requests\n- Renamed `--traceback` to `--debug`\n\n## [0.2.6](https://github.com/httpie/cli/compare/0.2.5...0.2.6) (2012-07-26)\n\n- The short option for `--headers` is now `-h` (`-t` has been\n  removed, for usage use `--help`)\n- Form data and URL parameters can have multiple fields with the same name\n  (e.g.,`http -f url a=1 a=2`)\n- Added `--check-status` to exit with an error on HTTP 3xx, 4xx and\n  5xx (3, 4, and 5, respectively)\n- If the output is piped to another program or redirected to a file,\n  the default behaviour is to only print the response body\n  (It can still be overwritten via the `--print` flag.)\n- Improved highlighting of HTTP headers\n- Added query string parameters (`param==value`)\n- Added support for terminal colors under Windows\n\n## [0.2.5](https://github.com/httpie/cli/compare/0.2.2...0.2.5) (2012-07-17)\n\n- Unicode characters in prettified JSON now don't get escaped for\n  improved readability\n- --auth now prompts for a password if only a username provided\n- Added support for request payloads from a file path with automatic\n  `Content-Type` (`http URL @/path`)\n- Fixed missing query string when displaying the request headers via\n  `--verbose`\n- Fixed Content-Type for requests with no data\n\n## [0.2.2](https://github.com/httpie/cli/compare/0.2.1...0.2.2) (2012-06-24)\n\n- The `METHOD` positional argument can now be omitted (defaults to\n  `GET`, or to `POST` with data)\n- Fixed --verbose --form\n- Added support for Tox\n\n## [0.2.1](https://github.com/httpie/cli/compare/0.2.0...0.2.1) (2012-06-13)\n\n- Added compatibility with `requests-0.12.1`\n- Dropped custom JSON and HTTP lexers in favor of the ones newly included\n  in `pygments-1.5`\n\n## [0.2.0](https://github.com/httpie/cli/compare/0.1.6...0.2.0) (2012-04-25)\n\n- Added Python 3 support\n- Added the ability to print the HTTP request as well as the response\n  (see `--print` and `--verbose`)\n- Added support for Digest authentication\n- Added file upload support\n  (`http -f POST file_field_name@/path/to/file`)\n- Improved syntax highlighting for JSON\n- Added support for field name escaping\n- Many bug fixes\n\n## [0.1.6](https://github.com/httpie/cli/compare/0.1.5...0.1.6) (2012-03-04)\n\n- Fixed `setup.py`\n\n## [0.1.5](https://github.com/httpie/cli/compare/0.1.4...0.1.5) (2012-03-04)\n\n- Many improvements and bug fixes\n\n## [0.1.4](https://github.com/httpie/cli/compare/b966efa...0.1.4) (2012-02-28)\n\n- Many improvements and bug fixes\n\n## [0.1.0](https://github.com/httpie/cli/commit/b966efa) (2012-02-25)\n\n- Initial public release\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at jakub@roztocil.co. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),\nversion 1.4, available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>\n\nFor answers to common questions about this code of conduct, see\n<https://www.contributor-covenant.org/faq>\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to HTTPie\n\nBug reports and code and documentation patches are welcome. You can\nhelp this project also by using the development version of HTTPie\nand by reporting any bugs you might encounter.\n\n## 1. Reporting bugs\n\n**It's important that you provide the full command argument list\nas well as the output of the failing command.**\n\nUse the `--debug` flag and copy&paste both the command and its output\nto your bug report, e.g.:\n\n```bash\n$ http --debug <COMPLETE ARGUMENT LIST THAT TRIGGERS THE ERROR>\n<COMPLETE OUTPUT>\n```\n\n## 2. Contributing Code and Docs\n\nBefore working on a new feature or a bug, please browse [existing issues](https://github.com/httpie/cli/issues)\nto see whether it has previously been discussed.\n\nIf your change alters HTTPie’s behaviour or interface, it's a good idea to\ndiscuss it before you start working on it.\n\nIf you are fixing an issue, the first step should be to create a test case that\nreproduces the incorrect behaviour. That will also help you to build an\nunderstanding of the issue at hand.\n\n**Pull requests introducing code changes without tests\nwill generally not get merged. The same goes for PRs changing HTTPie’s\nbehaviour and not providing documentation.**\n\nConversely, PRs consisting of documentation improvements or tests\nfor existing-yet-previously-untested behavior will very likely be merged.\nTherefore, docs and tests improvements are a great candidate for your first\ncontribution.\n\nConsider also adding a [CHANGELOG](https://github.com/httpie/cli/blob/master/CHANGELOG.md) entry for your changes.\n\n### Development Environment\n\n#### Getting the code\n\nGo to <https://github.com/httpie/cli> and fork the project repository.\n\n```bash\n# Clone your fork\n$ git clone git@github.com:<YOU>/httpie.git\n\n# Enter the project directory\n$ cd httpie\n\n# Create a branch for your changes\n$ git checkout -b my_topical_branch\n```\n\n#### Setup\n\nThe [Makefile](https://github.com/httpie/cli/blob/master/Makefile) contains a bunch of tasks to get you started.\nYou can run `$ make` to see all the available tasks.\n\nTo get started, run the command below, which:\n\n- Creates an isolated Python virtual environment inside `./venv`\n  (via the standard library [venv](https://docs.python.org/3/library/venv.html) tool);\n- installs all dependencies and also installs HTTPie\n  (in editable mode so that the `http` command will point to your\n  working copy).\n- and runs tests (It is the same as running `make install test`).\n\n```bash\n$ make all\n```\n\n#### Python virtual environment\n\nActivate the Python virtual environment—created via the `make install`\ntask during [setup](#setup) for your active shell session using the following command:\n\n```bash\n$ source venv/bin/activate\n```\n\n(If you use `virtualenvwrapper`, you can also use `workon httpie` to\nactivate the environment — we have created a symlink for you. It’s a bit of\na hack but it works™.)\n\nYou should now see `(httpie)` next to your shell prompt, and\nthe `http` command should point to your development copy:\n\n```bash\n(httpie) ~/Code/httpie $ which http\n/Users/<user>/Code/httpie/venv/bin/http\n(httpie) ~/Code/httpie $ http --version\n2.0.0-dev\n```\n\n(Btw, you don’t need to activate the virtual environment if you just want\nrun some of the `make` tasks. You can also invoke the development\nversion of HTTPie directly with `./venv/bin/http` without having to activate\nthe environment first. The same goes for `./venv/bin/pytest`, etc.).\n\n### Making Changes\n\nPlease make sure your changes conform to [Style Guide for Python Code](https://python.org/dev/peps/pep-0008/) (PEP8)\nand that `make pycodestyle` passes.\n\n### Testing & CI\n\nPlease add tests for any new features and bug fixes.\n\nWhen you open a Pull Request, [GitHub Actions](https://github.com/httpie/cli/actions) will automatically run HTTPie’s [test suite](https://github.com/httpie/cli/tree/master/tests) against your code, so please make sure all checks pass.\n\n#### Running tests locally\n\nHTTPie uses the [pytest](https://pytest.org/) runner.\n\n```bash\n# Run tests on the current Python interpreter with coverage.\n$ make test\n\n# Run tests with coverage\n$ make test-cover\n\n# Test PEP8 compliance\n$ make codestyle\n\n# Run extended tests — for code as well as .md files syntax, packaging, etc.\n$ make test-all\n```\n\n#### Running specific tests\n\nAfter you have activated your virtual environment (see [setup](#setup)), you\ncan run specific tests from the terminal:\n\n```bash\n# Run specific tests on the current Python\n$ python -m pytest tests/test_uploads.py\n$ python -m pytest tests/test_uploads.py::TestMultipartFormDataFileUpload\n$ python -m pytest tests/test_uploads.py::TestMultipartFormDataFileUpload::test_upload_ok\n```\n\nSee [Makefile](https://github.com/httpie/cli/blob/master/Makefile) for additional development utilities.\n\n#### Running benchmarks\n\nIf you are trying to work on speeding up HTTPie and want to verify your results, you\ncan run the benchmark suite. The suite will compare the last commit of your branch\nwith the master branch of your repository (or a fresh checkout of HTTPie master, through\n`--fresh`) and report the results back.\n\n```bash\n$ python extras/profiling/run.py\n```\n\nThe benchmarks can also be run on the CI. Since it is a long process, it requires manual\noversight. Ping one of the maintainers to get a `benchmark` label on your branch.\n\n#### Windows\n\nIf you are on a Windows machine and not able to run `make`,\nfollow the next steps for a basic setup. As a prerequisite, you need to have\nPython 3.7+ installed.\n\nCreate a virtual environment and activate it:\n\n```powershell\nC:\\> python -m venv --prompt httpie venv\nC:\\> venv\\Scripts\\activate\n```\n\nInstall HTTPie in editable mode with all the dependencies:\n\n```powershell\nC:\\> python -m pip install --upgrade -e .[dev]\n```\n\nYou should now see `(httpie)` next to your shell prompt, and\nthe `http` command should point to your development copy:\n\n```powershell\n# In PowerShell:\n(httpie) PS C:\\Users\\<user>\\httpie> Get-Command http\nCommandType     Name                                               Version    Source\n-----------     ----                                               -------    ------\nApplication     http.exe                                           0.0.0.0    C:\\Users\\<user>\\httpie\\venv\\Scripts\\http.exe\n```\n\n```bash\n# In CMD:\n(httpie) C:\\Users\\<user>\\httpie> where http\nC:\\Users\\<user>\\httpie\\venv\\Scripts\\http.exe\nC:\\Users\\<user>\\AppData\\Local\\Programs\\Python\\Python38-32\\Scripts\\http.exe\n\n(httpie) C:\\Users\\<user>\\httpie> http --version\n2.3.0-dev\n```\n\nUse `pytest` to run tests locally with an active virtual environment:\n\n```bash\n# Run all tests\n$ python -m pytest\n```\n\n______________________________________________________________________\n\nFinally, feel free to add yourself to [AUTHORS](https://github.com/httpie/cli/blob/master/AUTHORS.md)!\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright © 2012-2022 Jakub Roztocil <jakub@roztocil.co>\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    1. Redistributions of source code must retain the above copyright notice,\n       this list of conditions and the following disclaimer.\n\n    2. Redistributions in binary form must reproduce the above copyright\n       notice, this list of conditions and the following disclaimer in the\n       documentation and/or other materials provided with the distribution.\n\n    3. Neither the name of the copyright holder nor the names of its contributors\n       may be used to endorse or promote products derived from this software\n       without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE\ninclude README.md\ninclude CHANGELOG.md\ninclude AUTHORS.md\ninclude docs/README.md\n\n# <https://github.com/httpie/cli/issues/182>\nrecursive-include tests/ *\n"
  },
  {
    "path": "Makefile",
    "content": "###############################################################################\n# See ./CONTRIBUTING.md\n###############################################################################\n\n.PHONY: build\n\nROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))\nVERSION=$(shell grep __version__ httpie/__init__.py)\nH1=\"\\n\\n\\033[0;32m\\#\\#\\# \"\nH1END=\" \\#\\#\\# \\033[0m\\n\"\n\n\n# Only used to create our venv.\nSYSTEM_PYTHON=python3\n\nVENV_ROOT=venv\nVENV_BIN=$(VENV_ROOT)/bin\nVENV_PIP=$(VENV_BIN)/pip3\nVENV_PYTHON=$(VENV_BIN)/python\n\n\nexport PATH := $(VENV_BIN):$(PATH)\n\n\n\ndefault: list-tasks\n\n\n###############################################################################\n# Default task to get a list of tasks when `make' is run without args.\n# <https://stackoverflow.com/questions/4219255>\n###############################################################################\n\nlist-tasks:\n\t@echo Available tasks:\n\t@echo ----------------\n\t@$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ \"^[#.]\") {print $$1}}' | sort | grep -E -v -e '^[^[:alnum:]]' -e '^$@$$'\n\t@echo\n\n\n###############################################################################\n# Installation\n###############################################################################\n\nall: uninstall-httpie install test\n\n\ninstall: venv install-reqs\n\n\ninstall-reqs:\n\t@echo $(H1)Updating package tools$(H1END)\n\t$(VENV_PIP) install --upgrade pip wheel build\n\n\t@echo $(H1)Installing dev requirements$(H1END)\n\t$(VENV_PIP) install --upgrade '.[dev]' '.[test]'\n\n\t@echo $(H1)Installing HTTPie$(H1END)\n\t$(VENV_PIP) install --upgrade --editable .\n\n\t@echo\n\n\nclean:\n\t@echo $(H1)Cleaning up$(H1END)\n\trm -rf $(VENV_ROOT)\n\t# Remove symlink for virtualenvwrapper, if we’ve created one.\n\t[ -n \"$(WORKON_HOME)\" -a -L \"$(WORKON_HOME)/httpie\" -a -f \"$(WORKON_HOME)/httpie\" ] && rm $(WORKON_HOME)/httpie || true\n\trm -rf *.egg dist build .coverage .cache .pytest_cache httpie.egg-info\n\tfind . -name '__pycache__' -delete -o -name '*.pyc' -delete\n\t@echo\n\n\nvenv:\n\t@echo $(H1)Creating a Python environment $(VENV_ROOT) $(H1END)\n\n\t$(SYSTEM_PYTHON) -m venv --prompt httpie $(VENV_ROOT)\n\n\t@echo\n\t@echo done.\n\t@echo\n\t@echo To active it manually, run:\n\t@echo\n\t@echo \"    source $(VENV_BIN)/activate\"\n\t@echo\n\t@echo '(learn more: https://docs.python.org/3/library/venv.html)'\n\t@echo\n\t@if [ -n \"$(WORKON_HOME)\" ]; then \\\n\t\techo $(ROOT_DIR) >  $(VENV_ROOT)/.project; \\\n\t\tif [ ! -d $(WORKON_HOME)/httpie -a ! -L $(WORKON_HOME)/httpie ]; then \\\n\t\t\tln -s $(ROOT_DIR)/$(VENV_ROOT) $(WORKON_HOME)/httpie ; \\\n\t\t\techo ''; \\\n\t\t\techo 'Since you use virtualenvwrapper, we created a symlink'; \\\n\t\t\techo 'so you can also use \"workon httpie\" to activate the venv.'; \\\n\t\t\techo ''; \\\n\t\tfi; \\\n\tfi\n\n\n###############################################################################\n# Testing\n###############################################################################\n\n\ntest:\n\t@echo $(H1)Running tests$(HEADER_EXTRA)$(H1END)\n\t$(VENV_BIN)/python -m pytest $(COV)\n\t@echo\n\n\ntest-cover: COV=--cov=httpie --cov=tests\ntest-cover: HEADER_EXTRA=' (with coverage)'\ntest-cover: test\n\n\n# test-all is meant to test everything — even this Makefile\ntest-all: clean install test test-dist codestyle\n\t@echo\n\n\ntest-dist: test-sdist test-bdist-wheel\n\t@echo\n\n\ntest-sdist: clean venv\n\t@echo $(H1)Testing sdist build an installation$(H1END)\n\t$(VENV_PIP) install build\n\t$(VENV_PYTHON) -m build --sdist\n\t$(VENV_PIP) install --force-reinstall --upgrade dist/*.gz\n\t$(VENV_BIN)/http --version\n\t@echo\n\n\ntest-bdist-wheel: clean venv\n\t@echo $(H1)Testing wheel build an installation$(H1END)\n\t$(VENV_PIP) install build\n\t$(VENV_PYTHON) -m build --wheel\n\t$(VENV_PIP) install --force-reinstall --upgrade dist/*.whl\n\t$(VENV_BIN)/http --version\n\t@echo\n\n\ntwine-check:\n\ttwine check dist/*\n\n\n# Kept for convenience, \"make codestyle\" is preferred though\npycodestyle: codestyle\n\n\ncodestyle:\n\t@echo $(H1)Running flake8$(H1END)\n\t@[ -f $(VENV_BIN)/flake8 ] || $(VENV_PIP) install --upgrade --editable '.[dev]'\n\t$(VENV_BIN)/flake8 httpie/ tests/ extras/profiling/ docs/packaging/brew/ *.py\n\t@echo\n\n\ncodecov-upload:\n\t@echo $(H1)Running codecov$(H1END)\n\t@[ -f $(VENV_BIN)/codecov ] || $(VENV_PIP) install codecov\n\t# $(VENV_BIN)/codecov --required\n\t$(VENV_BIN)/codecov\n\t@echo\n\n\ndoc-check:\n\t@echo $(H1)Running documentations checks$(H1END)\n\tmdl --git-recurse --style docs/markdownlint.rb .\n\n\n###############################################################################\n# Publishing to PyPi\n###############################################################################\n\n\nbuild:\n\trm -rf build/ dist/\n\tmv httpie/internal/__build_channel__.py httpie/internal/__build_channel__.py.original\n\techo 'BUILD_CHANNEL = \"pip\"' > httpie/internal/__build_channel__.py\n\t$(VENV_PYTHON) -m build --sdist --wheel --outdir dist/\n\tmv httpie/internal/__build_channel__.py.original httpie/internal/__build_channel__.py\n\n\npublish: test-all publish-no-test\n\n\npublish-no-test:\n\t@echo $(H1)Testing wheel build an installation$(H1END)\n\t@echo \"$(VERSION)\"\n\t@echo \"$(VERSION)\" | grep -q \"dev\" && echo '!!!Not publishing dev version!!!' && exit 1 || echo ok\n\tmake build\n\tmake twine-check\n\t$(VENV_BIN)/twine upload --repository=httpie dist/*\n\t@echo\n\n\n\n###############################################################################\n# Uninstalling\n###############################################################################\n\nuninstall-httpie:\n\t@echo $(H1)Uninstalling httpie$(H1END)\n\t- $(VENV_PIP) uninstall --yes httpie &2>/dev/null\n\n\t@echo \"Verifying…\"\n\tcd .. && ! $(VENV_PYTHON) -m httpie --version &2>/dev/null\n\n\t@echo \"Done\"\n\t@echo\n\n\n###############################################################################\n# Homebrew\n###############################################################################\n\nbrew-deps:\n\tdocs/packaging/brew/brew-deps.py\n\nbrew-test:\n\t@echo $(H1)Uninstalling httpie$(H1END)\n\t- brew uninstall httpie\n\n\t@echo $(H1)Building from source…$(H1END)\n\t- brew install --HEAD --build-from-source ./docs/packaging/brew/httpie.rb\n\n\t@echo $(H1)Verifying…$(H1END)\n\thttp --version\n\thttps --version\n\n\t@echo $(H1)Auditing…$(H1END)\n\tbrew audit --strict httpie\n\n###############################################################################\n# Generated content\n###############################################################################\n\ncontent: man installation-docs\n\nman: install\n\t@echo $(H1)Regenerate man pages$(H1END)\n\t$(VENV_PYTHON) extras/scripts/generate_man_pages.py\n\ninstallation-docs:\n\t@echo $(H1)Updating installation instructions in the docs$(H1END)\n\t$(VENV_PYTHON) docs/installation/generate.py\n"
  },
  {
    "path": "README.md",
    "content": "<h2 align=\"center\">\n    <a href=\"https://httpie.io\" target=\"blank_\">\n        <img height=\"100\" alt=\"HTTPie\" src=\"https://raw.githubusercontent.com/httpie/cli/master/docs/httpie-logo.svg\" />\n    </a>\n    <br>\n    HTTPie CLI: human-friendly HTTP client for the API era\n</h2>\n\n<div align=\"center\">\n\n[![HTTPie for Desktop](https://img.shields.io/static/v1?label=HTTPie&message=Desktop&color=4B78E6)](https://httpie.io/product)\n[![](https://img.shields.io/static/v1?label=HTTPie&message=Web%20%26%20Mobile&color=73DC8C)](https://httpie.io/app)\n[![](https://img.shields.io/static/v1?label=HTTPie&message=CLI&color=FA9BFA)](https://httpie.io/cli)\n[![Twitter](https://img.shields.io/twitter/follow/httpie?style=flat&color=%234B78E6&logoColor=%234B78E6)](https://twitter.com/httpie)\n[![Chat](https://img.shields.io/discord/725351238698270761?style=flat&label=Chat%20on%20Discord&color=%23FA9BFA)](https://httpie.io/discord)\n\n</div>\n\n\n<div align=\"center\">\n\n[![Docs](https://img.shields.io/badge/stable%20docs-httpie.io%2Fdocs%2Fcli-brightgreen?style=flat&color=%2373DC8C&label=Docs)](https://httpie.org/docs/cli)\n[![Latest version](https://img.shields.io/pypi/v/httpie.svg?style=flat&label=Latest&color=%234B78E6&logo=&logoColor=white)](https://pypi.python.org/pypi/httpie)\n[![Build](https://img.shields.io/github/actions/workflow/status/httpie/cli/tests.yml?branch=master&color=%23FA9BFA&label=Build)](https://github.com/httpie/cli/actions)\n[![Coverage](https://img.shields.io/codecov/c/github/httpie/cli?style=flat&label=Coverage&color=%2373DC8C)](https://codecov.io/gh/httpie/cli)\n[![PyPi downloads](https://img.shields.io/pepy/dt/httpie?style=flat&label=Downloads%20from%20PyPi%20only&color=4B78E6)](https://www.pepy.tech/projects/httpie)\n\n</div>\n\nHTTPie (pronounced _aitch-tee-tee-pie_) is a command-line HTTP client.\nIts goal is to make CLI interaction with web services as human-friendly as possible.\nHTTPie is designed for testing, debugging, and generally interacting with APIs & HTTP servers.\nThe `http` & `https` commands allow for creating and sending arbitrary HTTP requests.\nThey use simple and natural syntax and provide formatted and colorized output.\n\n<div align=\"center\">\n\n<img src=\"https://raw.githubusercontent.com/httpie/cli/master/docs/httpie-animation.gif\" alt=\"HTTPie in action\" width=\"100%\"/>\n\n\n</div>\n\n\n\n\n## We lost 54k GitHub stars\n\nPlease note we recently accidentally made this repo private for a moment, and GitHub deleted our community that took a decade to build. Read the full story here: https://httpie.io/blog/stardust\n\n![](docs/stardust.png)\n\n\n## Getting started\n\n- [Installation instructions →](https://httpie.io/docs#installation)\n- [Full documentation →](https://httpie.io/docs)\n\n## Features\n\n- Expressive and intuitive syntax\n- Formatted and colorized terminal output\n- Built-in JSON support\n- Forms and file uploads\n- HTTPS, proxies, and authentication\n- Arbitrary request data\n- Custom headers\n- Persistent sessions\n- `wget`-like downloads\n\n[See all features →](https://httpie.io/docs)\n\n## Examples\n\nHello World:\n\n```bash\nhttps httpie.io/hello\n```\n\nCustom [HTTP method](https://httpie.io/docs#http-method), [HTTP headers](https://httpie.io/docs#http-headers) and [JSON](https://httpie.io/docs#json) data:\n\n```bash\nhttp PUT pie.dev/put X-API-Token:123 name=John\n```\n\nBuild and print a request without sending it using [offline mode](https://httpie.io/docs/cli/offline-mode):\n\n```bash\nhttp --offline pie.dev/post hello=offline\n```\n\nUse [GitHub API](https://developer.github.com/v3/issues/comments/#create-a-comment) to post a comment on an [Issue](https://github.com/httpie/cli/issues/83) with [authentication](https://httpie.io/docs#authentication):\n\n```bash\nhttp -a USERNAME POST https://api.github.com/repos/httpie/cli/issues/83/comments body='HTTPie is awesome! :heart:'\n```\n\n[See more examples →](https://httpie.io/docs#examples)\n\n## Community & support\n\n- Visit the [HTTPie website](https://httpie.io) for full documentation and useful links.\n- Join our [Discord server](https://httpie.io/discord) is to ask questions, discuss features, and for general API chat.\n- Tweet at [@httpie](https://twitter.com/httpie) on Twitter.\n- Use [StackOverflow](https://stackoverflow.com/questions/tagged/httpie) to ask questions and include a `httpie` tag.\n- Create [GitHub Issues](https://github.com/httpie/cli/issues) for bug reports and feature requests.\n- Subscribe to the [HTTPie newsletter](https://httpie.io) for occasional updates.\n\n## Contributing\n\nHave a look through existing [Issues](https://github.com/httpie/cli/issues) and [Pull Requests](https://github.com/httpie/cli/pulls) that you could help with. If you'd like to request a feature or report a bug, please [create a GitHub Issue](https://github.com/httpie/cli/issues) using one of the templates provided.\n\n[See contribution guide →](https://github.com/httpie/cli/blob/master/CONTRIBUTING.md)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security policy\n\n## Reporting a vulnerability\n\nWhen you identify a vulnerability in HTTPie, please report it privately using one of the following channels:\n\n- Email to [`security@httpie.io`](mailto:security@httpie.io)\n- Report on [huntr.dev](https://huntr.dev/)\n\nIn addition to the description of the vulnerability, include the following information:\n\n- A short reproducer to verify it (it can be a small HTTP server, shell script, docker image, etc.)\n- Your deemed severity level of the vulnerability (`LOW`/`MEDIUM`/`HIGH`/`CRITICAL`)\n- [CWE](https://cwe.mitre.org/) ID, if available.\n"
  },
  {
    "path": "docs/README.md",
    "content": "<div class='hidden-website'>\n\n# HTTPie documentation\n\n</div>\n\nHTTPie (pronounced _aitch-tee-tee-pie_) is a command-line HTTP client.\nIts goal is to make CLI interaction with web services as human-friendly as possible.\nHTTPie is designed for testing, debugging, and generally interacting with APIs & HTTP servers.\nThe `http` & `https` commands allow for creating and sending arbitrary HTTP requests.\nThey use simple and natural syntax and provide formatted and colorized output.\n\n<div class='hidden-website'>\n\n## About this document\n\nThis documentation is best viewed at [httpie.io/docs](https://httpie.org/docs).\n\nYou can select your corresponding HTTPie version as well as run examples directly from the browser using a [termible.io](https://termible.io?utm_source=httpie-readme) embedded terminal.\n\nIf you are reading this on GitHub, then this text covers the current *development* version.\nYou are invited to submit fixes and improvements to the docs by editing [this file](https://github.com/httpie/cli/blob/master/docs/README.md).\n\n</div>\n\n## Main features\n\n- Expressive and intuitive syntax\n- Formatted and colorized terminal output\n- Built-in JSON support\n- Forms and file uploads\n- HTTPS, proxies, and authentication\n- Arbitrary request data\n- Custom headers\n- Persistent sessions\n- Wget-like downloads\n- Linux, macOS, Windows, and FreeBSD support\n- Plugins\n- Documentation\n- Test coverage\n\n## Installation\n\n<div data-installation-instructions>\n\n<!--\nTHE INSTALLATION SECTION IS GENERATED\n\nDo not edit here, but in docs/installation/.\n\n-->\n\n- [Universal](#universal)\n- [macOS](#macos)\n- [Windows](#windows)\n- [Linux](#linux)\n- [FreeBSD](#freebsd)\n\n### Universal\n\n#### PyPI\n\nPlease make sure you have Python 3.7 or newer (`python --version`).\n\n```bash\n# Install httpie\n$ python -m pip install --upgrade pip wheel\n$ python -m pip install httpie\n```\n\n```bash\n# Upgrade httpie\n$ python -m pip install --upgrade pip wheel\n$ python -m pip install --upgrade httpie\n```\n\n### macOS\n\n#### Homebrew\n\nTo install [Homebrew](https://brew.sh/), see [its installation](https://docs.brew.sh/Installation).\n\n```bash\n# Install httpie\n$ brew update\n$ brew install httpie\n```\n\n```bash\n# Upgrade httpie\n$ brew update\n$ brew upgrade httpie\n```\n\n#### MacPorts\n\nTo install [MacPorts](https://www.macports.org/), see [its installation](https://www.macports.org/install.php).\n\n```bash\n# Install httpie\n$ port selfupdate\n$ port install httpie\n```\n\n```bash\n# Upgrade httpie\n$ port selfupdate\n$ port upgrade httpie\n```\n\n### Windows\n\n#### Chocolatey\n\nTo install [Chocolatey](https://chocolatey.org/), see [its installation](https://chocolatey.org/install).\n\n```bash\n# Install httpie\n$ choco install httpie\n```\n\n```bash\n# Upgrade httpie\n$ choco upgrade httpie\n```\n\n### Linux\n\n#### Debian and Ubuntu\n\nAlso works for other Debian-derived distributions like MX Linux, Linux Mint, deepin, Pop!_OS, KDE neon, Zorin OS, elementary OS, Kubuntu, Devuan, Linux Lite, Peppermint OS, Lubuntu, antiX, Xubuntu, etc.\n\n```bash\n# Install httpie\n$ curl -SsL https://packages.httpie.io/deb/KEY.gpg | sudo gpg --dearmor -o /usr/share/keyrings/httpie.gpg\n$ echo \"deb [arch=amd64 signed-by=/usr/share/keyrings/httpie.gpg] https://packages.httpie.io/deb ./\" | sudo tee /etc/apt/sources.list.d/httpie.list > /dev/null\n$ sudo apt update\n$ sudo apt install httpie\n```\n\n```bash\n# Upgrade httpie\n$ sudo apt update && sudo apt upgrade httpie\n```\n\n#### Fedora\n\n```bash\n# Install httpie\n$ dnf install httpie\n```\n\n```bash\n# Upgrade httpie\n$ dnf upgrade httpie\n```\n\n#### CentOS and RHEL\n\nAlso works for other RHEL-derived distributions like ClearOS, Oracle Linux, etc.\n\n```bash\n# Install httpie\n$ yum install epel-release\n$ yum install httpie\n```\n\n```bash\n# Upgrade httpie\n$ yum upgrade httpie\n```\n\n#### Single binary executables\n\nGet the standalone HTTPie Linux executables when you don't want to go through the full installation process.\n\n```bash\n# Install httpie\n$ https --download packages.httpie.io/binaries/linux/http-latest -o http\n$ ln -ls ./http ./https\n$ chmod +x ./http ./https\n```\n\n```bash\n# Upgrade httpie\n$ https --download packages.httpie.io/binaries/linux/http-latest -o http\n```\n\n#### Snapcraft (Linux)\n\nTo install [Snapcraft](https://snapcraft.io/), see [its installation](https://snapcraft.io/docs/installing-snapd).\n\n```bash\n# Install httpie\n$ snap install httpie\n```\n\n```bash\n# Upgrade httpie\n$ snap refresh httpie\n```\n\n#### Linuxbrew\n\nTo install [Linuxbrew](https://docs.brew.sh/Homebrew-on-Linux), see [its installation](https://docs.brew.sh/Homebrew-on-Linux#install).\n\n```bash\n# Install httpie\n$ brew update\n$ brew install httpie\n```\n\n```bash\n# Upgrade httpie\n$ brew update\n$ brew upgrade httpie\n```\n\n#### Arch Linux\n\nAlso works for other Arch-derived distributions like ArcoLinux, EndeavourOS, Artix Linux, etc.\n\n```bash\n# Install httpie\n$ pacman -Syu httpie\n```\n\n```bash\n# Upgrade httpie\n$ pacman -Syu\n```\n\n### FreeBSD\n\n#### FreshPorts\n\n```bash\n# Install httpie\n$ pkg install www/py-httpie\n```\n\n```bash\n# Upgrade httpie\n$ pkg upgrade www/py-httpie\n```\n\n<!-- /GENERATED SECTION -->\n\n</div>\n\n### Unstable version\n\nIf you want to try out the latest version of HTTPie that hasn't been officially released yet, you can install the development or unstable version directly from the master branch on GitHub. However, keep in mind that the development version is a work in progress and may not be as reliable as the stable version.\n\nYou can use the following command to install the development version of HTTPie on Linux, macOS, Windows, or FreeBSD operating systems. With this command, the code present in the `master` branch is downloaded and installed using `pip`.\n\n```bash\n$ python -m pip install --upgrade https://github.com/httpie/cli/archive/master.tar.gz\n```\n\nThere are other ways to install the development version of HTTPie on macOS and Linux.\n\nYou can install it using Homebrew by running the following commands:\n\n```bash\n$ brew uninstall --force httpie\n$ brew install --HEAD httpie\n```\n\nYou can install it using Snapcraft by running the following commands:\n\n```bash\n$ snap remove httpie\n$ snap install httpie --edge\n```\n\nTo verify the installation, you can compare the [version identifier on GitHub](https://github.com/httpie/cli/blob/master/httpie/__init__.py#L6) with the one available on your machine. You can check the version of HTTPie on your machine by using the command `http --version`.\n\n```bash\n$ http --version\n# 3.X.X.dev0\n```\n\nNote that on your machine, the version name will have the `.dev0` suffix.\n\n## Usage\n\nHello World:\n\n```bash\n$ https httpie.io/hello\n```\n\nSynopsis:\n\n```bash\n$ http [flags] [METHOD] URL [ITEM [ITEM]]\n```\n\nSee also `http --help` (and for systems where man pages are available, you can use `man http`).\n\n### Examples\n\nCustom [HTTP method](#http-method), [HTTP headers](#http-headers) and [JSON](#json) data:\n\n```bash\n$ http PUT pie.dev/put X-API-Token:123 name=John\n```\n\nSubmitting [forms](#forms):\n\n```bash\n$ http -f POST pie.dev/post hello=World\n```\n\nSee the request that is being sent using one of the [output options](#output-options):\n\n```bash\n$ http -v pie.dev/get\n```\n\nBuild and print a request without sending it using [offline mode](#offline-mode):\n\n```bash\n$ http --offline pie.dev/post hello=offline\n```\n\nUse [GitHub API](https://developer.github.com/v3/issues/comments/#create-a-comment) to post a comment on an [issue](https://github.com/httpie/cli/issues/83) with [authentication](#authentication):\n\n```bash\n$ http -a USERNAME POST https://api.github.com/repos/httpie/cli/issues/83/comments body='HTTPie is awesome! :heart:'\n```\n\nUpload a file using [redirected input](#redirected-input):\n\n```bash\n$ http pie.dev/post < files/data.json\n```\n\nDownload a file and save it via [redirected output](#redirected-output):\n\n```bash\n$ http pie.dev/image/png > image.png\n```\n\nDownload a file `wget` style:\n\n```bash\n$ http --download pie.dev/image/png\n```\n\nUse named [sessions](#sessions) to make certain aspects of the communication persistent between requests to the same host:\n\n```bash\n$ http --session=logged-in -a username:password pie.dev/get API-Key:123\n```\n\n```bash\n$ http --session=logged-in pie.dev/headers\n```\n\nSet a custom `Host` header to work around missing DNS records:\n\n```bash\n$ http localhost:8000 Host:example.com\n```\n\n## HTTP method\n\nThe name of the HTTP method comes right before the URL argument:\n\n```bash\n$ http DELETE pie.dev/delete\n```\n\nWhich looks similar to the actual `Request-Line` that is sent:\n\n```http\nDELETE /delete HTTP/1.1\n```\n\nIn addition to the standard methods (`GET`, `POST`, `HEAD`, `PUT`, `PATCH`, `DELETE`, etc.), you can use custom method names, for example:\n\n```bash\n$ http AHOY pie.dev/post\n```\n\nThere are no restrictions regarding which request methods can include a body. You can send an empty `POST` request:\n\n```bash\n$ http POST pie.dev/post\n```\n\nYou can also make `GET` requests containing a body:\n\n```bash\n$ http GET pie.dev/get hello=world\n```\n\n### Optional `GET` and `POST`\n\nThe `METHOD` argument is optional, and when you don’t specify it, HTTPie defaults to:\n\n- `GET` for requests without body\n- `POST` for requests with body\n\nHere we don’t specify any request data, so both commands will send the same `GET` request:\n\n```bash\n$ http GET pie.dev/get\n```\n\n```bash\n$ http pie.dev/get\n```\n\nHere, on the other hand, we do have some data, so both commands will make the same `POST` request:\n\n```bash\n$ http POST pie.dev/post hello=world\n```\n\n```bash\n$ http pie.dev/post hello=world\n```\n\n## Request URL\n\nThe only information HTTPie needs to perform a request is a URL.\n\nThe default scheme is `http://` and can be omitted from the argument:\n\n```bash\n$ http example.org\n# → http://example.org\n```\n\nHTTPie also installs an `https` executable, where the default scheme is `https://`:\n\n```bash\n$ https example.org\n# → https://example.org\n```\n\nWhen you paste a URL into the terminal, you can even keep the `://` bit in the URL argument to quickly convert the URL into an HTTPie call just by adding a space after the protocol name.\n\n```bash\n$ https ://example.org\n# → https://example.org\n```\n\n```bash\n$ http ://example.org\n# → http://example.org\n```\n\n### Querystring parameters\n\nIf you find yourself manually constructing URLs with querystring parameters on the terminal, you may appreciate the `param==value` syntax for appending URL parameters.\n\nWith that, you don’t have to worry about escaping the `&` separators for your shell. Additionally, any special characters in the parameter name or value get automatically URL-escaped (as opposed to the parameters specified in the full URL, which HTTPie doesn’t modify).\n\n```bash\n$ http https://api.github.com/search/repositories q==httpie per_page==1\n```\n\n```http\nGET /search/repositories?q=httpie&per_page=1 HTTP/1.1\n```\n\nYou can even retrieve the `value` from a file by using the `param==@file` syntax. This would also effectively strip the newlines from the end. See [file based separators](#file-based-separators) for more examples.\n\n```bash\n$ http pie.dev/get text==@files/text.txt\n```\n\n### URL shortcuts for `localhost`\n\nAdditionally, curl-like shorthand for localhost is supported.\nThis means that, for example, `:3000` would expand to `http://localhost:3000`.\nIf the port is omitted, then port 80 is assumed.\n\n```bash\n$ http :/foo\n```\n\n```http\nGET /foo HTTP/1.1\nHost: localhost\n```\n\n```bash\n$ http :3000/bar\n```\n\n```http\nGET /bar HTTP/1.1\nHost: localhost:3000\n```\n\n```bash\n$ http :\n```\n\n```http\nGET / HTTP/1.1\nHost: localhost\n```\n\n### Other default schemes\n\nWhen HTTPie is invoked as `https` then the default scheme is `https://` (`$ https example.org` will make a request to `https://example.org`).\n\nYou can also use the `--default-scheme <URL_SCHEME>` option to create shortcuts for other protocols than HTTP (possibly supported via [plugins](https://pypi.org/search/?q=httpie)). Example for the [httpie-unixsocket](https://github.com/httpie/httpie-unixsocket) plugin:\n\n```bash\n# Before\n$ http http+unix://%2Fvar%2Frun%2Fdocker.sock/info\n```\n\n```bash\n# Create an alias\n$ alias http-unix='http --default-scheme=\"http+unix\"'\n```\n\n```bash\n# Now the scheme can be omitted\n$ http-unix %2Fvar%2Frun%2Fdocker.sock/info\n```\n\n### `--path-as-is`\n\nThe standard behavior of HTTP clients is to normalize the path portion of URLs by squashing dot segments as a typical filesystem would:\n\n```bash\n$ http -v example.org/./../../etc/password\n```\n\n```http\nGET /etc/password HTTP/1.1\n```\n\nThe `--path-as-is` option allows you to disable this behavior:\n\n```bash\n$ http --path-as-is -v example.org/./../../etc/password\n```\n\n```http\nGET /../../etc/password HTTP/1.1\n```\n\n## Request items\n\nThere are a few different *request item* types that provide a convenient\nmechanism for specifying HTTP headers, JSON and form data, files,\nand URL parameters. This is a very practical way of constructing\nHTTP requests from scratch on the CLI.\n\nEach *request item* is simply a key/value pair separated with the following\ncharacters: `:` (headers), `=` (data field, e.g., JSON, form), `:=` (raw data field)\n`==` (query parameters), `@` (file upload).\n\n```bash\n$ http PUT pie.dev/put \\\n    X-Date:today \\                     # Header\n    token==secret \\                    # Query parameter\n    name=John \\                        # Data field\n    age:=29                            # Raw JSON\n```\n\n|                                                    Item Type | Description                                                                                                                                                                                                            |\n|-------------------------------------------------------------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n|                                    HTTP Headers `Name:Value` | Arbitrary HTTP header, e.g. `X-API-Token:123`                                                                                                                                                                          |\n|                                 URL parameters `name==value` | Appends the given name/value pair as a querystring parameter to the URL. The `==` separator is used.                                                                                                                   |\n|                                    Data Fields `field=value` | Request data fields to be serialized as a JSON object (default), to be form-encoded (with `--form, -f`), or to be serialized as `multipart/form-data` (with `--multipart`)                                             |\n|                                Raw JSON fields `field:=json` | Useful when sending JSON and one or more fields need to be a `Boolean`, `Number`, nested `Object`, or an `Array`, e.g., `meals:='[\"ham\",\"spam\"]'` or `pies:='[1,2,3]'` (note the quotes)                                 |\n| File upload fields `field@/dir/file`, `field@file;type=mime` | Only available with `--form`, `-f` and `--multipart`. For example `screenshot@~/Pictures/img.png`, or `'cv@cv.txt;type=text/markdown'`. With `--form`, the presence of a file field results in a `--multipart` request |\n\nNote that the structured data fields aren’t the only way to specify request data:\n[raw request body](#raw-request-body) is a mechanism for passing arbitrary request data.\n\n### File based separators\n\nUsing file contents as values for specific fields is a very common use case, which can be achieved through adding the `@` suffix to\nthe operators above. For example, instead of using a static string as the value for some header, you can use `:@` operator\nto pass the desired value from a file.\n\n```bash\n$ http POST pie.dev/post \\\n    X-Data:@files/text.txt             # Read a header from a file\n    token==@files/text.txt             # Read a query parameter from a file\n    name=@files/text.txt               # Read a data field’s value from a file\n    bookmarks:=@files/data.json        # Embed a JSON object from a file\n```\n\n### Escaping rules\n\nYou can use `\\` to escape characters that shouldn’t be used as separators (or parts thereof). For instance, `foo\\==bar` will become a data key/value pair (`foo=` and `bar`) instead of a URL parameter.\n\nOften it is necessary to quote the values, e.g. `foo='bar baz'`.\n\nIf any of the field names or headers starts with a minus (e.g. `-fieldname`), you need to place all such items after the special token `--` to prevent confusion with `--arguments`:\n\n```bash\n$ http pie.dev/post -- -name-starting-with-dash=foo -Unusual-Header:bar\n```\n\n```http\nPOST /post HTTP/1.1\n-Unusual-Header: bar\nContent-Type: application/json\n\n{\n    \"-name-starting-with-dash\": \"foo\"\n}\n```\n\n## JSON\n\nJSON is the *lingua franca* of modern web services, and it is also the **implicit content type** HTTPie uses by default.\n\nSimple example:\n\n```bash\n$ http PUT pie.dev/put name=John email=john@example.org\n```\n\n```http\nPUT / HTTP/1.1\nAccept: application/json, */*;q=0.5\nAccept-Encoding: gzip, deflate\nContent-Type: application/json\nHost: pie.dev\n\n{\n    \"name\": \"John\",\n    \"email\": \"john@example.org\"\n}\n```\n\n### Default behavior\n\nIf your command includes some data [request items](#request-items), they are serialized as a JSON object by default. HTTPie also automatically sets the following headers, both of which can be overwritten:\n\n|         Header | Value                         |\n|---------------:|-------------------------------|\n| `Content-Type` | `application/json`            |\n|       `Accept` | `application/json, */*;q=0.5` |\n\n### Explicit JSON\n\nYou can use `--json, -j` to explicitly set `Accept` to `application/json` regardless of whether you are sending data (it’s a shortcut for setting the header via the usual header notation: `http url Accept:'application/json, */*;q=0.5'`).\nAdditionally, HTTPie will try to detect JSON responses even when the `Content-Type` is incorrectly `text/plain` or unknown.\n\n### Non-string JSON fields\n\nNon-string JSON fields use the `:=` separator, which allows you to embed arbitrary JSON data into the resulting JSON object.\nAdditionally, text and raw JSON files can also be embedded into fields using `=@` and `:=@`:\n\n```bash\n$ http PUT pie.dev/put \\\n    name=John \\                        # String (default)\n    age:=29 \\                          # Raw JSON — Number\n    married:=false \\                   # Raw JSON — Boolean\n    hobbies:='[\"http\", \"pies\"]' \\      # Raw JSON — Array\n    favorite:='{\"tool\": \"HTTPie\"}' \\   # Raw JSON — Object\n    bookmarks:=@files/data.json \\      # Embed JSON file\n    description=@files/text.txt        # Embed text file\n```\n\n```http\nPUT /person/1 HTTP/1.1\nAccept: application/json, */*;q=0.5\nContent-Type: application/json\nHost: pie.dev\n\n{\n    \"age\": 29,\n    \"hobbies\": [\n        \"http\",\n        \"pies\"\n    ],\n    \"description\": \"John is a nice guy who likes pies.\",\n    \"married\": false,\n    \"name\": \"John\",\n    \"favorite\": {\n        \"tool\": \"HTTPie\"\n    },\n    \"bookmarks\": {\n        \"HTTPie\": \"https://httpie.org\",\n    }\n}\n```\n\nThe `:=`/`:=@` syntax is JSON-specific. You can switch your request to `--form` or `--multipart`,\nand string, float, and number values will continue to be serialized (as string form values).\nOther JSON types, however, are not allowed with `--form` or `--multipart`.\n\n### Nested JSON\n\nIf your use case involves sending complex JSON objects as part of the request body,\nHTTPie can help you build them right from your terminal. You still use the existing\ndata field operators (`=`/`:=`) but instead of specifying a top-level field name (like `key=value`),\nyou specify a path declaration. This tells HTTPie where and how to put the given value inside an object:\n\n```bash\nhttp pie.dev/post \\\n  platform[name]=HTTPie \\\n  platform[about][mission]='Make APIs simple and intuitive' \\\n  platform[about][homepage]=httpie.io \\\n  platform[about][homepage]=httpie.io \\\n  platform[about][stars]:=54000 \\\n  platform[apps][]=Terminal \\\n  platform[apps][]=Desktop \\\n  platform[apps][]=Web \\\n  platform[apps][]=Mobile\n```\n\n```json\n{\n    \"platform\": {\n        \"name\": \"HTTPie\",\n        \"about\": {\n            \"mission\": \"Make APIs simple and intuitive\",\n            \"homepage\": \"httpie.io\",\n            \"stars\": 54000\n        },\n        \"apps\": [\n            \"Terminal\",\n            \"Desktop\",\n            \"Web\",\n            \"Mobile\"\n        ]\n    }\n}\n```\n\n#### Introduction\n\nLet’s start with a simple example, and build a simple search query:\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n  category=tools \\\n  search[type]=id \\\n  search[id]:=1\n```\n\nIn the example above, the `search[type]` is an instruction for creating an object called `search`, and setting the `type` field of it to the given value (`\"id\"`).\n\nAlso note that, just as the regular syntax, you can use the `:=` operator to directly pass raw JSON values (e.g., numbers in the case above).\n\n```json\n{\n    \"category\": \"tools\",\n    \"search\": {\n        \"id\": 1,\n        \"type\": \"id\"\n    }\n}\n```\n\nBuilding arrays is also possible, through `[]` suffix (an append operation). This tells HTTPie to create an array in the given path (if there is not one already), and append the given value to that array.\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n  category=tools \\\n  search[type]=keyword \\\n  search[keywords][]=APIs \\\n  search[keywords][]=CLI\n```\n\n```json\n{\n    \"category\": \"tools\",\n    \"search\": {\n        \"keywords\": [\n            \"APIs\",\n            \"CLI\"\n        ],\n        \"type\": \"keyword\"\n    }\n}\n```\n\nIf you want to explicitly specify the position of elements inside an array,\nyou can simply pass the desired index as the path:\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n  category=tools \\\n  search[type]=keyword \\\n  search[keywords][1]=APIs \\\n  search[keywords][0]=CLI\n```\n\n```json\n{\n    \"category\": \"tools\",\n    \"search\": {\n        \"keywords\": [\n            \"CLIs\",\n            \"API\"\n        ],\n        \"type\": \"keyword\"\n    }\n}\n```\n\nIf there are any missing indexes, HTTPie will nullify them in order to create a concrete object that can be sent:\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n  category=tools \\\n  search[type]=platforms \\\n  search[platforms][]=Terminal \\\n  search[platforms][1]=Desktop \\\n  search[platforms][3]=Mobile\n```\n\n```json\n{\n    \"category\": \"tools\",\n    \"search\": {\n        \"platforms\": [\n            \"Terminal\",\n            \"Desktop\",\n            null,\n            \"Mobile\"\n        ],\n        \"type\": \"platforms\"\n    }\n}\n```\n\nIt is also possible to embed raw JSON to a nested structure, for example:\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n  category=tools \\\n  search[type]=platforms \\\n  'search[platforms]:=[\"Terminal\", \"Desktop\"]' \\\n  search[platforms][]=Web \\\n  search[platforms][]=Mobile\n```\n\n```json\n{\n    \"category\": \"tools\",\n    \"search\": {\n        \"platforms\": [\n            \"Terminal\",\n            \"Desktop\",\n            \"Web\",\n            \"Mobile\"\n        ],\n        \"type\": \"platforms\"\n    }\n}\n```\n\nAnd just to demonstrate all of these features together, let’s create a very deeply nested JSON object:\n\n```bash\n$ http PUT pie.dev/put \\\n    shallow=value \\                                # Shallow key-value pair\n    object[key]=value \\                            # Nested key-value pair\n    array[]:=1 \\                                   # Array — first item\n    array[1]:=2 \\                                  # Array — second item\n    array[2]:=3 \\                                  # Array — append (third item)\n    very[nested][json][3][httpie][power][]=Amaze   # Nested object\n```\n\n#### Advanced usage\n\n##### Top level arrays\n\nIf you want to send an array instead of a regular object, you can simply\ndo that by omitting the starting key:\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n    []:=1 \\\n    []:=2 \\\n    []:=3\n```\n\n```json\n[\n    1,\n    2,\n    3\n]\n```\n\nYou can also apply the nesting to the items by referencing their index:\n\n```bash\nhttp --offline --print=B pie.dev/post \\\n    [0][type]=platform [0][name]=terminal \\\n    [1][type]=platform [1][name]=desktop\n```\n\n```json\n[\n    {\n        \"type\": \"platform\",\n        \"name\": \"terminal\"\n    },\n    {\n        \"type\": \"platform\",\n        \"name\": \"desktop\"\n    }\n]\n```\n\nSending scalar JSON types (a single `null`, `true`, `false`,  string or number) as the top-level object is impossible using the key/value syntax. But you can still pass it via [`--raw='<value>'`](#raw-request-body).\n\n##### Escaping behavior\n\nNested JSON syntax uses the same [escaping rules](#escaping-rules) as\nthe terminal. There are 3 special characters, and 1 special token that you can escape.\n\nIf you want to send a bracket as is, escape it with a backslash (`\\`):\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n  'foo\\[bar\\]:=1' \\\n  'baz[\\[]:=2' \\\n  'baz[\\]]:=3'\n```\n\n```json\n{\n    \"baz\": {\n        \"[\": 2,\n        \"]\": 3\n    },\n    \"foo[bar]\": 1\n}\n```\n\nIf you want to send the literal backslash character (`\\`), escape it with another backslash:\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n  'backslash[\\\\]:=1'\n```\n\n```json\n{\n    \"backslash\": {\n        \"\\\\\": 1\n    }\n}\n```\n\nA regular integer in a path (e.g `[10]`) means an array index; but if you want it to be treated as\na string, you can escape the whole number by using a backslash (`\\`) prefix.\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n  'object[\\1]=stringified' \\\n  'object[\\100]=same' \\\n  'array[1]=indexified'\n```\n\n```json\n{\n    \"array\": [\n        null,\n        \"indexified\"\n    ],\n    \"object\": {\n        \"1\": \"stringified\",\n        \"100\": \"same\"\n    }\n}\n```\n\n##### Guiding syntax errors\n\nIf you make a typo or forget to close a bracket, the errors will guide you to fix it. For example:\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n  'foo[bar]=OK' \\\n  'foo[baz][quux=FAIL'\n```\n\n```console\nHTTPie Syntax Error: Expecting ']'\nfoo[baz][quux\n             ^\n```\n\nYou can follow to given instruction (adding a `]`) and repair your expression.\n\n##### Type safety\n\nEach container path (e.g., `x[y][z]` in `x[y][z][1]`) has a certain type, which gets defined with\nthe first usage and can’t be changed after that. If you try to do a key-based access to an array or\nan index-based access to an object, HTTPie will error out:\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n  'array[]:=1' \\\n  'array[]:=2' \\\n  'array[key]:=3'\nHTTPie Type Error: Can't perform 'key' based access on 'array' which has a type of 'array' but this operation requires a type of 'object'.\narray[key]\n     ^^^^^\n```\n\nType Safety does not apply to value overrides, for example:\n\n```bash\n$ http --offline --print=B pie.dev/post \\\n  user[name]:=411     # Defined as an integer\n  user[name]=string   # Overridden with a string\n```\n\n```json\n{\n    \"user\": {\n        \"name\": \"string\"\n    }\n}\n```\n\n### Raw JSON\n\nFor very complex JSON structures, it may be more convenient to [pass it as raw request body](#raw-request-body), for example:\n\n```bash\n$ echo -n '{\"hello\": \"world\"}' | http POST pie.dev/post\n```\n\n```bash\n$ http POST pie.dev/post < files/data.json\n```\n\n## Forms\n\nSubmitting forms is very similar to sending [JSON](#json) requests.\nOften the only difference is in adding the `--form, -f` option, which ensures that data fields are serialized as key-value tuples separated by '&', with a '=' between the key and the value. In addition `Content-Type` is set to `application/x-www-form-urlencoded; charset=utf-8`.\nIt is possible to make form data the implicit content type instead of JSON via the [config](#config) file.\n\n### Regular forms\n\n```bash\n$ http --form POST pie.dev/post name='John Smith'\n```\n\n```http\nPOST /post HTTP/1.1\nContent-Type: application/x-www-form-urlencoded; charset=utf-8\n\nname=John+Smith\n```\n\n### File upload forms\n\nIf one or more file fields is present, the serialization and content type is `multipart/form-data`:\n\n```bash\n$ http -f POST pie.dev/post name='John Smith' cv@~/files/data.xml\n```\n\nThe request above is the same as if the following HTML form were submitted:\n\n```html\n<form enctype=\"multipart/form-data\" method=\"post\" action=\"http://example.com/jobs\">\n    <input type=\"text\" name=\"name\" />\n    <input type=\"file\" name=\"cv\" />\n</form>\n```\n\nPlease note that `@` is used to simulate a file upload form field, whereas `=@` just embeds the file content as a regular text field value.\n\nWhen uploading files, their content type is inferred from the file name. You can manually override the inferred content type:\n\n```bash\n$ http -f POST pie.dev/post name='John Smith' cv@'~/files/data.bin;type=application/pdf'\n```\n\nTo perform a `multipart/form-data` request even without any files, use `--multipart` instead of `--form`:\n\n```bash\n$ http --multipart --offline example.org hello=world\n```\n\n```http\nPOST / HTTP/1.1\nContent-Length: 129\nContent-Type: multipart/form-data; boundary=c31279ab254f40aeb06df32b433cbccb\nHost: example.org\n\n--c31279ab254f40aeb06df32b433cbccb\nContent-Disposition: form-data; name=\"hello\"\n\nworld\n--c31279ab254f40aeb06df32b433cbccb--\n```\n\nFile uploads are always streamed to avoid memory issues with large files.\n\nBy default, HTTPie uses a random unique string as the multipart boundary, but you can use `--boundary` to specify a custom string instead:\n\n```bash\n$ http --form --multipart --boundary=xoxo --offline example.org hello=world\n```\n\n```http\nPOST / HTTP/1.1\nContent-Length: 129\nContent-Type: multipart/form-data; boundary=xoxo\nHost: example.org\n\n--xoxo\nContent-Disposition: form-data; name=\"hello\"\n\nworld\n--xoxo--\n```\n\nIf you specify a custom `Content-Type` header without including the boundary bit, HTTPie will add the boundary value (explicitly specified or auto-generated) to the header automatically:\n\n```bash\n$ http --form --multipart --offline example.org hello=world Content-Type:multipart/letter\n```\n\n```http\nPOST / HTTP/1.1\nContent-Length: 129\nContent-Type: multipart/letter; boundary=c31279ab254f40aeb06df32b433cbccb\nHost: example.org\n\n--c31279ab254f40aeb06df32b433cbccb\nContent-Disposition: form-data; name=\"hello\"\n\nworld\n--c31279ab254f40aeb06df32b433cbccb--\n```\n\n## HTTP headers\n\nTo set custom headers you can use the `Header:Value` notation:\n\n```bash\n$ http pie.dev/headers User-Agent:Bacon/1.0 'Cookie:valued-visitor=yes;foo=bar' \\\n    X-Foo:Bar Referer:https://httpie.org/\n```\n\n```http\nGET /headers HTTP/1.1\nAccept: */*\nAccept-Encoding: gzip, deflate\nCookie: valued-visitor=yes;foo=bar\nHost: pie.dev\nReferer: https://httpie.org/\nUser-Agent: Bacon/1.0\nX-Foo: Bar\n```\n\n### Default request headers\n\nThere are a couple of default headers that HTTPie sets:\n\n```http\nGET / HTTP/1.1\nAccept: */*\nAccept-Encoding: gzip, deflate\nUser-Agent: HTTPie/<version>\nHost: <taken-from-URL>\n```\n\nAll of these can be overwritten or unset (see below).\n\n### Reading headers from a file\n\nYou can read headers from a file by using the `:@` operator. This would also effectively strip the newlines from the end. See [file based separators](#file-based-separators) for more examples.\n\n```bash\n$ http pie.dev/headers X-Data:@files/text.txt\n```\n\n### Empty headers and header un-setting\n\nTo unset a previously specified header (such a one of the default headers), use `Header:`:\n\n```bash\n$ http pie.dev/headers Accept: User-Agent:\n```\n\nTo send a header with an empty value, use `Header;`, with a semicolon:\n\n```bash\n$ http pie.dev/headers 'Header;'\n```\n\nPlease note that some internal headers, such as `Content-Length`, can’t be unset if\nthey are automatically added by the client itself.\n\n### Multiple header values with the same name\n\nIf the request is sent with multiple headers that are sharing the same name, then\nthe HTTPie will send them individually.\n\n```bash\nhttp --offline example.org Cookie:one Cookie:two\n```\n\n```http\nGET / HTTP/1.1\nCookie: one\nCookie: two\n```\n\nIt is also possible to pass a single header value pair, where the value is a comma\nseparated list of header values. Then the client will send it as a single header.\n\n```bash\nhttp --offline example.org Numbers:one,two\n```\n\n```http\nGET / HTTP/1.1\nNumbers: one,two\n```\n\nAlso be aware that if the current session contains any headers they will get overwritten\nby individual commands when sending a request instead of being joined together.\n\n### Limiting response headers\n\nThe `--max-headers=n` option allows you to control the number of headers HTTPie reads before giving up (the default `0`, i.e., there’s no limit).\n\n```bash\n$ http --max-headers=100 pie.dev/get\n```\n\n## Offline mode\n\nUse `--offline` to construct HTTP requests without sending them anywhere.\nWith `--offline`, HTTPie builds a request based on the specified options and arguments, prints it to `stdout`, and then exits. It works completely offline; no network connection is ever made. This has a number of use cases, including:\n\nGenerating API documentation examples that you can copy & paste without sending a request:\n\n```bash\n$ http --offline POST server.chess/api/games API-Key:ZZZ w=magnus b=hikaru t=180 i=2\n```\n\n```bash\n$ http --offline MOVE server.chess/api/games/123 API-Key:ZZZ p=b a=R1a3 t=77\n```\n\nGenerating raw requests that can be sent with any other client:\n\n```bash\n# 1. save a raw request to a file:\n$ http --offline POST pie.dev/post hello=world > request.http\n```\n\n```bash\n# 2. send it over the wire with, for example, the fantastic netcat tool:\n$ nc pie.dev 80 < request.http\n```\n\nYou can also use the `--offline` mode for debugging and exploring HTTP and HTTPie, and for “dry runs”.\n\n`--offline` has the side effect of automatically activating `--print=HB`, i.e., both the request headers and the body\nare printed. You can customize the output with the usual [output options](#output-options), with the exception where there\nis no response to be printed. You can use `--offline` in combination with all the other options (e.g. `--session`).\n\n## Cookies\n\nHTTP clients send cookies to the server as regular [HTTP headers](#http-headers).\nThat means, HTTPie does not offer any special syntax for specifying cookies — the usual `Header:Value` notation is used:\n\nSend a single cookie:\n\n```bash\n$ http pie.dev/cookies Cookie:sessionid=foo\n```\n\n```http\nGET / HTTP/1.1\nAccept: */*\nAccept-Encoding: gzip, deflate\nConnection: keep-alive\nCookie: sessionid=foo\nHost: pie.dev\nUser-Agent: HTTPie/0.9.9\n```\n\nSend multiple cookies (note: the header is quoted to prevent the shell from interpreting the `;`):\n\n```bash\n$ http pie.dev/cookies 'Cookie:sessionid=foo;another-cookie=bar'\n```\n\n```http\nGET / HTTP/1.1\nAccept: */*\nAccept-Encoding: gzip, deflate\nConnection: keep-alive\nCookie: sessionid=foo;another-cookie=bar\nHost: pie.dev\nUser-Agent: HTTPie/0.9.9\n```\n\nIf you often deal with cookies in your requests, then you’d appreciate\nthe [sessions](#sessions) feature.\n\n## Authentication\n\nThe currently supported authentication schemes are Basic and Digest (see [auth plugins](#auth-plugins) for more). There are two flags that control authentication:\n\n|              Flag | Arguments                                                                                                                                                                                                                                                                                                                                                                                                                                 |\n|------------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n|      `--auth, -a` | Pass either a `username:password` pair or a `token` as the argument. If the selected authenticated method requires username/password combination and if you only specify a username (`-a username`), you’ll be prompted for the password before the request is sent. To send an empty password, pass `username:`. The `username:password@hostname` URL syntax is supported as well (but credentials passed via `-a` have higher priority) |\n| `--auth-type, -A` | Specify the auth mechanism. Possible values are `basic`, `digest`, `bearer` or the name of any [auth plugins](#auth-plugins) you have installed. The default value is `basic` so it can often be omitted                                                                                                                                                                                                                                  |\n\n### Basic auth\n\n```bash\n$ http -a username:password pie.dev/basic-auth/username/password\n```\n\n### Digest auth\n\n```bash\n$ http -A digest -a username:password pie.dev/digest-auth/httpie/username/password\n```\n\n### Bearer auth\n\n```bash\nhttps -A bearer -a token pie.dev/bearer\n```\n\n### Password prompt\n\nIf you omit the password part of `--auth, -a`, HTTPie securely prompts you for it:\n\n```bash\n$ http -a username pie.dev/basic-auth/username/password\n```\n\nPlease note that when you use [`--session`](#sessions), prompted passwords are persisted in session files.\n\n### Empty password\n\nTo send an empty password without being prompted for it, include a trailing colon in the credentials:\n\n```bash\n$ http -a username: pie.dev/headers\n```\n\n### `.netrc`\n\nAuthentication information from your `~/.netrc` file is by default honored as well.\n\nFor example:\n\n```bash\n$ cat ~/.netrc\nmachine pie.dev\nlogin httpie\npassword test\n```\n\n```bash\n$ http pie.dev/basic-auth/httpie/test\nHTTP/1.1 200 OK\n[...]\n```\n\nThis can be disabled with the `--ignore-netrc` option:\n\n```bash\n$ http --ignore-netrc pie.dev/basic-auth/httpie/test\nHTTP/1.1 401 UNAUTHORIZED\n[...]\n```\n\n### Auth plugins\n\nAdditional authentication mechanism can be installed as plugins.\nThey can be found on the [Python Package Index](https://pypi.python.org/pypi?%3Aaction=search&term=httpie&submit=search).\nHere are a few picks:\n\n- [httpie-api-auth](https://github.com/pd/httpie-api-auth): ApiAuth\n- [httpie-aws-auth](https://github.com/httpie/httpie-aws-auth): AWS / Amazon S3\n- [httpie-edgegrid](https://github.com/akamai-open/httpie-edgegrid): EdgeGrid\n- [httpie-hmac-auth](https://github.com/guardian/httpie-hmac-auth): HMAC\n- [httpie-jwt-auth](https://github.com/teracyhq/httpie-jwt-auth): JWTAuth (JSON Web Tokens)\n- [httpie-negotiate](https://github.com/ndzou/httpie-negotiate): SPNEGO (GSS Negotiate)\n- [httpie-ntlm](https://github.com/httpie/httpie-ntlm): NTLM (NT LAN Manager)\n- [httpie-oauth1](https://github.com/qcif/httpie-oauth1): OAuth 1.0a\n- [requests-hawk](https://github.com/mozilla-services/requests-hawk): Hawk\n\nSee [plugin manager](#plugin-manager) for more details.\n\n## HTTP redirects\n\nBy default, HTTP redirects are not followed and only the first\nresponse is shown:\n\n```bash\n$ http pie.dev/redirect/3\n```\n\n### Follow `Location`\n\nTo instruct HTTPie to follow the `Location` header of `30x` responses\nand show the final response instead, use the `--follow, -F` option:\n\n```bash\n$ http --follow pie.dev/redirect/3\n```\n\nWith `307 Temporary Redirect` and `308 Permanent Redirect`, the method and the body of the original request\nare reused to perform the redirected request. Otherwise, a body-less `GET` request is performed.\n\n### Showing intermediary redirect responses\n\nIf you wish to see the intermediary requests/responses,\nthen use the `--all` option:\n\n```bash\n$ http --follow --all pie.dev/redirect/3\n```\n\n### Limiting maximum redirects followed\n\nTo change the default limit of maximum `30` redirects, use the `--max-redirects=<limit>` option:\n\n```bash\n$ http --follow --all --max-redirects=2 pie.dev/redirect/3\n```\n\n## Proxies\n\nYou can specify proxies to be used through the `--proxy` argument for each protocol (which is included in the value in case of redirects across protocols):\n\n```bash\n$ http --proxy=http:http://10.10.1.10:3128 --proxy=https:https://10.10.1.10:1080 example.org\n```\n\nWith Basic authentication:\n\n```bash\n$ http --proxy=http:http://user:pass@10.10.1.10:3128 example.org\n```\n\n### Environment variables\n\nYou can also configure proxies by environment variables `ALL_PROXY`, `HTTP_PROXY` and `HTTPS_PROXY`, and the underlying\n[Requests library](https://requests.readthedocs.io/en/latest/) will pick them up.\nIf you want to disable proxies configured through the environment variables for certain hosts, you can specify them in `NO_PROXY`.\n\nIn your `~/.bash_profile`:\n\n```bash\nexport HTTP_PROXY=http://10.10.1.10:3128\nexport HTTPS_PROXY=https://10.10.1.10:1080\nexport NO_PROXY=localhost,example.com\n```\n\n### SOCKS\n\nUsage for SOCKS is the same as for other types of [proxies](#proxies):\n\n```bash\n$ http --proxy=http:socks5://user:pass@host:port --proxy=https:socks5://user:pass@host:port example.org\n```\n\n## HTTPS\n\n### Server SSL certificate verification\n\nTo skip the host’s SSL certificate verification, you can pass `--verify=no` (default is `yes`):\n\n```bash\n$ http --verify=no https://pie.dev/get\n```\n\n### Custom CA bundle\n\nYou can also use `--verify=<CA_BUNDLE_PATH>` to set a custom CA bundle path:\n\n```bash\n$ http --verify=/ssl/custom_ca_bundle https://example.org\n```\n\n### Client side SSL certificate\n\nTo use a client side certificate for the SSL communication, you can pass\nthe path of the cert file with `--cert`:\n\n```bash\n$ http --cert=client.pem https://example.org\n```\n\nIf the private key is not contained in the cert file, you may pass the\npath of the key file with `--cert-key`:\n\n```bash\n$ http --cert=client.crt --cert-key=client.key https://example.org\n```\n\nIf the given private key requires a passphrase, HTTPie will automatically detect it\nand ask it through a prompt:\n\n```bash\n$ http --cert=client.pem --cert-key=client.key https://example.org\nhttp: passphrase for client.key: ****\n```\n\nIf you don't want to see a prompt, you can supply the passphrase with the `--cert-key-pass`\nargument:\n\n```bash\n$ http --cert=client.pem --cert-key=client.key --cert-key-pass=my_password https://example.org\n```\n\n### SSL version\n\nUse the `--ssl=<PROTOCOL>` option to specify the desired protocol version to use.\nThis will default to SSL v2.3 which will negotiate the highest protocol that both the server and your installation of OpenSSL support.\nThe available protocols are `ssl2.3`, `ssl3`, `tls1`, `tls1.1`, `tls1.2`, `tls1.3`.\n(The actually available set of protocols may vary depending on your OpenSSL installation.)\n\n```bash\n# Specify the vulnerable SSL v3 protocol to talk to an outdated server:\n$ http --ssl=ssl3 https://vulnerable.example.org\n```\n\n### SSL ciphers\n\nYou can specify the available ciphers with `--ciphers`.\nIt should be a string in the [OpenSSL cipher list format](https://www.openssl.org/docs/man1.1.0/man1/ciphers.html).\n\n```bash\n$ http --ciphers=ECDHE-RSA-AES128-GCM-SHA256 https://pie.dev/get\n```\n\nNote: these cipher strings do not change the negotiated version of SSL or TLS, they only affect the list of available cipher suites.\n\nTo see the default cipher string, run `http --help` and see the `--ciphers` section under SSL.\n\n## Output options\n\nBy default, HTTPie only outputs the final response and the whole response\nmessage is printed (headers as well as the body). You can control what should\nbe printed via several options:\n\n|                     Option | What is printed                                                                                    |\n|---------------------------:|----------------------------------------------------------------------------------------------------|\n|            `--headers, -h` | Only the response headers are printed                                                              |\n|               `--body, -b` | Only the response body is printed                                                                  |\n|               `--meta, -m` | Only the [response metadata](#response-meta) is printed                                            |\n|            `--verbose, -v` | Print the whole HTTP exchange (request and response). This option also enables `--all` (see below) |\n| `--verbose --verbose, -vv` | Just like `-v`, but also include the response metadata.                                            |\n|              `--print, -p` | Selects parts of the HTTP exchange                                                                 |\n|              `--quiet, -q` | Don’t print anything to `stdout` and `stderr`                                                      |\n\n### What parts of the HTTP exchange should be printed\n\nAll the other [output options](#output-options) are under the hood just shortcuts for the more powerful `--print, -p`.\nIt accepts a string of characters each of which represents a specific part of the HTTP exchange:\n\n| Character | Stands for                      |\n|----------:|---------------------------------|\n|       `H` | request headers                 |\n|       `B` | request body                    |\n|       `h` | response headers                |\n|       `b` | response body                   |\n|       `m` | [response meta](#response-meta) |\n\nPrint request and response headers:\n\n```bash\n$ http --print=Hh PUT pie.dev/put hello=world\n```\n\n#### Response meta\n\nThe response metadata section currently includes the total time elapsed. It’s the number of seconds between opening the network connection and downloading the last byte of response the body.\n\n\nTo _only_ show the response metadata, use `--meta, -m` (analogically to `--headers, -h` and `--body, -b`):\n\n```bash\n$ http --meta pie.dev/delay/1\n```\n\n```console\nElapsed time: 1.099171542s\n```\n\nThe [extra verbose `-vv` output](#extra-verbose-output) includes the meta section by default. You can also show it in combination with other parts of the exchange via [`--print=m`](#what-parts-of-the-http-exchange-should-be-printed). For example, here we print it together with the response headers:\n\n```bash\n$ http --print=hm pie.dev/get\n```\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\n\nElapsed time: 0.077538375s\n```\n\n\nPlease note that it also includes time spent on formatting the output, which adds a small penalty. Also, if the body is not part of the output, [we don’t spend time downloading it](#conditional-body-download).\n\nIf you [use `--style` with one of the Pie themes](#colors-and-formatting), you’ll see the time information color-coded (green/yellow/orange/red) based on how long the exchange took.\n\n\n### Verbose output\n\n`--verbose` can often be useful for debugging the request and generating documentation examples:\n\n```bash\n$ http --verbose PUT pie.dev/put hello=world\nPUT /put HTTP/1.1\nAccept: application/json, */*;q=0.5\nAccept-Encoding: gzip, deflate\nContent-Type: application/json\nHost: pie.dev\nUser-Agent: HTTPie/0.2.7dev\n\n{\n    \"hello\": \"world\"\n}\n\nHTTP/1.1 200 OK\nConnection: keep-alive\nContent-Length: 477\nContent-Type: application/json\nDate: Sun, 05 Aug 2012 00:25:23 GMT\nServer: gunicorn/0.13.4\n\n{\n    […]\n}\n```\n\n#### Extra verbose output\n\nIf you run HTTPie with `-vv` or `--verbose --verbose`, then it would also display the [response metadata](#response-meta).\n\n```bash\n# Just like the above, but with additional columns like the total elapsed time\n$ http -vv pie.dev/get\n```\n\n### Quiet output\n\n`--quiet` redirects all output that would otherwise go to `stdout` and `stderr` to `/dev/null` (except for errors and warnings).\nThis doesn’t affect output to a file via `--output` or `--download`.\n\n```bash\n# There will be no output:\n$ http --quiet pie.dev/post enjoy='the silence'\n```\n\nIf you’d like to silence warnings as well, use `-q` or `--quiet` twice:\n\n```bash\n# There will be no output, even in case of an unexpected response status code:\n$ http -qq --check-status pie.dev/post enjoy='the silence without warnings'\n```\n\n### Update warnings\n\nWhen there is a new release available for your platform (for example; if you installed HTTPie through `pip`, it will check the latest version on `PyPI`), HTTPie will regularly warn you about the new update (once a week). If you want to disable this behavior, you can set `disable_update_warnings` to `true` in your [config](#config) file.\n\n### Viewing intermediary requests/responses\n\nTo see all the HTTP communication, i.e. the final request/response as well as any possible intermediary requests/responses, use the `--all` option.\nThe intermediary HTTP communication include followed redirects (with `--follow`), the first unauthorized request when HTTP digest authentication is used (`--auth=digest`), etc.\n\n```bash\n# Include all responses that lead to the final one:\n$ http --all --follow pie.dev/redirect/3\n```\n\nThe intermediary requests/responses are by default formatted according to `--print, -p` (and its shortcuts described above).\n\n### Conditional body download\n\nAs an optimization, the response body is downloaded from the server only if it’s part of the output.\nThis is similar to performing a `HEAD` request, except that it applies to any HTTP method you use.\n\nLet’s say that there is an API that returns the whole resource when it is updated, but you are only interested in the response headers to see the status code after an update:\n\n```bash\n$ http --headers PATCH pie.dev/patch name='New Name'\n```\n\nSince you are only printing the HTTP headers here, the connection to the server is closed as soon as all the response headers have been received.\nTherefore, bandwidth and time isn’t wasted downloading the body which you don’t care about.\nThe response headers are downloaded always, even if they are not part of the output\n\n## Raw request body\n\nIn addition to crafting structured [JSON](#json) and [forms](#forms) requests with the [request items](#request-items) syntax, you can provide a raw request body that will be sent without further processing.\nThese two approaches for specifying request data (i.e., structured and raw) cannot be combined.\n\nThere are three methods for passing raw request data: piping via `stdin`,\n`--raw='data'`, and `@/file/path`.\n\n### Redirected Input\n\nThe universal method for passing request data is through redirected `stdin`\n(standard input)—piping.\n\nBy default, `stdin` data is buffered and then with no further processing used as the request body.\nIf you provide `Content-Length`, then the request body is streamed without buffering.\nYou may also use `--chunked` to enable streaming via [chunked transfer encoding](#chunked-transfer-encoding)\nor `--compress, -x` to [compress the request body](#compressed-request-body).\n\nThere are multiple useful ways to use piping:\n\nRedirect from a file:\n\n```bash\n$ http PUT pie.dev/put X-API-Token:123 < files/data.json\n```\n\nOr the output of another program:\n\n```bash\n$ grep '401 Unauthorized' /var/log/httpd/error_log | http POST pie.dev/post\n```\n\nYou can use `echo` for simple data:\n\n```bash\n$ echo -n '{\"name\": \"John\"}' | http PATCH pie.dev/patch X-API-Token:123\n```\n\nYou can also use a Bash *here string*:\n\n```bash\n$ http pie.dev/post <<<'{\"name\": \"John\"}'\n```\n\nYou can even pipe web services together using HTTPie:\n\n```bash\n$ http GET https://api.github.com/repos/httpie/cli | http POST pie.dev/post\n```\n\nYou can use `cat` to enter multiline data on the terminal:\n\n```bash\n$ cat | http POST pie.dev/post\n<paste>\n^D\n```\n\n```bash\n$ cat | http POST pie.dev/post Content-Type:text/plain\n- buy milk\n- call parents\n^D\n```\n\nOn macOS, you can send the contents of the clipboard with `pbpaste`:\n\n```bash\n$ pbpaste | http PUT pie.dev/put\n```\n\nPassing data through `stdin` **can’t** be combined with data fields specified on the command line:\n\n```bash\n$ echo -n 'data' | http POST example.org more=data  # This is invalid\n```\n\nTo prevent HTTPie from reading `stdin` data you can use the `--ignore-stdin` option.\n\n### Request data via `--raw`\n\nIn a situation when piping data via `stdin` is not convenient (for example,\nwhen generating API docs examples), you can specify the raw request body via\nthe `--raw` option.\n\n```bash\n$ http --raw 'Hello, world!' pie.dev/post\n```\n\n```bash\n$ http --raw '{\"name\": \"John\"}' pie.dev/post\n```\n\n### Request data from a filename\n\nAn alternative to redirected `stdin` is specifying a filename (as `@/path/to/file`) whose content is used as if it came from `stdin`.\n\nIt has the advantage that the `Content-Type` header is automatically set to the appropriate value based on the filename extension.\nFor example, the following request sends the verbatim contents of that XML file with `Content-Type: application/xml`:\n\n```bash\n$ http PUT pie.dev/put @files/data.xml\n```\n\nFile uploads are always streamed to avoid memory issues with large files.\n\n## Chunked transfer encoding\n\nYou can use the `--chunked` flag to instruct HTTPie to use `Transfer-Encoding: chunked`:\n\n```bash\n$ http --chunked PUT pie.dev/put hello=world\n```\n\n```bash\n$ http --chunked --multipart PUT pie.dev/put hello=world foo@files/data.xml\n```\n\n```bash\n$ http --chunked pie.dev/post @files/data.xml\n```\n\n```bash\n$ cat files/data.xml | http --chunked pie.dev/post\n```\n\n## Compressed request body\n\nYou can use the `--compress, -x` flag to instruct HTTPie to use `Content-Encoding: deflate` and compress the request data:\n\n```bash\n$ http --compress pie.dev/post @files/data.xml\n```\n\n```bash\n$ cat files/data.xml | http --compress pie.dev/post\n```\n\nIf compressing the data does not save size, HTTPie sends it untouched. To always compress the data, specify `--compress, -x` twice:\n\n```bash\n$ http -xx PUT pie.dev/put hello=world\n```\n\n## Terminal output\n\nHTTPie does several things by default in order to make its terminal output easy to read.\n\n### Colors and formatting\n\nSyntax highlighting is applied to HTTP headers and bodies (where it makes sense).\nYou can choose your preferred color scheme via the `--style` option if you don’t like the default one.\nThere are dozens of styles available, here are just a few notable ones:\n\n|       Style | Description                                                                                                                                                                                                                                                 |\n|------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n|      `auto` | Follows your terminal ANSI color styles. This is the default style used by HTTPie                                                                                                                                                                           |\n|   `default` | Default styles of the underlying Pygments library. Not actually used by default by HTTPie. You can enable it with `--style=default`                                                                                                                         |\n|  `pie-dark` | HTTPie’s original brand style. Also used in [HTTPie for Web and Desktop](https://httpie.io/product).                                                                                                                                                        |\n| `pie-light` | Like `pie-dark`, but for terminals with light background colors.                                                                                                                                                                                            |\n|       `pie` | A generic version of `pie-dark` and `pie-light` themes that can work with any terminal background. Its universality requires compromises in terms of legibility, but it’s useful if you frequently switch your terminal between dark and light backgrounds. |\n|   `monokai` | A popular color scheme. Enable with `--style=monokai`                                                                                                                                                                                                       |\n|    `fruity` | A bold, colorful scheme. Enable with `--style=fruity`                                                                                                                                                                                                       |\n|           … | See `$ http --help` for all the possible `--style` values                                                                                                                                                                                                   |\n\nUse one of these options to control output processing:\n\n|            Option | Description                                                   |\n|------------------:|---------------------------------------------------------------|\n|    `--pretty=all` | Apply both colors and formatting. Default for terminal output |\n| `--pretty=colors` | Apply colors                                                  |\n| `--pretty=format` | Apply formatting                                              |\n|   `--pretty=none` | Disables output processing. Default for redirected output     |\n\nHTTPie looks at `Content-Type` to select the right syntax highlighter and formatter for each message body. If that fails (e.g., the server provides the wrong type), or you prefer a different treatment, you can manually overwrite the mime type for a response with `--response-mime`:\n\n```bash\n$ http --response-mime=text/yaml pie.dev/get\n```\n\nFormatting has the following effects:\n\n- HTTP headers are sorted by name.\n- JSON data is indented, sorted by keys, and unicode escapes are converted\n  to the characters they represent.\n- XML and XHTML data is indented.\n\nPlease note that sometimes there might be changes made by formatters on the actual response body (e.g.,\ncollapsing empty tags on XML) but the end result will always be semantically indistinguishable. Some of\nthese formatting changes can be configured more granularly through [format options](#format-options).\n\n### Format options\n\nThe `--format-options=opt1:value,opt2:value` option allows you to control how the output should be formatted\nwhen formatting is applied. The following options are available:\n\n|           Option | Default value | Shortcuts                |\n|-----------------:|:-------------:|--------------------------|\n|   `headers.sort` |    `true`     | `--sorted`, `--unsorted` |\n|    `json.format` |    `true`     | N/A                      |\n|    `json.indent` |      `4`      | N/A                      |\n| `json.sort_keys` |    `true`     | `--sorted`, `--unsorted` |\n|     `xml.format` |    `true`     | N/A                      |\n|     `xml.indent` |      `2`      | N/A                      |\n\nFor example, this is how you would disable the default header and JSON key\nsorting, and specify a custom JSON indent size:\n\n```bash\n$ http --format-options headers.sort:false,json.sort_keys:false,json.indent:2 pie.dev/get\n```\n\nThere are also two shortcuts that allow you to quickly disable and re-enable\nsorting-related format options (currently it means JSON keys and headers):\n`--unsorted` and `--sorted`.\n\nThis is something you will typically store as one of the default options in your [config](#config) file.\n\n### Redirected output\n\nHTTPie uses a different set of defaults for redirected output than for [terminal output](#terminal-output).\nThe differences being:\n\n- Formatting and colors aren’t applied (unless `--pretty` is specified).\n- Only the response body is printed (unless one of the [output options](#output-options) is set).\n- Also, binary data isn’t suppressed.\n\nThe reason is to make piping HTTPie’s output to another programs and downloading files work with no extra flags.\nMost of the time, only the raw response body is of an interest when the output is redirected.\n\nDownload a file:\n\n```bash\n$ http pie.dev/image/png > image.png\n```\n\nDownload an image of an [Octocat](https://octodex.github.com/images/original.jpg), resize it using [ImageMagick](https://imagemagick.org/), and upload it elsewhere:\n\n```bash\n$ http octodex.github.com/images/original.jpg | convert - -resize 25% - | http example.org/Octocats\n```\n\nForce colorizing and formatting, and show both the request and the response in `less` pager:\n\n```bash\n$ http --pretty=all --verbose pie.dev/get | less -R\n```\n\nThe `-R` flag tells `less` to interpret color escape sequences included HTTPie’s output.\n\nYou can create a shortcut for invoking HTTPie with colorized and paged output by adding the following to your `~/.bash_profile`:\n\n```bash\nfunction httpless {\n    # `httpless example.org'\n    http --pretty=all --print=hb \"$@\" | less -R;\n}\n```\n\n### Binary data\n\nBinary data is suppressed for terminal output, which makes it safe to perform requests to URLs that send back binary data.\nBinary data is also suppressed in redirected but prettified output.\nThe connection is closed as soon as we know that the response body is binary,\n\n```bash\n$ http pie.dev/bytes/2000\n```\n\nYou will nearly instantly see something like this:\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/octet-stream\n\n+-----------------------------------------+\n| NOTE: binary data not shown in terminal |\n+-----------------------------------------+\n```\n\n### Display encoding\n\nHTTPie tries to do its best to decode message bodies when printing them to the terminal correctly. It uses the encoding specified in the `Content-Type` `charset` attribute. If a message doesn’t define its charset, we auto-detect it. For very short messages (1–32B), where auto-detection would be unreliable, we default to UTF-8. For cases when the response encoding is still incorrect, you can manually overwrite the response charset with `--response-charset`:\n\n```bash\n$ http --response-charset=big5 pie.dev/get\n```\n\n## Download mode\n\nHTTPie features a download mode in which it acts similarly to `wget`.\n\nWhen enabled using the `--download, -d` flag, response headers are printed to the terminal (`stderr`), and a progress bar is shown while the response body is being saved to a file.\n\n```bash\n$ http --download https://github.com/httpie/cli/archive/master.tar.gz\n```\n\n```http\nHTTP/1.1 200 OK\nContent-Disposition: attachment; filename=httpie-master.tar.gz\nContent-Length: 257336\nContent-Type: application/x-gzip\n\nDownloading 251.30 kB to \"httpie-master.tar.gz\"\nDone. 251.30 kB in 2.73862s (91.76 kB/s)\n```\n\n### Downloaded filename\n\nThere are three mutually exclusive ways through which HTTPie determines\nthe output filename (with decreasing priority):\n\n1. You can explicitly provide it via `--output, -o`. The file gets overwritten if it already exists (or appended to with `--continue, -c`).\n2. The server may specify the filename in the optional `Content-Disposition` response header. Any leading dots are stripped from a server-provided filename.\n3. The last resort HTTPie uses is to generate the filename from a combination of the request URL and the response `Content-Type`. The initial URL is always used as the basis for the generated filename — even if there has been one or more redirects.\n\nTo prevent data loss by overwriting, HTTPie adds a unique numerical suffix to the filename when necessary (unless specified with `--output, -o`).\n\n### Piping while downloading\n\nYou can also redirect the response body to another program while the response headers and progress are still shown in the terminal:\n\n```bash\n$ http -d https://github.com/httpie/cli/archive/master.tar.gz | tar zxf -\n```\n\n### Resuming downloads\n\nIf `--output, -o` is specified, you can resume a partial download using the `--continue, -c` option.\nThis only works with servers that support `Range` requests and `206 Partial Content` responses.\nIf the server doesn’t support that, the whole file will simply be downloaded:\n\n```bash\n$ http -dco file.zip example.org/file\n```\n\n`-dco` is shorthand for `--download` `--continue` `--output`.\n\n### Other notes\n\n- The `--download` option only changes how the response body is treated.\n- You can still set custom headers, use sessions, `--verbose, -v`, etc.\n- `--download` always implies `--follow` (redirects are followed).\n- `--download` also implies `--check-status` (error HTTP status will result in a non-zero exist static code).\n- HTTPie exits with status code `1` (error) if the body hasn’t been fully downloaded.\n- `Accept-Encoding` can’t be set with `--download`.\n\n## Streamed responses\n\nResponses are downloaded and printed in chunks.\nThis allows for streaming and large file downloads without using too much memory.\nHowever, when [colors and formatting](#colors-and-formatting) are applied, the whole response is buffered and only then processed at once.\n\n### Disabling buffering\n\nYou can use the `--stream, -S` flag to make two things happen:\n\n1. The output is flushed in much smaller chunks without any buffering, which makes HTTPie behave kind of like `tail -f` for URLs.\n2. Streaming becomes enabled even when the output is prettified: It will be applied to each line of the response and flushed immediately. This makes it possible to have a nice output for long-lived requests, such as one to the [Twitter streaming API](https://developer.twitter.com/en/docs/tutorials/consuming-streaming-data).\n\nThe `--stream` option is automatically enabled when the response headers include `Content-Type: text/event-stream`.\n\n### Example use cases\n\nPrettified streamed response:\n\n```bash\n$ http --stream pie.dev/stream/3\n```\n\nStreamed output by small chunks à la `tail -f`:\n\n```bash\n# Send each new line (JSON object) to another URL as soon as it arrives from a streaming API:\n$ http --stream pie.dev/stream/3 | while read line; do echo \"$line\" | http pie.dev/post ; done\n```\n\n## Sessions\n\nBy default, every request HTTPie makes is completely independent of any previous ones to the same host.\n\nHowever, HTTPie also supports persistent sessions via the `--session=SESSION_NAME_OR_PATH` option.\nIn a session, custom [HTTP headers](#http-headers) (except for the ones starting with `Content-` or `If-`), [authentication](#authentication), and [cookies](#cookies) (manually specified or sent by the server) persist between requests to the same host.\n\n```bash\n# Create a new session:\n$ http --session=./session.json pie.dev/headers API-Token:123\n```\n\n```bash\n# Inspect / edit the generated session file:\n$ cat session.json\n```\n\n```bash\n# Re-use the existing session — the API-Token header will be set:\n$ http --session=./session.json pie.dev/headers\n```\n\nAll session data, including credentials, prompted passwords, cookie data, and custom headers are stored in plain text.\nThat means session files can also be created and edited manually in a text editor—they are regular JSON.\nIt also means that they can be read by anyone who has access to the session file.\n\n### Named sessions\n\nYou can create one or more named session per host. For example, this is how you can create a new session named `user1` for `pie.dev`:\n\n```bash\n$ http --session=user1 -a user1:password pie.dev/get X-Foo:Bar\n```\n\nFrom now on, you can refer to the session by its name (`user1`).\nWhen you choose to use the session again, all previously specified authentication or HTTP headers will automatically be set:\n\n```bash\n$ http --session=user1 pie.dev/get\n```\n\nTo create or reuse a different session, simply specify a different name:\n\n```bash\n$ http --session=user2 -a user2:password pie.dev/get X-Bar:Foo\n```\n\nNamed sessions’ data is stored in JSON files inside the `sessions` subdirectory of the [config](#config) directory, typically `~/.config/httpie/sessions/<host>/<name>.json` (`%APPDATA%\\httpie\\sessions\\<host>\\<name>.json` on Windows).\n\nIf you have executed the above commands on a Unix machine, you should be able to list the generated sessions files using:\n\n```bash\n$ ls -l ~/.config/httpie/sessions/pie.dev\n```\n\n### Anonymous sessions\n\nInstead of giving it a name, you can also directly specify a path to a session file.\nThis allows for sessions to be re-used across multiple hosts:\n\n```bash\n# Create a session:\n$ http --session=/tmp/session.json example.org\n```\n\n```bash\n# Use the session to make a request to another host:\n$ http --session=/tmp/session.json admin.example.org\n```\n\n```bash\n# You can also refer to a previously created named session:\n$ http --session=~/.config/httpie/sessions/another.example.org/test.json example.org\n```\n\nWhen creating anonymous sessions, please remember to always include at least one `/`, even if the session files is located in the current directory (i.e. `--session=./session.json` instead of just `--session=session.json`), otherwise HTTPie assumes a named session instead.\n\n### Readonly session\n\nTo use the original session file without updating it from the request/response exchange after it has been created, specify the session name via `--session-read-only=SESSION_NAME_OR_PATH` instead.\n\n```bash\n# If the session file doesn’t exist, then it is created:\n$ http --session-read-only=./ro-session.json pie.dev/headers Custom-Header:orig-value\n```\n\n```bash\n# But it is not updated:\n$ http --session-read-only=./ro-session.json pie.dev/headers Custom-Header:new-value\n```\n\n### Host-based cookie policy\n\nCookies persisted in sessions files have a `domain` field. This _binds_ them to a specified hostname. For example:\n\n```json\n{\n    \"cookies\": [\n        {\n            \"domain\": \"pie.dev\",\n            \"name\": \"pie\",\n            \"value\": \"apple\"\n        },\n        {\n            \"domain\": \"httpbin.org\",\n            \"name\": \"bin\",\n            \"value\": \"http\"\n        }\n    ]\n}\n```\n\nUsing this session file, we include `Cookie: pie=apple` only in requests against `pie.dev` and subdomains (e.g., `foo.pie.dev` or `foo.bar.pie.dev`):\n\n```bash\n$ http --session=./session.json pie.dev/cookies\n```\n\n```json\n{\n    \"cookies\": {\n        \"pie\": \"apple\"\n    }\n}\n```\n\nTo make a cookie domain _unbound_ (i.e., to make it available to all hosts, including throughout a cross-domain redirect chain), you can set the `domain` field to `null` in the session file:\n\n```json\n{\n    \"cookies\": [\n        {\n            \"domain\": null,\n            \"name\": \"unbound-cookie\",\n            \"value\": \"send-me-to-any-host\"\n        }\n    ]\n}\n```\n\n```bash\n$ http --session=./session.json pie.dev/cookies\n```\n\n```json\n{\n    \"cookies\": {\n        \"unbound-cookie\": \"send-me-to-any-host\"\n    }\n}\n```\n\n\n### Cookie storage behavior\n\nThere are three possible sources of persisted cookies within a session. They have the following storage priority: 1—response; 2—command line; 3—session file.\n\n1. Receive a response with a `Set-Cookie` header:\n\n    ```bash\n    $ http --session=./session.json pie.dev/cookie/set?foo=bar\n    ```\n\n2. Send a cookie specified on the command line as seen in [cookies](#cookies):\n\n    ```bash\n    $ http --session=./session.json pie.dev/headers Cookie:foo=bar\n    ```\n\n3. Manually set cookie parameters in the session file:\n\n    ```json\n    {\n       \"cookies\": {\n           \"foo\": {\n               \"expires\": null,\n               \"path\": \"/\",\n               \"secure\": false,\n               \"value\": \"bar\"\n               }\n       }\n    }\n    ```\n\nIn summary:\n\n- Cookies set via the CLI overwrite cookies of the same name inside session files.\n- Server-sent `Set-Cookie` header cookies overwrite any pre-existing ones with the same name.\n\nCookie expiration handling:\n\n- When the server expires an existing cookie, HTTPie removes it from the session file.\n- When a cookie in a session file expires, HTTPie removes it before sending a new request.\n\n### Upgrading sessions\n\nHTTPie may introduce changes in the session file format.  When HTTPie detects an obsolete format, it shows a warning. You can upgrade your session files using the following commands:\n\nUpgrade all existing [named sessions](#named-sessions) inside the `sessions` subfolder of your [config directory](https://httpie.io/docs/cli/config-file-directory):\n\n```bash\n$ httpie cli sessions upgrade-all\nUpgraded 'api_auth' @ 'pie.dev' to v3.1.0\nUpgraded 'login_cookies' @ 'httpie.io' to v3.1.0\n```\n\nUpgrading individual sessions requires you to specify the session's hostname. That allows HTTPie to find the correct file in the case of name sessions. Additionally, it allows it to correctly bind cookies when upgrading with [`--bind-cookies`](#session-upgrade-options).\n\nUpgrade a single [named session](#named-sessions):\n\n```bash\n$ httpie cli sessions upgrade pie.dev api_auth\nUpgraded 'api_auth' @ 'pie.dev' to v3.1.0\n```\n\nUpgrade a single [anonymous session](#anonymous-sessions) using a file path:\n\n```bash\n$ httpie cli sessions upgrade pie.dev ./session.json\nUpgraded 'session.json' @ 'pie.dev' to v3.1.0\n```\n\n#### Session upgrade options\n\nThese flags are available for both `sessions upgrade` and `sessions upgrade-all`:\n\n| Option           | Description                                                                                                                                                                   |\n|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `--bind-cookies` | Bind all previously [unbound cookies](#host-based-cookie-policy) to the session’s host ([context](https://github.com/httpie/cli/security/advisories/GHSA-9w4w-cpc8-h2fq)). |\n\n\n## Config\n\nHTTPie uses a simple `config.json` file.\nThe file doesn’t exist by default, but you can create it manually.\n\n### Config file directory\n\nTo see the exact location for your installation, run `http --debug` and look for `config_dir` in the output.\n\nThe default location of the configuration file on most platforms is `$XDG_CONFIG_HOME/httpie/config.json` (defaulting to `~/.config/httpie/config.json`).\n\nFor backward compatibility, if the directory `~/.httpie` exists, the configuration file there will be used instead.\n\nOn Windows, the config file is located at `%APPDATA%\\httpie\\config.json`.\n\nThe config directory can be changed by setting the `$HTTPIE_CONFIG_DIR` environment variable:\n\n```bash\n$ export HTTPIE_CONFIG_DIR=/tmp/httpie\n$ http pie.dev/get\n```\n\n### Configurable options\n\nCurrently, HTTPie offers a single configurable option:\n\n#### `default_options`\n\nAn `Array` (by default empty) of default options that should be applied to every invocation of HTTPie.\n\nFor instance, you can use this config option to change your default color theme:\n\n```bash\n$ cat ~/.config/httpie/config.json\n```\n\n```json\n{\n    \"default_options\": [\n        \"--style=fruity\"\n    ]\n}\n```\n\nTechnically, it is possible to include any HTTPie options in there.\nHowever, it is not recommended modifying the default behavior in a way that would break your compatibility with the wider world as that may become confusing.\n\n#### `plugins_dir`\n\nThe directory where the plugins will be installed. HTTPie needs to have read/write access on that directory, since\n`httpie cli plugins install` will download new plugins to there. See [plugin manager](#plugin-manager) for more information.\n\n### Un-setting previously specified options\n\nDefault options from the config file, or specified any other way, can be unset for a particular invocation via `--no-OPTION` arguments passed via the command line (e.g., `--no-style` or `--no-session`).\n\n## Scripting\n\nWhen using HTTPie from shell scripts, it can be handy to set the `--check-status` flag.\nIt instructs HTTPie to exit with an error if the HTTP status is one of `3xx`, `4xx`, or `5xx`.\nThe exit status will be `3` (unless `--follow` is set), `4`, or `5`, respectively.\n\n```bash\n#!/bin/bash\n\nif http --check-status --ignore-stdin --timeout=2.5 HEAD pie.dev/get &> /dev/null; then\n    echo 'OK!'\nelse\n    case $? in\n        2) echo 'Request timed out!' ;;\n        3) echo 'Unexpected HTTP 3xx Redirection!' ;;\n        4) echo 'HTTP 4xx Client Error!' ;;\n        5) echo 'HTTP 5xx Server Error!' ;;\n        6) echo 'Exceeded --max-redirects=<n> redirects!' ;;\n        *) echo 'Other Error!' ;;\n    esac\nfi\n```\n\n### Best practices\n\nThe default behavior of automatically reading `stdin` is typically not desirable during non-interactive invocations.\nYou most likely want to use the `--ignore-stdin` option to disable it.\n\nIt's important to note that without the `--ignore-stdin` option, HTTPie may appear to have stopped working (hang). This happens because, in situations where HTTPie is invoked outside of an interactive session, such as from a cron job, `stdin` is not connected to a terminal. This means that the rules for [redirected input](#redirected-input) will be followed. When `stdin` is redirected, HTTPie assumes that the input will contain the request body, and it waits for the input to be provided. But, since there is neither any input data nor an end-of-file (`EOF`) signal, HTTPie gets stuck. To avoid this problem, the `--ignore-stdin` flag should be used in scripts, unless data is being piped to HTTPie.\n\nTo prevent your program from becoming unresponsive when the server fails to respond, it's a good idea to use the `--timeout` option to set a connection timeout limit.\n\n## Plugin manager\n\nHTTPie offers extensibility through a [plugin API](https://github.com/httpie/cli/blob/master/httpie/plugins/base.py),\nand there are dozens of plugins available to try!\nThey add things like new authentication methods ([akamai/httpie-edgegrid](https://github.com/akamai/httpie-edgegrid)),\ntransport mechanisms ([httpie/httpie-unixsocket](https://github.com/httpie/httpie-unixsocket)),\nmessage convertors ([banteg/httpie-image](https://github.com/banteg/httpie-image)), or simply\nchange how a response is formatted.\n\n> Note: Plugins are usually made by our community members, and thus have no direct relationship with\n> the HTTPie project. We do not control / review them at the moment, so use them at your own discretion.\n\nFor managing these plugins; starting with 3.0, we are offering a new plugin manager.\n\nThis command is currently in beta.\n\n### `httpie cli`\n\n#### `httpie cli check-updates`\n\nYou can check whether a new update is available for your system by running `httpie cli check-updates`:\n\n```bash-termible\n$ httpie cli check-updates\n```\n\n#### `httpie cli export-args`\n\n`httpie cli export-args` command can expose the parser specification of `http`/`https` commands\n(like an API definition) to outside tools so that they can use this to build better interactions\nover them (e.g., offer auto-complete).\n\nAvailable formats to export in include:\n\n| Format | Description                                                                                                                                       |\n|--------|---------------------------------------------------------------------------------------------------------------------------------------------------|\n| `json` | Export the parser spec in JSON. The schema includes a top-level `version` parameter which should be interpreted in [semver](https://semver.org/). |\n\nYou can use any of these formats with `--format` parameter, but the default one is `json`.\n\n```bash\n$ httpie cli export-args | jq '\"Program: \" + .spec.name + \", Version: \" +  .version'\n\"Program: http, Version: 0.0.1a0\"\n```\n\n#### `httpie cli plugins`\n\n`plugins` interface is a very simple plugin manager for installing, listing and uninstalling HTTPie plugins.\n\nIn the past `pip` was used to install/uninstall plugins, but on some environments (e.g., brew installed\npackages) it wasn’t working properly. The new interface is a very simple overlay on top of `pip` to allow\nplugin installations on every installation method.\n\nBy default, the plugins (and their missing dependencies) will be stored under the configuration directory,\nbut this can be modified through `plugins_dir` variable on the config.\n\n##### `httpie cli plugins install`\n\nFor installing plugins from [PyPI](https://pypi.org/) or from local paths, `httpie cli plugins install`\ncan be used.\n\n```bash\n$ httpie cli plugins install httpie-plugin\nInstalling httpie-plugin...\nSuccessfully installed httpie-plugin-1.0.2\n```\n\n> Tip: Generally HTTPie plugins start with `httpie-` prefix. Try searching for it on [PyPI](https://pypi.org/search/?q=httpie-)\n> to find out all plugins from the community.\n\n##### `httpie cli plugins list`\n\nList all installed plugins.\n\n```bash\n$ httpie cli plugins list\nhttpie_plugin (1.0.2)\n  httpie_plugin (httpie.plugins.auth.v1)\nhttpie_plugin_2 (1.0.6)\n  httpie_plugin_2 (httpie.plugins.auth.v1)\nhttpie_converter (1.0.0)\n  httpie_iterm_converter (httpie.plugins.converter.v1)\n  httpie_konsole_konverter (httpie.plugins.converter.v1)\n```\n\n##### `httpie cli plugins upgrade`\n\nFor upgrading already installed plugins, use `httpie plugins upgrade`.\n\n```bash\n$ httpie cli plugins upgrade httpie-plugin\n```\n\n##### `httpie cli plugins uninstall`\n\nUninstall plugins from the isolated plugins directory. If the plugin is not installed\nthrough `httpie cli plugins install`, it won’t uninstall it.\n\n```bash\n$ httpie cli plugins uninstall httpie-plugin\n```\n\n## Meta\n\n### Interface design\n\nThe syntax of the command arguments closely correspond to the actual HTTP requests sent over the wire.\nIt has the advantage that it’s easy to remember and read.\nYou can often translate an HTTP request to an HTTPie argument list just by inlining the request elements.\nFor example, compare this HTTP request:\n\n```http\nPOST /post HTTP/1.1\nHost: pie.dev\nX-API-Key: 123\nUser-Agent: Bacon/1.0\nContent-Type: application/x-www-form-urlencoded\n\nname=value&name2=value2\n```\n\nwith the HTTPie command that sends it:\n\n```bash\n$ http -f POST pie.dev/post \\\n    X-API-Key:123 \\\n    User-Agent:Bacon/1.0 \\\n    name=value \\\n    name2=value2\n```\n\nNotice that both the order of elements and the syntax are very similar, and that only a small portion of the command is used to control HTTPie and doesn’t directly correspond to any part of the request (here, it’s only `-f` asking HTTPie to send a form request).\n\nThe two modes, `--pretty=all` (default for terminal) and `--pretty=none` (default for [redirected output](#redirected-output)), allow for both user-friendly interactive use and usage from scripts, where HTTPie serves as a generic HTTP client.\n\nIn the future, the command line syntax and some of the `--OPTIONS` may change slightly, as HTTPie improves and new features are added.\nAll changes are recorded in the [change log](#change-log).\n\n### Community and Support\n\nHTTPie has the following community channels:\n\n- [GitHub Issues](https://github.com/httpie/cli/issues) for bug reports and feature requests\n- [Discord server](https://httpie.io/discord) to ask questions, discuss features, and for general API development discussion\n- [StackOverflow](https://stackoverflow.com) to ask questions (make sure to use the [httpie](https://stackoverflow.com/questions/tagged/httpie) tag)\n\n### Related projects\n\n#### Dependencies\n\nUnder the hood, HTTPie uses these two amazing libraries:\n\n- [Requests](https://requests.readthedocs.io/en/latest/) — Python HTTP library for humans\n- [Pygments](https://pygments.org/) — Python syntax highlighter\n\n#### HTTPie friends\n\nHTTPie plays exceptionally well with the following tools:\n\n- [http-prompt](https://github.com/httpie/http-prompt) — an interactive shell for HTTPie featuring autocomplete and command syntax highlighting\n- [jq](https://stedolan.github.io/jq/) — CLI JSON processor that works great in conjunction with HTTPie\n\nHelpers to convert from other client tools:\n\n- [CurliPie](https://curlipie.open-api.vn) — library to convert cURL commands to HTTPie\n\n#### Alternatives\n\n- [httpcat](https://github.com/httpie/httpcat) — a lower-level sister utility of HTTPie for constructing raw HTTP requests on the command line\n- [curl](https://curl.haxx.se) — a \"Swiss knife\" command line tool and an exceptional library for transferring data with URLs.\n\n### Contributing\n\nSee [CONTRIBUTING](https://github.com/httpie/cli/blob/master/CONTRIBUTING.md).\n\n### Security policy\n\nSee [github.com/httpie/cli/security/policy](https://github.com/httpie/cli/security/policy).\n\n### Change log\n\nSee [CHANGELOG](https://github.com/httpie/cli/blob/master/CHANGELOG.md).\n\n### Artwork\n\n- [README Animation](https://github.com/httpie/cli/blob/master/docs/httpie-animation.gif) by [Allen Smith](https://github.com/loranallensmith).\n\n### Licence\n\nBSD-3-Clause: [LICENSE](https://github.com/httpie/cli/blob/master/LICENSE).\n\n### Authors\n\n[Jakub Roztocil](https://roztocil.co) ([@jakubroztocil](https://twitter.com/jakubroztocil)) created HTTPie and [these fine people](https://github.com/httpie/cli/blob/master/AUTHORS.md) have contributed.\n\n"
  },
  {
    "path": "docs/config.json",
    "content": "{\n  \"website\": {\n    \"master_and_released_docs_differ_after\": null\n  }\n}\n"
  },
  {
    "path": "docs/contributors/README.md",
    "content": "Here we maintain a database of contributors, from which we generate credits on release blog posts and social media.\n\nFor the HTTPie blog see: <https://httpie.io/blog>.\n"
  },
  {
    "path": "docs/contributors/fetch.py",
    "content": "\"\"\"\nGenerate the contributors database.\n\nFIXME: replace `requests` calls with the HTTPie API, when available.\n\"\"\"\nimport json\nimport os\nimport re\nimport sys\nfrom copy import deepcopy\nfrom datetime import datetime\nfrom pathlib import Path\nfrom subprocess import check_output\nfrom time import sleep\nfrom typing import Any, Dict, Optional, Set\n\nimport requests\n\nFullNames = Set[str]\nGitHubLogins = Set[str]\nPerson = Dict[str, str]\nPeople = Dict[str, Person]\nUserInfo = Dict[str, Any]\n\nCO_AUTHORS = re.compile(r'Co-authored-by: ([^<]+) <').finditer\nAPI_URL = 'https://api.github.com'\nREPO = OWNER = 'httpie'\nREPO_URL = f'{API_URL}/repos/{REPO}/{OWNER}'\n\nHERE = Path(__file__).parent\nDB_FILE = HERE / 'people.json'\n\nDEFAULT_PERSON: Person = {'committed': [], 'reported': [], 'github': '', 'twitter': ''}\nSKIPPED_LABELS = {'invalid'}\n\nGITHUB_TOKEN = os.getenv('GITHUB_TOKEN')\nassert GITHUB_TOKEN, 'GITHUB_TOKEN envar is missing'\n\n\nclass FinishedForNow(Exception):\n    \"\"\"Raised when remaining GitHub rate limit is zero.\"\"\"\n\n\ndef main(previous_release: str, current_release: str) -> int:\n    since = release_date(previous_release)\n    until = release_date(current_release)\n\n    contributors = load_awesome_people()\n    try:\n        committers = find_committers(since, until)\n        reporters = find_reporters(since, until)\n    except Exception as exc:\n        # We want to save what we fetched so far. So pass.\n        print(' !! ', exc)\n\n    try:\n        merge_all_the_people(current_release, contributors, committers, reporters)\n        fetch_missing_users_details(contributors)\n    except FinishedForNow:\n        # We want to save what we fetched so far. So pass.\n        print(' !! Committers:', committers)\n        print(' !! Reporters:', reporters)\n        exit_status = 1\n    else:\n        exit_status = 0\n\n    save_awesome_people(contributors)\n    return exit_status\n\n\ndef find_committers(since: str, until: str) -> FullNames:\n    url = f'{REPO_URL}/commits'\n    page = 1\n    per_page = 100\n    params = {\n        'since': since,\n        'until': until,\n        'per_page': per_page,\n    }\n    committers: FullNames = set()\n\n    while 'there are commits':\n        params['page'] = page\n        data = fetch(url, params=params)\n\n        for item in data:\n            commit = item['commit']\n            committers.add(commit['author']['name'])\n            debug(' >>> Commit', item['html_url'])\n            for co_author in CO_AUTHORS(commit['message']):\n                name = co_author.group(1)\n                committers.add(name)\n\n        if len(data) < per_page:\n            break\n        page += 1\n\n    return committers\n\n\ndef find_reporters(since: str, until: str) -> GitHubLogins:\n    url = f'{API_URL}/search/issues'\n    page = 1\n    per_page = 100\n    params = {\n        'q': f'repo:{REPO}/{OWNER} is:issue closed:{since}..{until}',\n        'per_page': per_page,\n    }\n    reporters: GitHubLogins = set()\n\n    while 'there are issues':\n        params['page'] = page\n        data = fetch(url, params=params)\n\n        for item in data['items']:\n            # Filter out unwanted labels.\n            if any(label['name'] in SKIPPED_LABELS for label in item['labels']):\n                continue\n            debug(' >>> Issue', item['html_url'])\n            reporters.add(item['user']['login'])\n\n        if len(data['items']) < per_page:\n            break\n        page += 1\n\n    return reporters\n\n\ndef merge_all_the_people(release: str, contributors: People, committers: FullNames, reporters: GitHubLogins) -> None:\n    \"\"\"\n    >>> contributors = {'Alice': new_person(github='alice', twitter='alice')}\n    >>> merge_all_the_people('2.6.0', contributors, {}, {})\n    >>> contributors\n    {'Alice': {'committed': [], 'reported': [], 'github': 'alice', 'twitter': 'alice'}}\n\n    >>> contributors = {'Bob': new_person(github='bob', twitter='bob')}\n    >>> merge_all_the_people('2.6.0', contributors, {'Bob'}, {'bob'})\n    >>> contributors\n    {'Bob': {'committed': ['2.6.0'], 'reported': ['2.6.0'], 'github': 'bob', 'twitter': 'bob'}}\n\n    >>> contributors = {'Charlotte': new_person(github='charlotte', twitter='charlotte', committed=['2.5.0'], reported=['2.5.0'])}\n    >>> merge_all_the_people('2.6.0', contributors, {'Charlotte'}, {'charlotte'})\n    >>> contributors\n    {'Charlotte': {'committed': ['2.5.0', '2.6.0'], 'reported': ['2.5.0', '2.6.0'], 'github': 'charlotte', 'twitter': 'charlotte'}}\n\n    \"\"\"\n    # Update known contributors.\n    for name, details in contributors.items():\n        if name in committers:\n            if release not in details['committed']:\n                details['committed'].append(release)\n            committers.remove(name)\n        if details['github'] in reporters:\n            if release not in details['reported']:\n                details['reported'].append(release)\n            reporters.remove(details['github'])\n\n    # Add new committers.\n    for name in committers:\n        user_info = user(fullname=name)\n        contributors[name] = new_person(\n            github=user_info['login'],\n            twitter=user_info['twitter_username'],\n            committed=[release],\n        )\n        if user_info['login'] in reporters:\n            contributors[name]['reported'].append(release)\n            reporters.remove(user_info['login'])\n\n    # Add new reporters.\n    for github_username in reporters:\n        user_info = user(github_username=github_username)\n        contributors[user_info['name'] or user_info['login']] = new_person(\n            github=github_username,\n            twitter=user_info['twitter_username'],\n            reported=[release],\n        )\n\n\ndef release_date(release: str) -> str:\n    date = check_output(['git', 'log', '-1', '--format=%ai', release], text=True).strip()\n    return datetime.strptime(date, '%Y-%m-%d %H:%M:%S %z').isoformat()\n\n\ndef load_awesome_people() -> People:\n    try:\n        with DB_FILE.open(encoding='utf-8') as fh:\n            return json.load(fh)\n    except (FileNotFoundError, ValueError):\n        return {}\n\n\ndef fetch(url: str, params: Optional[Dict[str, str]] = None) -> UserInfo:\n    headers = {\n        'Accept': 'application/vnd.github.v3+json',\n        'Authentication': f'token {GITHUB_TOKEN}'\n    }\n    for retry in range(1, 6):\n        debug(f'[{retry}/5]', f'{url = }', f'{params = }')\n        with requests.get(url, params=params, headers=headers) as req:\n            try:\n                req.raise_for_status()\n            except requests.exceptions.HTTPError as exc:\n                if exc.response.status_code == 403:\n                    # 403 Client Error: rate limit exceeded for url: ...\n                    now = int(datetime.utcnow().timestamp())\n                    xrate_limit_reset = int(exc.response.headers['X-RateLimit-Reset'])\n                    wait = xrate_limit_reset - now\n                    if wait > 20:\n                        raise FinishedForNow()\n                    debug(' !', 'Waiting', wait, 'seconds before another try ...')\n                    sleep(wait)\n                continue\n            return req.json()\n    assert ValueError('Rate limit exceeded')\n\n\ndef new_person(**kwargs: str) -> Person:\n    data = deepcopy(DEFAULT_PERSON)\n    data.update(**kwargs)\n    return data\n\n\ndef user(fullname: Optional[str] = '', github_username: Optional[str] = '') -> UserInfo:\n    if github_username:\n        url = f'{API_URL}/users/{github_username}'\n        return fetch(url)\n\n    url = f'{API_URL}/search/users'\n    for query in (f'fullname:{fullname}', f'user:{fullname}'):\n        params = {\n            'q': f'repo:{REPO}/{OWNER} {query}',\n            'per_page': 1,\n        }\n        user_info = fetch(url, params=params)\n        if user_info['items']:\n            user_url = user_info['items'][0]['url']\n            return fetch(user_url)\n\n\ndef fetch_missing_users_details(people: People) -> None:\n    for name, details in people.items():\n        if details['github'] and details['twitter']:\n            continue\n        user_info = user(github_username=details['github'], fullname=name)\n        if not details['github']:\n            details['github'] = user_info['login']\n        if not details['twitter']:\n            details['twitter'] = user_info['twitter_username']\n\n\ndef save_awesome_people(people: People) -> None:\n    with DB_FILE.open(mode='w', encoding='utf-8') as fh:\n        json.dump(people, fh, indent=4, sort_keys=True)\n        fh.write(\"\\n\")\n\n\ndef debug(*args: Any) -> None:\n    if os.getenv('DEBUG') == '1':\n        print(*args)\n\n\nif __name__ == '__main__':\n    ret = 1\n    try:\n        ret = main(*sys.argv[1:])\n    except TypeError:\n        ret = 2\n        print(f'''\nFetch contributors to a release.\n\nUsage:\n    python {sys.argv[0]} {sys.argv[0]} <RELEASE N-1> <RELEASE N>\nExample:\n    python {sys.argv[0]} 2.4.0 2.5.0\n\nDefine the DEBUG=1 environment variable to enable verbose output.\n''')\n    except KeyboardInterrupt:\n        ret = 255\n    sys.exit(ret)\n"
  },
  {
    "path": "docs/contributors/generate.py",
    "content": "\"\"\"\nGenerate snippets to copy-paste.\n\"\"\"\nimport sys\n\nfrom jinja2 import Template\n\nfrom fetch import HERE, load_awesome_people\n\nTPL_FILE = HERE / 'snippet.jinja2'\n\nHTTPIE_TEAM = {\n    'claudiatd',\n    'jakubroztocil',\n    'jkbr',\n    'isidentical'\n}\n\nBOT_ACCOUNTS = {\n    'dependabot-sr'\n}\n\nIGNORE_ACCOUNTS = HTTPIE_TEAM | BOT_ACCOUNTS\n\n\ndef generate_snippets(release: str) -> str:\n    people = load_awesome_people()\n    contributors = {\n        name: details\n        for name, details in people.items()\n        if details['github'] not in IGNORE_ACCOUNTS\n        and (release in details['committed'] or release in details['reported'])\n    }\n\n    template = Template(source=TPL_FILE.read_text(encoding='utf-8'))\n    output = template.render(contributors=contributors, release=release)\n    print(output)\n    return 0\n\n\nif __name__ == '__main__':\n    ret = 1\n    try:\n        ret = generate_snippets(sys.argv[1])\n    except (IndexError, TypeError):\n        ret = 2\n        print(f'''\nGenerate snippets for contributors to a release.\n\nUsage:\n    python {sys.argv[0]} {sys.argv[0]} <RELEASE>\n''')\n    sys.exit(ret)\n"
  },
  {
    "path": "docs/contributors/people.json",
    "content": "{\n    \"Aaron Miller\": {\n        \"committed\": [],\n        \"github\": \"aaronhmiller\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": \"aaronmiller8\"\n    },\n    \"Alexander Bogdanov\": {\n        \"committed\": [],\n        \"github\": \"ab-kily\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Almad\": {\n        \"committed\": [\n            \"2.5.0\"\n        ],\n        \"github\": \"Almad\",\n        \"reported\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"twitter\": \"almadcz\"\n    },\n    \"Andr\\u00e1s Czig\\u00e1ny\": {\n        \"committed\": [],\n        \"github\": \"andrascz\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Annette Wilson\": {\n        \"committed\": [],\n        \"github\": \"annettejanewilson\",\n        \"reported\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Anton Emelyanov\": {\n        \"committed\": [\n            \"2.5.0\"\n        ],\n        \"github\": \"king-menin\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"Batuhan Taskaya\": {\n        \"committed\": [\n            \"3.0.0\",\n            \"3.2.0\"\n        ],\n        \"github\": \"isidentical\",\n        \"reported\": [\n            \"3.0.0\",\n            \"3.2.0\"\n        ],\n        \"twitter\": \"isidentical\"\n    },\n    \"Brad Crittenden\": {\n        \"committed\": [],\n        \"github\": \"bac\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Chad\": {\n        \"committed\": [],\n        \"github\": \"cythrawll\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"D8ger\": {\n        \"committed\": [],\n        \"github\": \"caofanCPU\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Dave\": {\n        \"committed\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"github\": \"davecheney\",\n        \"reported\": [],\n        \"twitter\": \"davecheney\"\n    },\n    \"Dawid Ferenczy Rogo\\u017ean\": {\n        \"committed\": [],\n        \"github\": \"ferenczy\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": \"DawidFerenczy\"\n    },\n    \"Ed Rooth\": {\n        \"committed\": [],\n        \"github\": \"sym3tri\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Elena Lape\": {\n        \"committed\": [\n            \"2.5.0\"\n        ],\n        \"github\": \"elenalape\",\n        \"reported\": [],\n        \"twitter\": \"elena_lape\"\n    },\n    \"Ethan Mills\": {\n        \"committed\": [\n            \"3.2.0\"\n        ],\n        \"github\": \"ethanmills\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"Fabio Peruzzo\": {\n        \"committed\": [],\n        \"github\": \"peruzzof\",\n        \"reported\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"F\\u00fash\\u0113ng\": {\n        \"committed\": [],\n        \"github\": \"lienide\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Gabriel Cruz\": {\n        \"committed\": [],\n        \"github\": \"gmelodie\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": \"gmelodiecruz\"\n    },\n    \"Gaurav\": {\n        \"committed\": [\n            \"3.0.0\"\n        ],\n        \"github\": \"gkcs\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"Giampaolo Rodola\": {\n        \"committed\": [],\n        \"github\": \"giampaolo\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Greg Myers\": {\n        \"committed\": [\n            \"3.0.0\"\n        ],\n        \"github\": \"myersg86\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"Hugh Williams\": {\n        \"committed\": [],\n        \"github\": \"hughpv\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Ilya Sukhanov\": {\n        \"committed\": [\n            \"2.5.0\"\n        ],\n        \"github\": \"IlyaSukhanov\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Jakub Roztocil\": {\n        \"committed\": [\n            \"2.5.0\",\n            \"2.6.0\",\n            \"3.0.0\",\n            \"3.2.0\"\n        ],\n        \"github\": \"jakubroztocil\",\n        \"reported\": [\n            \"2.5.0\",\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"twitter\": \"jakubroztocil\"\n    },\n    \"Jan Bra\\u0161na\": {\n        \"committed\": [\n            \"3.0.0\"\n        ],\n        \"github\": \"janbrasna\",\n        \"reported\": [],\n        \"twitter\": \"janbrasna\"\n    },\n    \"Jan Verbeek\": {\n        \"committed\": [\n            \"2.5.0\"\n        ],\n        \"github\": \"blyxxyz\",\n        \"reported\": [\n            \"3.0.0\",\n            \"3.2.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Jannik Vieten\": {\n        \"committed\": [\n            \"2.5.0\"\n        ],\n        \"github\": \"exploide\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"Jesper Holmberg\": {\n        \"committed\": [],\n        \"github\": \"strindberg\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Kirill Krasnov\": {\n        \"committed\": [],\n        \"github\": \"Kirill\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Marcel St\\u00f6r\": {\n        \"committed\": [\n            \"2.5.0\"\n        ],\n        \"github\": \"marcelstoer\",\n        \"reported\": [],\n        \"twitter\": \"frightanic\"\n    },\n    \"Marco Seguri\": {\n        \"committed\": [],\n        \"github\": \"seguri\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Mariano Ruiz\": {\n        \"committed\": [],\n        \"github\": \"mrsarm\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": \"mrsarm82\"\n    },\n    \"Mark Rosenbaum\": {\n        \"committed\": [],\n        \"github\": \"markrosenbaum\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Micka\\u00ebl Schoentgen\": {\n        \"committed\": [\n            \"2.5.0\",\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"github\": \"BoboTiG\",\n        \"reported\": [\n            \"2.5.0\",\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"twitter\": \"__tiger222__\"\n    },\n    \"Mike DePalatis\": {\n        \"committed\": [],\n        \"github\": \"mivade\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Miro Hron\\u010dok\": {\n        \"committed\": [\n            \"2.5.0\",\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"github\": \"hroncok\",\n        \"reported\": [],\n        \"twitter\": \"hroncok\"\n    },\n    \"Mohamed Daahir\": {\n        \"committed\": [],\n        \"github\": \"ducaale\",\n        \"reported\": [\n            \"2.5.0\",\n            \"3.2.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Nanashi.\": {\n        \"committed\": [],\n        \"github\": \"sevenc-nanashi\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": \"sevenc_nanashi\"\n    },\n    \"Nicklas Ansman Giertz\": {\n        \"committed\": [],\n        \"github\": \"ansman\",\n        \"reported\": [\n            \"3.2.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Oliver Fish\": {\n        \"committed\": [],\n        \"github\": \"Oliver-Fish\",\n        \"reported\": [\n            \"3.2.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Omer Akram\": {\n        \"committed\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"github\": \"om26er\",\n        \"reported\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"twitter\": \"om26er\"\n    },\n    \"Patrick Taylor\": {\n        \"committed\": [],\n        \"github\": \"pmeister\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Paul Laffitte\": {\n        \"committed\": [],\n        \"github\": \"paullaffitte\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": \"plaffitt\"\n    },\n    \"Pavel Alexeev aka Pahan-Hubbitus\": {\n        \"committed\": [],\n        \"github\": \"Hubbitus\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Roberto L\\u00f3pez L\\u00f3pez\": {\n        \"committed\": [],\n        \"github\": \"robertolopezlopez\",\n        \"reported\": [\n            \"3.2.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Russell Shurts\": {\n        \"committed\": [],\n        \"github\": \"rshurts\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Samuel Marks\": {\n        \"committed\": [],\n        \"github\": \"SamuelMarks\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Sebastian Czech\": {\n        \"committed\": [\n            \"3.0.0\"\n        ],\n        \"github\": \"sebastianczech\",\n        \"reported\": [],\n        \"twitter\": \"sebaczech\"\n    },\n    \"Sullivan SENECHAL\": {\n        \"committed\": [],\n        \"github\": \"soullivaneuh\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Thomas Klinger\": {\n        \"committed\": [],\n        \"github\": \"mosesontheweb\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Vincent van \\u2019t Zand\": {\n        \"committed\": [],\n        \"github\": \"vovtz\",\n        \"reported\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Vivaan Verma\": {\n        \"committed\": [\n            \"3.0.0\"\n        ],\n        \"github\": \"doublevcodes\",\n        \"reported\": [],\n        \"twitter\": \"doublevcodes\"\n    },\n    \"Vladimir Berkutov\": {\n        \"committed\": [\n            \"3.0.0\"\n        ],\n        \"github\": \"dair-targ\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"Will Rogers\": {\n        \"committed\": [],\n        \"github\": \"wjrogers\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"Yannic Schneider\": {\n        \"committed\": [],\n        \"github\": \"cynay\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"a1346054\": {\n        \"committed\": [\n            \"2.5.0\"\n        ],\n        \"github\": \"a1346054\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"arloan\": {\n        \"committed\": [],\n        \"github\": \"arloan\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"bl-ue\": {\n        \"committed\": [\n            \"2.5.0\"\n        ],\n        \"github\": \"FiReBlUe45\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"blueray453\": {\n        \"committed\": [],\n        \"github\": \"blueray453\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"claudiatd\": {\n        \"committed\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"github\": \"claudiatd\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"coldcoff\": {\n        \"committed\": [],\n        \"github\": \"coldcoff\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"dependabot[bot]\": {\n        \"committed\": [\n            \"3.2.0\"\n        ],\n        \"github\": \"dependabot-sr\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"dkreeft\": {\n        \"committed\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"github\": \"dkreeft\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"greg\": {\n        \"committed\": [\n            \"3.0.0\"\n        ],\n        \"github\": \"gregkh\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"henryhu712\": {\n        \"committed\": [\n            \"2.5.0\"\n        ],\n        \"github\": \"henryhu712\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"hosseingt\": {\n        \"committed\": [\n            \"3.0.0\"\n        ],\n        \"github\": \"hosseingt\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"jakubroztocil\": {\n        \"committed\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"github\": \"jkbr\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"josephworks\": {\n        \"committed\": [],\n        \"github\": \"josephworks\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"jungle-boogie\": {\n        \"committed\": [],\n        \"github\": \"jungle-boogie\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"luisuimi\": {\n        \"committed\": [],\n        \"github\": \"luisuimi\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"luzpaz\": {\n        \"committed\": [\n            \"3.2.0\"\n        ],\n        \"github\": \"luzpaz\",\n        \"reported\": [],\n        \"twitter\": null\n    },\n    \"nixbytes\": {\n        \"committed\": [\n            \"2.5.0\"\n        ],\n        \"github\": \"nixbytes\",\n        \"reported\": [],\n        \"twitter\": \"linuxbyte3\"\n    },\n    \"peterpt\": {\n        \"committed\": [],\n        \"github\": \"peterpt\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"qiulang\": {\n        \"committed\": [],\n        \"github\": \"qiulang\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"stonebig\": {\n        \"committed\": [],\n        \"github\": \"stonebig\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"whodidthis\": {\n        \"committed\": [],\n        \"github\": \"whodidthis\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"zhaohanqing95\": {\n        \"committed\": [],\n        \"github\": \"zhaohanqing95\",\n        \"reported\": [\n            \"3.2.0\"\n        ],\n        \"twitter\": null\n    },\n    \"zoulja\": {\n        \"committed\": [],\n        \"github\": \"zoulja\",\n        \"reported\": [\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"zwx00\": {\n        \"committed\": [],\n        \"github\": \"zwx00\",\n        \"reported\": [\n            \"2.5.0\"\n        ],\n        \"twitter\": null\n    },\n    \"\\u5d14\\u5c0f\\u4e8c\": {\n        \"committed\": [],\n        \"github\": \"rogerdehe\",\n        \"reported\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    },\n    \"\\u9ec4\\u6d77\": {\n        \"committed\": [],\n        \"github\": \"hh-in-zhuzhou\",\n        \"reported\": [\n            \"2.6.0\",\n            \"3.0.0\"\n        ],\n        \"twitter\": null\n    }\n}\n"
  },
  {
    "path": "docs/contributors/snippet.jinja2",
    "content": "<!-- Blog post -->\n\n## Community contributions\n\nWe’d like to thank these amazing people for their contributions to this release:\n{% for name, details in contributors.items() -%}\n- [{{ name }}](https://github.com/{{ details.github }}){{ '' if loop.last else '\\n' }}\n{%- endfor %}\n\n<!-- Twitter -->\n\nWe’d like to thank these amazing people for their contributions to HTTPie {{ release }}: {% for name, details in contributors.items() if details.twitter -%}\n    @{{ details.twitter }}{{ '' if loop.last else ', ' }}\n{%- endfor %} 🥧\n"
  },
  {
    "path": "docs/installation/README.md",
    "content": "Here we maintain a database of installation methods, from which we generate\nthe installation section in docs. If you’d like add or update an installation method,\nedit [methods.yml](./methods.yml), do not edit the main docs directly.\n\nFor HTTPie installation instructions see: <https://httpie.io/docs#installation>.\n"
  },
  {
    "path": "docs/installation/generate.py",
    "content": "import re\nimport sys\nfrom pathlib import Path\nfrom typing import Dict\n\nimport yaml\nfrom jinja2 import Template\n\nDatabase = Dict[str, dict]\n\n# Files\nHERE = Path(__file__).parent\nDB_FILE = HERE / 'methods.yml'\nDOC_FILE = HERE.parent / 'README.md'\nTPL_FILE = HERE / 'installation.jinja2'\n\n# Database keys\nKEY_DOC_STRUCTURE = 'docs-structure'\nKEY_TOOLS = 'tools'\n\n# Markers in-between content will be put.\nMARKER_START = '<div data-installation-instructions>'\nMARKER_END = '</div>'\n\n\ndef generate_documentation() -> str:\n    database = load_database()\n    structure = build_docs_structure(database)\n    template = Template(source=TPL_FILE.read_text(encoding='utf-8'))\n    output = template.render(structure=structure)\n    output = clean_template_output(output)\n    return output\n\n\ndef save_doc_file(content: str) -> None:\n    current_doc = load_doc_file()\n    marker_start = current_doc.find(MARKER_START) + len(MARKER_START)\n    assert marker_start > 0, 'cannot find the start marker'\n    marker_end = current_doc.find(MARKER_END, marker_start)\n    assert marker_start < marker_end, f'{marker_end=} < {marker_start=}'\n    updated_doc = (\n        current_doc[:marker_start]\n        + '\\n\\n'\n        + content\n        + '\\n\\n'\n        + current_doc[marker_end:]\n    )\n    if current_doc != updated_doc:\n        DOC_FILE.write_text(updated_doc, encoding='utf-8')\n\n\ndef build_docs_structure(database: Database):\n    tools = database[KEY_TOOLS]\n    assert len(tools) == len({tool['title'] for tool in tools.values()}), 'tool titles need to be unique'\n    tree = database[KEY_DOC_STRUCTURE]\n    structure = []\n    for platform, tools_ids in tree.items():\n        assert platform.isalnum(), f'{platform=} must be alphanumeric for generated links to work'\n        platform_tools = [tools[tool_id] for tool_id in tools_ids]\n        structure.append((platform, platform_tools))\n    return structure\n\n\ndef clean_template_output(output):\n    output = '\\n'.join(line.strip() for line in output.strip().splitlines())\n    output = re.sub('\\n{3,}', '\\n\\n', output)\n    return output\n\n\ndef load_database() -> Database:\n    return yaml.safe_load(DB_FILE.read_text(encoding='utf-8'))\n\n\ndef load_doc_file() -> str:\n    return DOC_FILE.read_text(encoding='utf-8')\n\n\ndef main() -> int:\n    content = generate_documentation()\n    save_doc_file(content)\n    return 0\n\n\nif __name__ == '__main__':\n    sys.exit(main())\n"
  },
  {
    "path": "docs/installation/installation.jinja2",
    "content": "<!--\nTHE INSTALLATION SECTION IS GENERATED\n\nDo not edit here, but in docs/installation/.\n\n-->\n{% for platform, tools in structure %}\n    - [{{ platform }}](#{{ platform.lower() }}){% endfor %} {# <= keep `endfor` here to prevent unwanted `\\n` #}\n\n{% for platform, tools in structure %}\n\n    ### {{ platform }}\n\n    {% for tool in tools %}\n        #### {{ tool.title }}\n\n        {% if tool.note %}\n            {{ tool.note }}\n        {% endif %}\n\n        {% if tool.links.setup %}\n            To install [{{ tool.name }}]({{ tool.links.homepage }}), see [its installation]({{ tool.links.setup }}).\n        {% endif %}\n\n        ```bash\n        # Install httpie\n        $ {{ tool.commands.install|join('\\n$ ') }}\n        ```\n\n        ```bash\n        # Upgrade httpie\n        $ {{ tool.commands.upgrade|join('\\n$ ') }}\n        ```\n    {% endfor %}\n\n{% endfor %}\n<!-- /GENERATED SECTION -->\n"
  },
  {
    "path": "docs/installation/methods.yml",
    "content": "# Database of HTTPie installation methods. Used to build the docs.\n#\n# We currently only include here methods for popular systems where we take care of the package,\n# or have a good relationship with the maintainers.\n#\n# Each tool name should be unique (it becomes a linkable header).\n# If a tools have `links.setup`, it also needs `links.homepage`.\n# Some tools are available on multiple platforms, take into account when editing.\n#\n\ndocs-structure:\n  Universal:\n    - pypi\n  macOS:\n    - brew-mac\n    - port\n  Windows:\n    - chocolatey\n  Linux:\n    - apt\n    - dnf\n    - yum\n    - single-binary\n    - snap-linux\n    - brew-linux\n    - pacman\n  FreeBSD:\n    - pkg\n\ntools:\n  apt:\n    title: Debian and Ubuntu\n    note: Also works for other Debian-derived distributions like MX Linux, Linux Mint, deepin, Pop!_OS, KDE neon, Zorin OS, elementary OS, Kubuntu, Devuan, Linux Lite, Peppermint OS, Lubuntu, antiX, Xubuntu, etc.\n    name: APT\n    links:\n      homepage: https://en.wikipedia.org/wiki/APT_(software)\n      package: https://packages.debian.org/sid/web/httpie\n    commands:\n      install:\n        - curl -SsL https://packages.httpie.io/deb/KEY.gpg | sudo gpg --dearmor -o /usr/share/keyrings/httpie.gpg\n      # - curl -SsL -o /etc/apt/sources.list.d/httpie.list https://packages.httpie.io/deb/httpie.list\n        - echo \"deb [arch=amd64 signed-by=/usr/share/keyrings/httpie.gpg] https://packages.httpie.io/deb ./\" | sudo tee /etc/apt/sources.list.d/httpie.list > /dev/null\n        - sudo apt update\n        - sudo apt install httpie\n      upgrade:\n        - sudo apt update && sudo apt upgrade httpie\n\n  brew-mac:\n    title: Homebrew\n    name: Homebrew\n    links:\n      homepage: https://brew.sh/\n      setup: https://docs.brew.sh/Installation\n      package: https://formulae.brew.sh/formula/httpie\n    commands:\n      install:\n        - brew update\n        - brew install httpie\n      upgrade:\n        - brew update\n        - brew upgrade httpie\n\n  brew-linux:\n    title: Linuxbrew\n    name: Linuxbrew\n    links:\n      homepage: https://docs.brew.sh/Homebrew-on-Linux\n      setup: https://docs.brew.sh/Homebrew-on-Linux#install\n      package: https://formulae.brew.sh/formula/httpie\n    commands:\n      install:\n        - brew update\n        - brew install httpie\n      upgrade:\n        - brew update\n        - brew upgrade httpie\n\n  chocolatey:\n    title: Chocolatey\n    name: Chocolatey\n    links:\n      homepage: https://chocolatey.org/\n      setup: https://chocolatey.org/install\n      package: https://community.chocolatey.org/packages/httpie/\n    commands:\n      install:\n        - choco install httpie\n      upgrade:\n        - choco upgrade httpie\n\n  dnf:\n    title: Fedora\n    name: DNF\n    links:\n      homepage: https://fedoraproject.org/wiki/DNF\n      package: https://src.fedoraproject.org/rpms/httpie\n    commands:\n      install:\n        - dnf install httpie\n      upgrade:\n        - dnf upgrade httpie\n\n  pacman:\n    title: Arch Linux\n    name: pacman\n    note: Also works for other Arch-derived distributions like ArcoLinux, EndeavourOS, Artix Linux, etc.\n    links:\n      homepage: https://archlinux.org/pacman/\n      package: https://archlinux.org/packages/community/any/httpie/\n    commands:\n      install:\n        - pacman -Syu httpie\n      upgrade:\n        - pacman -Syu\n\n  pkg:\n    title: FreshPorts\n    name: FreshPorts\n    links:\n      homepage: https://www.freebsd.org/cgi/man.cgi?query=pkg&sektion=8&n=1\n      package: https://www.freshports.org/www/py-httpie/\n    commands:\n      install:\n        - pkg install www/py-httpie\n      upgrade:\n        - pkg upgrade www/py-httpie\n\n  port:\n    title: MacPorts\n    name: MacPorts\n    links:\n      homepage: https://www.macports.org/\n      setup: https://www.macports.org/install.php\n      package: https://ports.macports.org/port/httpie/\n    commands:\n      install:\n        - port selfupdate\n        - port install httpie\n      upgrade:\n        - port selfupdate\n        - port upgrade httpie\n\n  pypi:\n    title: PyPI\n    name: pip\n    note: Please make sure you have Python 3.7 or newer (`python --version`).\n    links:\n      homepage: https://pypi.org/\n      # setup: https://pip.pypa.io/en/stable/installation/\n      package: https://pypi.org/project/httpie/\n    commands:\n      install:\n        - python -m pip install --upgrade pip wheel\n        - python -m pip install httpie\n      upgrade:\n        - python -m pip install --upgrade pip wheel\n        - python -m pip install --upgrade httpie\n\n  snap-linux:\n    title: Snapcraft (Linux)\n    name: Snapcraft\n    links:\n      homepage: https://snapcraft.io/\n      setup: https://snapcraft.io/docs/installing-snapd\n      package: https://snapcraft.io/httpie\n    commands:\n      install:\n        - snap install httpie\n      upgrade:\n        - snap refresh httpie\n\n  yum:\n    title: CentOS and RHEL\n    name: Yum\n    note: Also works for other RHEL-derived distributions like ClearOS, Oracle Linux, etc.\n    links:\n      homepage: http://yum.baseurl.org/\n      package: https://src.fedoraproject.org/rpms/httpie\n    commands:\n      install:\n        - yum install epel-release\n        - yum install httpie\n      upgrade:\n        - yum upgrade httpie\n\n  single-binary:\n    title: Single binary executables\n    name: Single binary executables\n    note: Get the standalone HTTPie Linux executables when you don't want to go through the full installation process.\n    links:\n    commands:\n      install:\n        - https --download packages.httpie.io/binaries/linux/http-latest -o http\n        - ln -ls ./http ./https\n        - chmod +x ./http ./https\n      upgrade:\n        - https --download packages.httpie.io/binaries/linux/http-latest -o http\n"
  },
  {
    "path": "docs/markdownlint.rb",
    "content": "# Rules for <https://github.com/markdownlint/markdownlint>\n\n# Load all rules by default\nall\n\n#\n# Tweak rules\n#\n\n# MD002 First header should be a top level header\n# Because we use HTML to hide them on the website.\nexclude_rule 'MD002'\n\n# MD007 Allow unordered list indentation\nexclude_rule 'MD007'\n\n# MD013 Line length\nexclude_rule 'MD013'\n\n# MD014 Dollar signs used before commands without showing output\nexclude_rule 'MD014'\n\n# MD028 Blank line inside blockquote\nexclude_rule 'MD028'\n\n# MD012 Multiple consecutive blank lines\nexclude_rule 'MD012'\n\n# Tell the linter to use ordered lists:\n#   1. Foo\n#   2. Bar\n#   3. Baz\n#\n# Instead of:\n#   1. Foo\n#   1. Bar\n#   1. Baz\nrule 'MD029', :style => :ordered\n\n# MD033 Inline HTML\n# TODO: Tweak elements when https://github.com/markdownlint/markdownlint/issues/118 will be done?\nexclude_rule 'MD033'\n\n# MD034 Bare URL used\n# TODO: Remove when https://github.com/markdownlint/markdownlint/issues/328 will be fixed.\nexclude_rule 'MD034'\n\n# MD041 First line in file should be a top level header\n# Because we use HTML to hide them on the website.\nexclude_rule 'MD041'\n"
  },
  {
    "path": "docs/packaging/README.md",
    "content": "# HTTPie release process\n\nWelcome on the documentation part of the **HTTPie release process**.\n\n- If you do not know HTTPie, have a look [here](https://httpie.io/cli).\n- If you are looking for HTTPie installation or upgrade instructions, then you can find all you need for your OS on [that page](https://httpie.io/docs#installation). In the case you do not find your OS, [let us know](https://github.com/httpie/cli/issues/).\n- If you are looking for technical information about the HTTPie packaging, then you are at the good place.\n\n## About\n\nYou are looking at the HTTPie packaging documentation, where you will find valuable information about how we manage to release HTTPie to lots of OSes, including technical data that may be worth reading if you are a package maintainer.\n\nThe overall release process starts simple:\n\n1. Bump the version identifiers in the following places:\n    - `httpie/__init__.py`\n    - `docs/packaging/windows-chocolatey/httpie.nuspec`\n    - `CHANGELOG.md`\n2. Commit your changes and make a PR against the `master`.\n3. Merge the PR, and tag the last commit with your version identifier.\n4. Make a GitHub release (by copying the text in `CHANGELOG.md`)\n5. Push that release to PyPI (dispatch the `Release PyPI` GitHub action).\n6. Once PyPI is ready, push the release to the Snap, Homebrew and Chocolatey with their respective actions.\n7. Go to the [`httpie/debian.httpie.io`](https://github.com/httpie/debian.httpie.io) repo and trigger the package index workflow.\n\n## Company-specific tasks\n\n- Blank the `master_and_released_docs_differ_after` value in [config.json](https://github.com/httpie/cli/blob/master/docs/config.json).\n- Update the [contributors list](../contributors).\n- Update the HTTPie version bundled into [Termible](https://termible.io/) ([example](https://github.com/httpie/termible/pull/1)).\n\n## Finally, spread dowstream\n\nFind out how we do release new versions for each and every supported OS in the following table.\nA more complete state of deployment can be found on [repology](https://repology.org/project/httpie/versions), including unofficial packages.\n\n|                                           OS | Maintainer     |\n| -------------------------------------------: | -------------- |\n|       [Arch Linux, and derived](linux-arch/) | trusted person |\n|   [CentOS, RHEL, and derived](linux-centos/) | trusted person |\n|                      [Fedora](linux-fedora/) | trusted person |\n| [Debian, Ubuntu, and derived](linux-debian/) | **HTTPie**     |\n|                 [Homebrew, Linuxbrew](brew/) | **HTTPie**     |\n|                      [Snapcraft](snapcraft/) | **HTTPie**     |\n|  [Windows — Chocolatey](windows-chocolatey/) | **HTTPie**     |\n\n:new: You do not find your system or you would like to see HTTPie supported on another OS? Then [let us know](https://github.com/httpie/cli/issues/).\n"
  },
  {
    "path": "docs/packaging/brew/README.md",
    "content": "# HTTPie on Homebrew, and Linuxbrew\n\nWelcome to the documentation about **packaging HTTPie for Homebrew**.\n\n- If you do not know HTTPie, have a look [here](https://httpie.io/cli).\n- If you are looking for HTTPie installation or upgrade instructions on Homebrew, then you can find them on [that page](https://httpie.io/docs#homebrew) ([that one](https://httpie.io/docs#linuxbrew) for Linuxbrew).\n- If you are looking for technical information about the HTTPie packaging on Homebrew, then you are in a good place.\n\n## About\n\nThis document contains technical details, where we describe how to create a patch for the latest HTTPie version for Homebrew. They apply to Linuxbrew as well.\nWe will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.\n\n## Overall process\n\nThe brew deployment is completely automated, and only requires a trigger to [`Release on Homebrew`](https://github.com/httpie/cli/actions/workflows/release-brew.yml) action\nfrom the release manager.\n\nIf it is needed to be done manually, the following command can be used:\n\n```console\n$ brew bump-formula-pr httpie --version={TARGET_VERSION}\n```\n\nwhich will bump the formula, and create a PR against the package index.\n\n## Hacking\n\nMake your changes, test the formula through the [`Test Brew Package`](https://github.com/httpie/cli/actions/workflows/test-package-mac-brew.yml) action\nand then finally submit your patch to [`homebrew-core`](https://github.com/Homebrew/homebrew-core`)\n\n"
  },
  {
    "path": "docs/packaging/brew/httpie.rb",
    "content": "class Httpie < Formula\n  include Language::Python::Virtualenv\n\n  desc \"User-friendly cURL replacement (command-line HTTP client)\"\n  homepage \"https://httpie.io/\"\n  url \"https://files.pythonhosted.org/packages/32/85/bb095699be20cc98731261cb80884e9458178f8fef2a38273530ce77c0a5/httpie-3.1.0.tar.gz\"\n  sha256 \"2e4a2040b84a912e65c01fb34f7aafe88cad2a3af2da8c685ca65080f376feda\"\n  license \"BSD-3-Clause\"\n  head \"https://github.com/httpie/cli.git\", branch: \"master\"\n\n  bottle do\n    sha256 cellar: :any_skip_relocation, arm64_monterey: \"9bb6e8c1ef5ba8b019ddedd7e908dd2174da695351aa9a238dfb28b0f57ef005\"\n    sha256 cellar: :any_skip_relocation, arm64_big_sur:  \"47ffccd3241155d863e1b4f6259d538a34d42a0cdeed8152bda257ee607b51be\"\n    sha256 cellar: :any_skip_relocation, monterey:       \"dc4a04cb05a9cd1bfa6a632a0e4a21975905954af54ece41f9050c52474267be\"\n    sha256 cellar: :any_skip_relocation, big_sur:        \"ae469e37864e967e0fd99fba15a78e719dcb351b462f98f3843c78ed1473df6d\"\n    sha256 cellar: :any_skip_relocation, catalina:       \"291a3eaecb2a2cc845c1652686a9a14b21053d7e3a7d0115245b2150ca2e199e\"\n    sha256 cellar: :any_skip_relocation, x86_64_linux:   \"710836e27c44c8e3ad181d668f4a9f78c4cb4c355d7b148a397599a7cd42713d\"\n  end\n\n  depends_on \"python@3.10\"\n\n  resource \"certifi\" do\n    url \"https://files.pythonhosted.org/packages/6c/ae/d26450834f0acc9e3d1f74508da6df1551ceab6c2ce0766a593362d6d57f/certifi-2021.10.8.tar.gz\"\n    sha256 \"78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872\"\n  end\n\n  resource \"charset-normalizer\" do\n    url \"https://files.pythonhosted.org/packages/56/31/7bcaf657fafb3c6db8c787a865434290b726653c912085fbd371e9b92e1c/charset-normalizer-2.0.12.tar.gz\"\n    sha256 \"2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597\"\n  end\n\n  resource \"defusedxml\" do\n    url \"https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz\"\n    sha256 \"1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69\"\n  end\n\n  resource \"idna\" do\n    url \"https://files.pythonhosted.org/packages/62/08/e3fc7c8161090f742f504f40b1bccbfc544d4a4e09eb774bf40aafce5436/idna-3.3.tar.gz\"\n    sha256 \"9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d\"\n  end\n\n  resource \"multidict\" do\n    url \"https://files.pythonhosted.org/packages/fa/a7/71c253cdb8a1528802bac7503bf82fe674367e4055b09c28846fdfa4ab90/multidict-6.0.2.tar.gz\"\n    sha256 \"5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013\"\n  end\n\n  resource \"Pygments\" do\n    url \"https://files.pythonhosted.org/packages/94/9c/cb656d06950268155f46d4f6ce25d7ffc51a0da47eadf1b164bbf23b718b/Pygments-2.11.2.tar.gz\"\n    sha256 \"4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a\"\n  end\n\n  resource \"PySocks\" do\n    url \"https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz\"\n    sha256 \"3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0\"\n  end\n\n  resource \"requests\" do\n    url \"https://files.pythonhosted.org/packages/60/f3/26ff3767f099b73e0efa138a9998da67890793bfa475d8278f84a30fec77/requests-2.27.1.tar.gz\"\n    sha256 \"68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61\"\n  end\n\n  resource \"requests-toolbelt\" do\n    url \"https://files.pythonhosted.org/packages/28/30/7bf7e5071081f761766d46820e52f4b16c8a08fef02d2eb4682ca7534310/requests-toolbelt-0.9.1.tar.gz\"\n    sha256 \"968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0\"\n  end\n\n  resource \"urllib3\" do\n    url \"https://files.pythonhosted.org/packages/b0/b1/7bbf5181f8e3258efae31702f5eab87d8a74a72a0aa78bc8c08c1466e243/urllib3-1.26.8.tar.gz\"\n    sha256 \"0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c\"\n  end\n\n  def install\n    virtualenv_install_with_resources\n  end\n\n  test do\n    assert_match version.to_s, shell_output(\"#{bin}/httpie --version\")\n    assert_match version.to_s, shell_output(\"#{bin}/https --version\")\n    assert_match version.to_s, shell_output(\"#{bin}/http --version\")\n\n    raw_url = \"https://raw.githubusercontent.com/Homebrew/homebrew-core/HEAD/Formula/httpie.rb\"\n    assert_match \"PYTHONPATH\", shell_output(\"#{bin}/http --ignore-stdin #{raw_url}\")\n  end\nend\n"
  },
  {
    "path": "docs/packaging/brew/update.sh",
    "content": "#!/bin/bash\n\nset -xe\n\nrm -f httpie.rb\nhttp --download https://raw.githubusercontent.com/Homebrew/homebrew-core/master/Formula/httpie.rb\n"
  },
  {
    "path": "docs/packaging/linux-arch/PKGBUILD",
    "content": "# Maintainer: Jelle van der Waa <jelle@archlinux.org>\n# Maintainer: daurnimator <daurnimator@archlinux.org>\n# Contributor: Daniel Micay <danielmicay@gmail.com>\n# Contributor: Thomas Weißschuh <thomas_weissschuh lavabit com>\n\npkgname=httpie\npkgver=2.6.0\npkgrel=1\npkgdesc=\"human-friendly CLI HTTP client for the API era\"\nurl=\"https://github.com/httpie/cli\"\ndepends=('python-defusedxml'\n         'python-pygments'\n         'python-pysocks'\n         'python-requests'\n         'python-requests-toolbelt'\n         'python-charset-normalizer')\nmakedepends=('python-setuptools')\ncheckdepends=('python-pytest'\n              'python-pytest-httpbin'\n              'python-responses')\nconflicts=(python-httpie)\nreplaces=(python-httpie python2-httpie)\nlicense=('BSD')\narch=('any')\nsource=($pkgname-$pkgver.tar.gz::\"https://github.com/httpie/cli/archive/$pkgver.tar.gz\")\nsha256sums=('3bcd9a8cb2b11299da12d3af36c095c6d4b665e41c395898a07f1ae4d99fc14a')\n\nbuild() {\n  cd $pkgname-$pkgver\n  python3 setup.py build\n}\n\npackage() {\n  cd $pkgname-$pkgver\n  install -Dm644 LICENSE \"$pkgdir/usr/share/licenses/httpie/LICENSE\"\n  python3 setup.py install --root=\"$pkgdir\" --optimize=1\n\n  # Fix upstream, include them in MANIFEST.in and use data_files in setup.py to install them automatically\n  # TODO: add zsh support\n  install -Dm644 extras/httpie-completion.bash \"$pkgdir\"/usr/share/bash-completion/completions/http\n  install -Dm644 extras/httpie-completion.fish \"$pkgdir\"/usr/share/fish/vendor_completions.d/http.fish\n}\n\ncheck() {\n  cd $pkgname-$pkgver\n  PYTHONDONTWRITEBYTECODE=1 pytest tests\n}\n"
  },
  {
    "path": "docs/packaging/linux-arch/README.md",
    "content": "# HTTPie on Arch Linux, and derived\n\nWelcome to the documentation about **packaging HTTPie for Arch Linux**.\n\n- If you do not know HTTPie, have a look [here](https://httpie.io/cli).\n- If you are looking for HTTPie installation or upgrade instructions on Arch Linux, then you can find them on [that page](https://httpie.io/docs#arch-linux).\n- If you are looking for technical information about the HTTPie packaging on Arch Linux, then you are in a good place.\n\n## About\n\nThis document contains technical details, where we describe how to create a patch for the latest HTTPie version for Arch Linux. They apply to Arch-derived distributions as well, like ArcoLinux, EndeavourOS, Artix Linux, etc.\nWe will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.\n\n## Overall process\n\nNote: Sending patches downstream does not seem easy. We failed to find where is located the package file on <https://gitlab.archlinux.org>. So we are relying on the last maintainer, daurnimator, and it works pretty well so far.\n\nCheck <https://archlinux.org/packages/community/any/httpie/> and if the version is outdated, simply [report it](https://archlinux.org/packages/community/any/httpie/flag/).\n\n## Hacking\n\nLeft blank on purpose, we will fill that section when we will have access to the downstream repository.\n"
  },
  {
    "path": "docs/packaging/linux-centos/README.md",
    "content": "# HTTPie on CentOS, RHEL, and derived\n\nWelcome to the documentation about **packaging HTTPie for CentOS and RHEL**.\n\n- If you do not know HTTPie, have a look [here](https://httpie.io/cli).\n- If you are looking for HTTPie installation or upgrade instructions on CentOS, then you can find them on [that page](https://httpie.io/docs#centos-and-rhel).\n- If you are looking for technical information about the HTTPie packaging on CentOS, then you are in a good place.\n\n## About\n\nThis document contains technical details, where we describe how to create a patch for the latest HTTPie version for CentOS. They apply to RHEL as well, and any RHEL-derived distributions like ClearOS, Oracle Linux, etc.\nWe will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.\n\nThe current maintainer is [Mikel Olasagasti](https://github.com/kaxero).\n\n## Overall process\n\nSame as [Fedora](../linux-fedora/README.md#overall-process).\n\n## Q/A with Mikel\n\nQ: What should we do to help seeing a new version on CentOS?\n\nA: When a new release is published Miro and I get notified by [release-monitoring](https://release-monitoring.org/project/1337/), that fills a BugZilla ticket reporting a new version being available.\n\nThe system also tries to create a simple patch to update the spec file, but in the case of CentOS it needs some manual revision. For example for 2.5.0 `defuxedxml` dep is required. Maybe with CentOS-9 and some new macros that are available now in Fedora it can be automated same way. But even the bump can be automated, maintainers should check for license changes, new binaries/docs/ and so on.\n"
  },
  {
    "path": "docs/packaging/linux-debian/README.md",
    "content": "# HTTPie on Debian, Ubuntu, and derived\n\nWelcome to the documentation about **packaging HTTPie for Debian GNU/Linux**.\n\n- If you do not know HTTPie, have a look [here](https://httpie.io/cli).\n- If you are looking for HTTPie installation or upgrade instructions on Debian GNU/Linux, then you can find them on [that page](https://httpie.io/docs#debian-and-ubuntu).\n- If you are looking for technical information about the HTTPie packaging on Debian GNU/Linux, then you are in a good place.\n\n## About\n\nThis document contains technical details, where we describe how to create a patch for the latest HTTPie version for Debian GNU/Linux. They apply to Ubuntu as well, and any Debian-derived distributions like MX Linux, Linux Mint, deepin, Pop!_OS, KDE neon, Zorin OS, elementary OS, Kubuntu, Devuan, Linux Lite, Peppermint OS, Lubuntu, antiX, Xubuntu, etc.\nWe will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.\n\nWe create the standalone binaries (see this [for more details](../../../extras/packaging/linux/)) and package them with\n[FPM](https://github.com/jordansissel/fpm)'s `dir` mode. The core `http`/`https` commands don't have any dependencies, but the `httpie`\ncommand (due to the underlying `httpie cli plugins` interface) explicitly depends to the system Python (through `python3`/`python3-pip`).\n\n## Overall process\n\nThe [`Release as Standalone Linux Binary`](https://github.com/httpie/cli/actions/workflows/release-linux-standalone.yml) will be automatically\ntriggered when a new release is created, and it will submit the `.deb` package as a release asset.\n\nFor making that asset available for all debian users, the release manager needs to go to the [`httpie/debian.httpie.io`](https://github.com/httpie/debian.httpie.io) repo\nand trigger the [`Update Index`](https://github.com/httpie/debian.httpie.io/actions/workflows/update-index.yml) action. It will automatically\nscrape all new debian packages from the release assets, properly update the indexes and create a new PR ([an example](https://github.com/httpie/debian.httpie.io/pull/1))\nwhich then will become active when merged.\n"
  },
  {
    "path": "docs/packaging/linux-fedora/README.md",
    "content": "# HTTPie on Fedora\n\nWelcome to the documentation about **packaging HTTPie for Fedora**.\n\n- If you do not know HTTPie, have a look [here](https://httpie.io/cli).\n- If you are looking for HTTPie installation or upgrade instructions on Fedora, then you can find them on [that page](https://httpie.io/docs#fedora).\n- If you are looking for technical information about the HTTPie packaging on Fedora, then you are in a good place.\n\n## About\n\nThis document contains technical details, where we describe how to create a patch for the latest HTTPie version for Fedora.\nWe will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.\n\nThe current maintainer is [Miro Hrončok](https://github.com/hroncok).\n\n## Overall process\n\nWe added the [.packit.yaml](https://github.com/httpie/cli/blob/master/.packit.yaml) local file.\nIt unlocks real-time Fedora checks on pull requests and new releases.\n\nSo there is nothing to do on our side: `Packit` will see the new release and open a pull request [there](https://src.fedoraproject.org/rpms/httpie). Then, the Fedora maintainer will review and merge.\n\nIt is also possible to follow [user feedbacks](https://bodhi.fedoraproject.org/updates/?packages=httpie) for all builds.\n\n## Q/A with Miro\n\nQ: What would the command to install the latest stable version look like?\n\nA: Assuming the latest stable version is already propagated to Fedora:\n\n```bash\n# Note that yum is an alias to dnf.\n$ sudo dnf install httpie\n```\n\nQ: Will dnf/yum upgrade then update to the latest?\n\nA: Yes, assuming the same as above.\n\nQ: Are new versions backported automatically?\n\nA: No. The process is:\n\n1. A new HTTPie release is created on Github.\n2. A pull request for Fedora `rawhide` (the development version of Fedora, currently Fedora 36) is created.\n3. A Fedora packager (usually Miro) sanity checks the pull request and merges, builds. HTTPie is updated in `rawhide` within 24 hours (sometimes more, for unrelated issues).\n4. A Fedora packager decides whether the upgrade is suitable for stable Fedora releases (currently 35, 34, 33), if so, merges the changes there.\n5. (if the above is yes) The new version of HTTPie lands in `updates-testing` repo where it waits for user feedback and lands within ~1 week for broad availability.\n"
  },
  {
    "path": "docs/packaging/linux-fedora/httpie.spec.txt",
    "content": "Name:           httpie\nVersion:        3.1.0\nRelease:        1%{?dist}\nSummary:        A Curl-like tool for humans\n\nLicense:        BSD\nURL:            https://httpie.org/\nSource0:        https://github.com/httpie/cli/archive/%{version}/%{name}-%{version}.tar.gz\n\nBuildArch:      noarch\n\nBuildRequires:  python3-devel\nBuildRequires:  pyproject-rpm-macros\n\nBuildRequires:  help2man\n\n%description\nHTTPie is a CLI HTTP utility built out of frustration with existing tools. The\ngoal is to make CLI interaction with HTTP-based services as human-friendly as\npossible.\n\nHTTPie does so by providing an http command that allows for issuing arbitrary\nHTTP requests using a simple and natural syntax and displaying colorized\nresponses.\n\n\n%prep\n%autosetup -p1\n\n\n%generate_buildrequires\n%pyproject_buildrequires -rx test\n\n\n%build\n%pyproject_wheel\n\n\n%install\n%pyproject_install\n%pyproject_save_files httpie\n\n# Bash completion\nmkdir -p %{buildroot}%{_datadir}/bash-completion/completions\ncp -a extras/httpie-completion.bash %{buildroot}%{_datadir}/bash-completion/completions/http\nln -s ./http %{buildroot}%{_datadir}/bash-completion/completions/https\n\n# Fish completion\nmkdir -p %{buildroot}%{_datadir}/fish/vendor_completions.d/\ncp -a extras/httpie-completion.fish %{buildroot}%{_datadir}/fish/vendor_completions.d/http.fish\nln -s ./http.fish %{buildroot}%{_datadir}/fish/vendor_completions.d/https.fish\n\n\n# Generate man pages for everything\nexport PYTHONPATH=%{buildroot}%{python3_sitelib}\nmkdir -p %{buildroot}%{_mandir}/man1\nhelp2man %{buildroot}%{_bindir}/http > %{buildroot}%{_mandir}/man1/http.1\nhelp2man %{buildroot}%{_bindir}/https > %{buildroot}%{_mandir}/man1/https.1\nhelp2man %{buildroot}%{_bindir}/httpie > %{buildroot}%{_mandir}/man1/httpie.1\n\n\n%check\n%pytest -v\n\n\n%files -f %{pyproject_files}\n%doc README.md\n%license LICENSE\n%{_bindir}/http\n%{_bindir}/https\n%{_bindir}/httpie\n%{_mandir}/man1/http.1*\n%{_mandir}/man1/https.1*\n%{_mandir}/man1/httpie.1*\n# we co-own the entire directory structures for bash/fish completion to avoid a dependency\n%{_datadir}/bash-completion/\n%{_datadir}/fish/\n\n\n%changelog\n* Tue Mar 08 2022 Miro Hrončok <mhroncok@redhat.com> - 3.1.0-1\n- Update to 3.1.0\n- Fixes: rhbz#2061597\n\n* Mon Jan 24 2022 Miro Hrončok <mhroncok@redhat.com> - 3.0.2-1\n- Update to 3.0.2\n- Fixes: rhbz#2044572\n\n* Mon Jan 24 2022 Miro Hrončok <mhroncok@redhat.com> - 3.0.1-1\n- Update to 3.0.1\n- Fixes: rhbz#2044058\n\n* Fri Jan 21 2022 Miro Hrončok <mhroncok@redhat.com> - 3.0.0-1\n- Update to 3.0.0\n- Fixes: rhbz#2043680\n\n* Thu Jan 20 2022 Fedora Release Engineering <releng@fedoraproject.org> - 2.6.0-2\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild\n\n* Fri Oct 15 2021 Miro Hrončok <mhroncok@redhat.com> - 2.6.0-1\n- Update to 2.6.0\n- Fixes: rhbz#2014022\n\n* Tue Sep 07 2021 Miro Hrončok <mhroncok@redhat.com> - 2.5.0-1\n- Update to 2.5.0\n- Fixes: rhbz#2001693\n\n* Thu Jul 22 2021 Fedora Release Engineering <releng@fedoraproject.org> - 2.4.0-4\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild\n\n* Fri Jun 04 2021 Python Maint <python-maint@redhat.com> - 2.4.0-3\n- Rebuilt for Python 3.10\n\n* Thu May 27 2021 Miro Hrončok <mhroncok@redhat.com> - 2.4.0-2\n- Add Bash and Fish completion\n- Fixes rhbz#1834441\n- Run tests on build time\n\n* Wed Mar 24 2021 Mikel Olasagasti Uranga <mikel@olasagasti.info> - 2.4.0-1\n- Update to 2.4.0\n- Use pypi_source macro\n\n* Tue Jan 26 2021 Fedora Release Engineering <releng@fedoraproject.org> - 2.3.0-3\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild\n\n* Thu Jan 21 2021 Nils Philippsen <nils@tiptoe.de> - 2.3.0-2\n- use macros for Python dependencies\n- add missing Python dependencies needed for running help2man\n- remove manual Python dependencies\n- discard stderr when running help2man\n\n* Thu Dec 24 2020 Nils Philippsen <nils@tiptoe.de> - 2.3.0-1\n- version 2.3.0\n- Python 2 is no more\n- use %%autosetup and Python build macros\n- remove EL7-isms\n- explicitly require sed for building\n\n* Tue Jul 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 1.0.3-4\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild\n\n* Tue May 26 2020 Miro Hrončok <mhroncok@redhat.com> - 1.0.3-3\n- Rebuilt for Python 3.9\n\n* Wed Jan 29 2020 Fedora Release Engineering <releng@fedoraproject.org> - 1.0.3-2\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild\n\n* Mon Sep 30 2019 Rick Elrod <relrod@redhat.com> - 1.0.3-1\n- Latest upstream\n\n* Mon Aug 19 2019 Miro Hrončok <mhroncok@redhat.com> - 0.9.4-15\n- Rebuilt for Python 3.8\n\n* Thu Jul 25 2019 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.4-14\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild\n\n* Fri Feb 01 2019 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.4-13\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild\n\n* Fri Jul 13 2018 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.4-12\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild\n\n* Tue Jun 19 2018 Miro Hrončok <mhroncok@redhat.com> - 0.9.4-11\n- Rebuilt for Python 3.7\n\n* Wed Feb 07 2018 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.4-10\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild\n\n* Wed Jul 26 2017 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.4-9\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild\n\n* Fri Mar 10 2017 Ralph Bean <rbean@redhat.com> - 0.9.4-8\n- Fix help2man usage with python3.\n  https://bugzilla.redhat.com/show_bug.cgi?id=1430733\n\n* Mon Feb 27 2017 Ralph Bean <rbean@redhat.com> - 0.9.4-7\n- Fix missing Requires.  https://bugzilla.redhat.com/show_bug.cgi?id=1417730\n\n* Fri Feb 10 2017 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.4-6\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild\n\n* Mon Jan 2 2017 Ricky Elrod <relrod@redhat.com> - 0.9.4-5\n- Add missing Obsoletes.\n\n* Mon Jan 2 2017 Ricky Elrod <relrod@redhat.com> - 0.9.4-4\n- Nuke python-version-specific subpackages. Just use py3 if we can.\n\n* Mon Dec 19 2016 Miro Hrončok <mhroncok@redhat.com> - 0.9.4-3\n- Rebuild for Python 3.6\n\n* Tue Jul 19 2016 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.4-2\n- https://fedoraproject.org/wiki/Changes/Automatic_Provides_for_Python_RPM_Packages\n\n* Tue Jul 05 2016 Ricky Elrod <relrod@redhat.com> - 0.9.4-1\n- Update to latest upstream.\n\n* Fri Jun 03 2016 Ricky Elrod <relrod@redhat.com> - 0.9.3-4\n- Add proper Obsoletes for rhbz#1329226.\n\n* Wed Feb 03 2016 Fedora Release Engineering <releng@fedoraproject.org> - 0.9.3-3\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild\n\n* Mon Jan 04 2016 Ralph Bean <rbean@redhat.com> - 0.9.3-2\n- Modernize python macros and subpackaging.\n- Move LICENSE to %%license macro.\n- Make python3 the default on modern Fedora.\n\n* Mon Jan 04 2016 Ralph Bean <rbean@redhat.com> - 0.9.3-1\n- new version\n\n* Tue Nov 10 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.2-3\n- Rebuilt for https://fedoraproject.org/wiki/Changes/python3.5\n\n* Wed Jun 17 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.2-2\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild\n\n* Thu Mar 26 2015 Ricky Elrod <relrod@redhat.com> - 0.9.2-1\n- Latest upstream release.\n\n* Sat Jun 07 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.8.0-3\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild\n\n* Wed May 28 2014 Kalev Lember <kalevlember@gmail.com> - 0.8.0-2\n- Rebuilt for https://fedoraproject.org/wiki/Changes/Python_3.4\n\n* Fri Jan 31 2014 Ricky Elrod <codeblock@fedoraproject.org> - 0.8.0-1\n- Latest upstream release.\n\n* Fri Oct 4 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.7.2-2\n- Add in patch to work without having python-requests 2.0.0.\n\n* Sat Sep 28 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.7.2-1\n- Latest upstream release.\n\n* Thu Sep 5 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.6.0-7\n- Only try building the manpage on Fedora, since RHEL's help2man doesn't\n  have the --no-discard-stderr flag.\n\n* Thu Sep 5 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.6.0-6\n- Loosen the requirement on python-pygments.\n\n* Sat Aug 03 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.6.0-5\n- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild\n\n* Tue Jul 2 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.6.0-4\n- python-requests 1.2.3 exists in rawhide now.\n\n* Sun Jun 30 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.6.0-3\n- Patch to use python-requests 1.1.0 for now.\n\n* Sat Jun 29 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.6.0-2\n- Update to latest upstream release.\n\n* Mon Apr 29 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.5.0-2\n- Fix changelog messup.\n\n* Mon Apr 29 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.5.0-1\n- Update to latest upstream release.\n\n* Mon Apr 8 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.4.1-3\n- Fix manpage generation by exporting PYTHONPATH.\n\n* Tue Mar 26 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.4.1-2\n- Include Python3 support, and fix other review blockers.\n\n* Mon Mar 11 2013 Ricky Elrod <codeblock@fedoraproject.org> - 0.4.1-1\n- Update to latest upstream release\n\n* Thu Jul 19 2012 Ricky Elrod <codeblock@fedoraproject.org> - 0.2.5-1\n- Initial build.\n"
  },
  {
    "path": "docs/packaging/linux-fedora/update.sh",
    "content": "#!/bin/bash\n\nset -xe\n\nrm -f httpie.spec.txt\nhttps --download src.fedoraproject.org/rpms/httpie/raw/rawhide/f/httpie.spec -o httpie.spec.txt\n"
  },
  {
    "path": "docs/packaging/mac-ports/Portfile",
    "content": "# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8::et:sw=4:ts=4:sts=4\n\nPortSystem          1.0\nPortGroup           github 1.0\nPortGroup           python 1.0\n\ngithub.setup        httpie httpie 2.6.0\n\nmaintainers         {g5pw @g5pw} openmaintainer\ncategories          net\ndescription         Modern, user-friendly command-line HTTP client for the API era\nlong_description    HTTPie (pronounced aych-tee-tee-pie) is a command line HTTP \\\n                    client. Its goal is to make CLI interaction with web \\\n                    services as human-friendly as possible. It provides a simple \\\n                    http command that allows for sending arbitrary HTTP requests \\\n                    using a simple and natural syntax, and displays colorized \\\n                    responses. HTTPie can be used for testing, debugging, and \\\n                    generally interacting with HTTP servers.\nplatforms           darwin\nlicense             BSD\nhomepage            https://httpie.io/\n\nvariant python37 conflicts python36 python38 python39 python310 description \"Use Python 3.7\" {}\nvariant python38 conflicts python36 python37 python39 python310 description \"Use Python 3.8\" {}\nvariant python39 conflicts python36 python37 python38 python310 description \"Use Python 3.9\" {}\nvariant python310 conflicts python36 python37 python38 python39 description \"Use Python 3.10\" {}\n\nif {[variant_isset python37]} {\n    python.default_version 37\n} elseif {[variant_isset python39]} {\n    python.default_version 39\n} elseif {[variant_isset python310]} {\n    python.default_version 310\n} else {\n    default_variants +python38\n    python.default_version 38\n}\n\ndepends_lib-append  port:py${python.version}-requests \\\n                    port:py${python.version}-requests-toolbelt \\\n                    port:py${python.version}-pygments \\\n                    port:py${python.version}-socks \\\n                    port:py${python.version}-charset-normalizer \\\n                    port:py${python.version}-defusedxml\n\nchecksums           rmd160  07b1d1592da1c505ed3ee4ef3b6056215e16e9ff \\\n                    sha256  63cf104bf3552305c68a74f16494a90172b15296610a875e17918e5e36373c0b \\\n                    size    1133491\n\npython.link_binaries_suffix\n"
  },
  {
    "path": "docs/packaging/mac-ports/README.md",
    "content": "# HTTPie on MacPorts\n\nWelcome to the documentation about **packaging HTTPie for MacPorts**.\n\n- If you do not know HTTPie, have a look [here](https://httpie.io/cli).\n- If you are looking for HTTPie installation or upgrade instructions on MacPorts, then you can find them on [that page](https://httpie.io/docs#macports).\n- If you are looking for technical information about the HTTPie packaging on MacPorts, then you are in a good place.\n\n## About\n\nThis document contains technical details, where we describe how to create a patch for the latest HTTPie version for MacPorts.\nWe will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.\n\n## Overall process\n\nOpen a pull request to update the [downstream file](https://github.com/macports/macports-ports/blob/master/net/httpie/Portfile) ([example](https://github.com/macports/macports-ports/pull/12583)).\n\n- Here is how to calculate the size and checksums (replace `2.5.0` with the correct version):\n\n  ```bash\n  # Download the archive\n  $ wget https://api.github.com/repos/httpie/cli/tarball/2.5.0\n\n  # Size\n  $ stat --printf=\"%s\\n\" 2.5.0\n  1105185\n\n  # Checksums\n  $ openssl dgst -rmd160 2.5.0\n  RIPEMD160(2.5.0)= 88d227d52199c232c0ddf704a219d1781b1e77ee\n  $ openssl dgst -sha256 2.5.0\n  SHA256(2.5.0)= 00c4b7bbe7f65abe1473f37b39d9d9f8f53f44069a430ad143a404c01c2179fc\n  ```\n\n- The commit message must be `httpie: update to XXX`.\n- The commit must be signed-off (`git commit -s`).\n\n## Hacking\n\n:construction: Work in progress.\n"
  },
  {
    "path": "docs/packaging/snapcraft/README.md",
    "content": "# HTTPie on Snapcraft\n\nWelcome to the documentation about **packaging HTTPie for Snapcraft**.\n\n- If you do not know HTTPie, have a look [here](https://httpie.io/cli).\n- If you are looking for HTTPie installation or upgrade instructions on Snapcraft, then you can find them on [that page](https://httpie.io/docs#snapcraft-linux) ([that one](https://httpie.io/docs#snapcraft-macos) for macOS).\n- If you are looking for technical information about the HTTPie packaging on Snapcraft, then you are in a good place.\n\n## About\n\nThis document contains technical details, where we describe how to create a patch for the latest HTTPie version for Snapcraft. They apply to Snapcraft on Linux, macOS, and Windows.\nWe will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.\n\n## Overall process\n\nTrigger the [`Release on Snap`](https://github.com/httpie/cli/actions/workflows/release-snap.yml) action, which will\ncreate a snap package for HTTPie and then push it to Snap Store in the following channels:\n\n- Edge\n- Beta\n- Candidate\n- Stable\n\nIf a push to any of them fail, all the release tasks for the following channels will be cancelled so that the\nrelease manager can look into the underlying cause.\n\n## Hacking\n\nLaunch the docker image:\n\n```bash\ndocker pull ubuntu/latest\ndocker run -it --rm ubuntu/latest\n```\n\nFrom inside the container:\n\n```bash\n# Clone\ngit clone --depth=1 https://github.com/httpie/cli.git\ncd httpie\n\n# Build\nexport SNAPCRAFT_BUILD_ENVIRONMENT_CPU=8\nexport SNAPCRAFT_BUILD_ENVIRONMENT_MEMORY=16G\nsnapcraft --debug\n\n# Install\nsudo snap install --dangerous httpie_XXX_amd64.snap\n\n# Test\nhttpie.http --version\nhttpie.https --version\n# Auto-aliases cannot be tested when installing a snap outside the store.\n# http --version\n# https --version\n\n# Remove\nsudo snap remove httpie\n```\n"
  },
  {
    "path": "docs/packaging/windows-chocolatey/README.md",
    "content": "# HTTPie on Chocolatey\n\nWelcome to the documentation about **packaging HTTPie for Chocolatey**.\n\n- If you do not know HTTPie, have a look [here](https://httpie.io/cli).\n- If you are looking for HTTPie installation or upgrade instructions on Chocolatey, then you can find them on [that page](https://httpie.io/docs#chocolatey).\n- If you are looking for technical information about the HTTPie packaging on Chocolatey, then you are in a good place.\n\n## About\n\nThis document contains technical details, where we describe how to create a patch for the latest HTTPie version for Chocolatey.\nWe will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.\n\n## Overall process\n\nAfter having successfully [built and tested](#hacking) the package, either trigger the\n[`Release on Chocolatey`](https://github.com/httpie/cli/actions/workflows/release-choco.yml) action\nto push it to the `Chocolatey` store or use the CLI:\n\n```bash\n# Replace 2.5.0 with the correct version\nchoco push httpie.2.5.0.nupkg -s https://push.chocolatey.org/ --api-key=API_KEY\n```\n\nBe aware that it might take multiple days until the release is approved, sine it goes through multiple\nsets of reviews (some of them are done manually).\n\n## Hacking\n\n```bash\n# Clone\ngit clone --depth=1 https://github.com/httpie/cli.git\ncd httpie/docs/packaging/windows-chocolatey\n\n# Build\nchoco pack\n\n# Check metadata\nchoco info httpie -s .\n\n# Install\nchoco install httpie -y -dv -s \"'.;https://community.chocolatey.org/api/v2/'\"\n\n# Test\nhttp --version\nhttps --version\n\n# Remove\nchoco uninstall -y httpie\n```\n"
  },
  {
    "path": "docs/packaging/windows-chocolatey/httpie.nuspec",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<package xmlns=\"http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd\">\n\t<metadata>\n\t\t<id>httpie</id>\n\t\t<version>3.2.2</version>\n\t\t<summary>Modern, user-friendly command-line HTTP client for the API era</summary>\n\t\t<description>\nHTTPie *aitch-tee-tee-pie* is a user-friendly command-line HTTP client for the API era.\nIt comes with JSON support, syntax highlighting, persistent sessions, wget-like downloads, plugins, and more.\n\nThe project's goal is to make CLI interaction with web services as human-friendly as possible. HTTPie is designed for testing, debugging, and generally interacting with APIs and HTTP servers.\nThe `http` and `https` commands allow for creating and sending arbitrary HTTP requests. They use simple and natural syntax and provide formatted and colorized output.\n\nMain features:\n\n- Built-in JSON support\n- Colorized and formatted terminal output\n- Sensible defaults for the API era\n- Persistent sessions\n- Forms and file uploads\n- HTTPS, proxies, and authentication support\n- Support for arbitrary request data and headers\n- Wget-like downloads\n- Extensions API\n- Expressive and intuitive syntax\n- Linux, macOS, Windows, and FreeBSD support\n- All that and more in 2 simple commands: `http` + `https`\n\t\t</description>\n\t\t<title>HTTPie</title>\n\t\t<authors>HTTPie</authors>\n\t\t<owners>jakubroztocil</owners>\n\t\t<copyright>2012-2022 Jakub Roztocil</copyright>\n\t\t<licenseUrl>https://raw.githubusercontent.com/httpie/cli/master/LICENSE</licenseUrl>\n\t\t<iconUrl>https://pie-assets.s3.eu-central-1.amazonaws.com/LogoIcons/GB.png</iconUrl>\n\t\t<requireLicenseAcceptance>false</requireLicenseAcceptance>\n\t\t<releaseNotes>See the [changelog](https://github.com/httpie/cli/releases/tag/3.2.2).</releaseNotes>\n\t\t<tags>httpie http https rest api client curl python ssl cli foss oss url</tags>\n\t\t<projectUrl>https://httpie.io</projectUrl>\n\t\t<packageSourceUrl>https://github.com/httpie/cli/tree/master/docs/packaging/windows-chocolatey</packageSourceUrl>\n\t\t<projectSourceUrl>https://github.com/httpie/cli</projectSourceUrl>\n\t\t<docsUrl>https://httpie.io/docs</docsUrl>\n\t\t<bugTrackerUrl>https://github.com/httpie/cli/issues</bugTrackerUrl>\n\t\t<dependencies>\n\t\t\t<dependency id=\"python3\" version=\"3.7\" />\n\t\t</dependencies>\n\t</metadata>\n\t<files>\n\t\t<file src=\"tools\\**\" target=\"tools\" />\n\t</files>\n</package>\n"
  },
  {
    "path": "docs/packaging/windows-chocolatey/tools/chocolateyinstall.ps1",
    "content": "﻿$ErrorActionPreference = 'Stop';\npy -m pip install $env:ChocolateyPackageName==$env:ChocolateyPackageVersion --disable-pip-version-check\n"
  },
  {
    "path": "docs/packaging/windows-chocolatey/tools/chocolateyuninstall.ps1",
    "content": "﻿$ErrorActionPreference = 'Stop';\npy -m pip uninstall -y $env:ChocolateyPackageName --disable-pip-version-check\n"
  },
  {
    "path": "extras/httpie-completion.bash",
    "content": "_http_complete() {\n    local cur_word=${COMP_WORDS[COMP_CWORD]}\n    local prev_word=${COMP_WORDS[COMP_CWORD - 1]}\n\n    if [[ \"$cur_word\" == -* ]]; then\n        _http_complete_options \"$cur_word\"\n    fi\n}\n\ncomplete -o default -F _http_complete http httpie.http httpie.https https\n\n_http_complete_options() {\n    local cur_word=$1\n    local options=\"-j --json -f --form --pretty -s --style -p --print\n    -v --verbose -h --headers -b --body -S --stream -o --output -d --download\n    -c --continue --session --session-read-only -a --auth --auth-type --proxy\n    --follow --verify --cert --cert-key --timeout --check-status --ignore-stdin\n    --help --version --traceback --debug --raw\"\n    COMPREPLY=( $( compgen -W \"$options\" -- \"$cur_word\" ) )\n}\n"
  },
  {
    "path": "extras/httpie-completion.fish",
    "content": "function __fish_httpie_styles\n    printf '%s\\n' abap algol algol_nu arduino auto autumn borland bw colorful default emacs friendly fruity gruvbox-dark gruvbox-light igor inkpot lovelace manni material monokai murphy native paraiso-dark paraiso-light pastie perldoc pie pie-dark pie-light rainbow_dash rrt sas solarized solarized-dark solarized-light stata stata-dark stata-light tango trac vim vs xcode zenburn\nend\n\nfunction __fish_httpie_mime_types\n    test -r /usr/share/mime/types && cat /usr/share/mime/types\nend\n\nfunction __fish_httpie_print_args\n    set -l arg (commandline -t)\n    string match -qe H \"$arg\" || echo -e $arg\"H\\trequest headers\"\n    string match -qe B \"$arg\" || echo -e $arg\"B\\trequest body\"\n    string match -qe h \"$arg\" || echo -e $arg\"h\\tresponse headers\"\n    string match -qe b \"$arg\" || echo -e $arg\"b\\tresponse body\"\n    string match -qe m \"$arg\" || echo -e $arg\"m\\tresponse metadata\"\nend\n\nfunction __fish_httpie_auth_types\n    echo -e \"basic\\tBasic HTTP auth\"\n    echo -e \"digest\\tDigest HTTP auth\"\n    echo -e \"bearer\\tBearer HTTP Auth\"\nend\n\nfunction __fish_http_verify_options\n    echo -e \"yes\\tEnable cert verification\"\n    echo -e \"no\\tDisable cert verification\"\nend\n\n\n# Predefined Content Types\n\ncomplete -c http -s j -l json         -d 'Data items are serialized as a JSON object'\ncomplete -c http -s f -l form         -d 'Data items are serialized as form fields'\ncomplete -c http      -l multipart    -d 'Always sends a multipart/form-data request'\ncomplete -c http      -l boundary  -x -d 'Custom boundary string for multipart/form-data requests'\ncomplete -c http      -l raw       -x -d 'Pass raw request data without extra processing'\n\n\n# Content Processing Options\n\ncomplete -c http -s x -l compress -d 'Content compressed with Deflate algorithm'\n\n\n# Output Processing\n\ncomplete -c http      -l pretty           -xa \"all colors format none\"     -d 'Controls output processing'\ncomplete -c http -s s -l style            -xa \"(__fish_httpie_styles)\"     -d 'Output coloring style'\ncomplete -c http      -l unsorted                                          -d 'Disables all sorting while formatting output'\ncomplete -c http      -l sorted                                            -d 'Re-enables all sorting options while formatting output'\ncomplete -c http      -l response-charset -x                               -d 'Override the response encoding'\ncomplete -c http      -l response-mime    -xa \"(__fish_httpie_mime_types)\" -d 'Override the response mime type for coloring and formatting'\ncomplete -c http      -l format-options   -x                               -d 'Controls output formatting'\n\n\n# Output Options\n\ncomplete -c http -s p -l print         -xa \"(__fish_httpie_print_args)\" -d 'String specifying what the output should contain'\ncomplete -c http -s h -l headers                                        -d 'Print only the response headers'\ncomplete -c http -s m -l meta                                           -d 'Print only the response metadata'\ncomplete -c http -s b -l body                                           -d 'Print only the response body'\ncomplete -c http -s v -l verbose                                        -d 'Print the whole request as well as the response'\ncomplete -c http      -l all                                            -d 'Show any intermediary requests/responses'\ncomplete -c http -s S -l stream                                         -d 'Always stream the response body by line'\ncomplete -c http -s o -l output        -F                               -d 'Save output to FILE'\ncomplete -c http -s d -l download                                       -d 'Download a file'\ncomplete -c http -s c -l continue                                       -d 'Resume an interrupted download'\ncomplete -c http -s q -l quiet                                          -d 'Do not print to stdout or stderr'\n\n\n# Sessions\n\ncomplete -c http -l session           -F -d 'Create, or reuse and update a session'\ncomplete -c http -l session-read-only -F -d 'Create or read a session without updating it'\n\n\n# Authentication\n\ncomplete -c http -s a -l auth         -x                               -d 'Username and password for authentication'\ncomplete -c http -s A -l auth-type    -xa \"(__fish_httpie_auth_types)\" -d 'The authentication mechanism to be used'\ncomplete -c http      -l ignore-netrc                                  -d 'Ignore credentials from .netrc'\n\n\n# Network\n\ncomplete -c http      -l offline          -d 'Build the request and print it but don\\'t actually send it'\ncomplete -c http      -l proxy         -x -d 'String mapping protocol to the URL of the proxy'\ncomplete -c http -s F -l follow           -d 'Follow 30x Location redirects'\ncomplete -c http      -l max-redirects -x -d 'Set maximum number of redirects'\ncomplete -c http      -l max-headers   -x -d 'Maximum number of response headers to be read before giving up'\ncomplete -c http      -l timeout       -x -d 'Connection timeout in seconds'\ncomplete -c http      -l check-status     -d 'Error with non-200 HTTP status code'\ncomplete -c http      -l path-as-is       -d 'Bypass dot segment URL squashing'\ncomplete -c http      -l chunked          -d 'Enable streaming via chunked transfer encoding'\n\n\n# SSL\n\ncomplete -c http -l verify        -xa \"(__fish_http_verify_options)\" -d 'Enable/disable cert verification'\ncomplete -c http -l ssl           -x                                 -d 'Desired protocol version to use'\ncomplete -c http -l ciphers       -x                                 -d 'String in the OpenSSL cipher list format'\ncomplete -c http -l cert          -F                                 -d 'Client side SSL certificate'\ncomplete -c http -l cert-key      -F                                 -d 'Private key to use with SSL'\ncomplete -c http -l cert-key-pass -x                                 -d 'Passphrase for the given private key'\n\n\n# Troubleshooting\n\ncomplete -c http -s I -l ignore-stdin      -d 'Do not attempt to read stdin'\ncomplete -c http      -l help              -d 'Show help'\ncomplete -c http      -l manual            -d 'Show the full manual'\ncomplete -c http      -l version           -d 'Show version'\ncomplete -c http      -l traceback         -d 'Prints exception traceback should one occur'\ncomplete -c http      -l default-scheme -x -d 'The default scheme to use'\ncomplete -c http      -l debug             -d 'Show debugging output'\n\n\n# Alias for https to http\n\ncomplete -c https -w http\n"
  },
  {
    "path": "extras/man/http.1",
    "content": ".\\\" This file is auto-generated from the parser declaration in httpie/cli/definition.py by extras/scripts/generate_man_pages.py.\n.TH http 1 \"2024-11-01\" \"HTTPie 3.2.4\" \"HTTPie Manual\"\n.SH NAME\nhttp\n.SH SYNOPSIS\nhttp [METHOD] URL [REQUEST_ITEM ...]\n\n.SH DESCRIPTION\nHTTPie: modern, user-friendly command-line HTTP client for the API era. <https://httpie.io>\n.SH Positional arguments\n\nThese arguments come after any flags and in the order they are listed here.\nOnly URL is required.\n\n.IP \"\\fB\\,METHOD\\/\\fR\"\n\n\nThe HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).\n\nThis argument can be omitted in which case HTTPie will use POST if there\nis some data to be sent, otherwise GET:\n\n    $ http example.org               # => GET\n    $ http example.org hello=world   # => POST\n\n\n\n.IP \"\\fB\\,URL\\/\\fR\"\n\n\nThe request URL. Scheme defaults to \\[aq]http://\\[aq] if the URL\ndoes not include one. (You can override this with: \\fB\\,--default-scheme\\/\\fR=http/https)\n\nYou can also use a shorthand for localhost\n\n    $ http :3000                    # => http://localhost:3000\n    $ http :/foo                    # => http://localhost/foo\n\n\n\n.IP \"\\fB\\,REQUEST_ITEM\\/\\fR\"\n\n\nOptional key-value pairs to be included in the request. The separator used\ndetermines the type:\n\n\\[aq]:\\[aq] HTTP headers:\n\n    Referer:https://httpie.io  Cookie:foo=bar  User-Agent:bacon/1.0\n\n\\[aq]==\\[aq] URL parameters to be appended to the request URI:\n\n    search==httpie\n\n\\[aq]=\\[aq] Data fields to be serialized into a JSON object (with \\fB\\,--json\\/\\fR, \\fB\\,-j\\/\\fR)\n    or form data (with \\fB\\,--form\\/\\fR, \\fB\\,-f\\/\\fR):\n\n    name=HTTPie  language=Python  description=\\[aq]CLI HTTP client\\[aq]\n\n\\[aq]:=\\[aq] Non-string JSON data fields (only with \\fB\\,--json\\/\\fR, \\fB\\,-j\\/\\fR):\n\n    awesome:=true  amount:=42  colors:=\\[aq][\\[dq]red\\[dq], \\[dq]green\\[dq], \\[dq]blue\\[dq]]\\[aq]\n\n\\[aq]@\\[aq] Form file fields (only with \\fB\\,--form\\/\\fR or \\fB\\,--multipart\\/\\fR):\n\n    cv@\\(ti/Documents/CV.pdf\n    cv@\\[aq]\\(ti/Documents/CV.pdf;type=application/pdf\\[aq]\n\n\\[aq]=@\\[aq] A data field like \\[aq]=\\[aq], but takes a file path and embeds its content:\n\n    essay=@Documents/essay.txt\n\n\\[aq]:=@\\[aq] A raw JSON field like \\[aq]:=\\[aq], but takes a file path and embeds its content:\n\n    package:=@./package.json\n\nYou can use a backslash to escape a colliding separator in the field name:\n\n    field-name-with\\e:colon=value\n\n\n\n.PP\n.SH Predefined content types\n.IP \"\\fB\\,--json\\/\\fR, \\fB\\,-j\\/\\fR\"\n\n\n(default) Data items from the command line are serialized as a JSON object.\nThe Content-Type and Accept headers are set to application/json\n(if not specified).\n\n\n\n.IP \"\\fB\\,--form\\/\\fR, \\fB\\,-f\\/\\fR\"\n\n\nData items from the command line are serialized as form fields.\n\nThe Content-Type is set to application/x-www-form-urlencoded (if not\nspecified). The presence of any file fields results in a\nmultipart/form-data request.\n\n\n\n.IP \"\\fB\\,--multipart\\/\\fR\"\n\n\nSimilar to \\fB\\,--form\\/\\fR, but always sends a multipart/form-data request (i.e., even without files).\n\n\n.IP \"\\fB\\,--boundary\\/\\fR\"\n\n\nSpecify a custom boundary string for multipart/form-data requests. Only has effect only together with \\fB\\,--form\\/\\fR.\n\n\n.IP \"\\fB\\,--raw\\/\\fR\"\n\n\nThis option allows you to pass raw request data without extra processing\n(as opposed to the structured request items syntax):\n\n    $ http \\fB\\,--raw\\/\\fR=\\[aq]data\\[aq] pie.dev/post\n\nYou can achieve the same by piping the data via stdin:\n\n    $ echo data | http pie.dev/post\n\nOr have HTTPie load the raw data from a file:\n\n    $ http pie.dev/post @data.txt\n\n\n\n\n.PP\n.SH Content processing options\n.IP \"\\fB\\,--compress\\/\\fR, \\fB\\,-x\\/\\fR\"\n\n\nContent compressed (encoded) with Deflate algorithm.\nThe Content-Encoding header is set to deflate.\n\nCompression is skipped if it appears that compression ratio is\nnegative. Compression can be forced by repeating the argument.\n\n\n\n.PP\n.SH Output processing\n.IP \"\\fB\\,--pretty\\/\\fR\"\n\n\nControls output processing. The value can be \\[dq]none\\[dq] to not prettify\nthe output (default for redirected output), \\[dq]all\\[dq] to apply both colors\nand formatting (default for terminal output), \\[dq]colors\\[dq], or \\[dq]format\\[dq].\n\n\n\n.IP \"\\fB\\,--style\\/\\fR, \\fB\\,-s\\/\\fR \\fI\\,STYLE\\/\\fR\"\n\n\nOutput coloring style (default is \\[dq]auto\\[dq]). It can be one of:\n\n    auto, pie, pie-dark, pie-light, solarized\n\n\nFor finding out all available styles in your system, try:\n\n$ http \\fB\\,--style\\/\\fR\n\nThe \\[dq]auto\\[dq] style follows your terminal\\[aq]s ANSI color styles.\nFor non-auto styles to work properly, please make sure that the\n$TERM environment variable is set to \\[dq]xterm-256color\\[dq] or similar\n(e.g., via `export TERM=xterm-256color\\[aq] in your \\(ti/.bashrc).\n\n.IP \"\\fB\\,--unsorted\\/\\fR\"\n\n\nDisables all sorting while formatting output. It is a shortcut for:\n\n    \\fB\\,--format-options\\/\\fR=headers.sort:false,json.sort_keys:false\n\n\n\n.IP \"\\fB\\,--sorted\\/\\fR\"\n\n\nRe-enables all sorting options while formatting output. It is a shortcut for:\n\n    \\fB\\,--format-options\\/\\fR=headers.sort:true,json.sort_keys:true\n\n\n\n.IP \"\\fB\\,--response-charset\\/\\fR \\fI\\,ENCODING\\/\\fR\"\n\n\nOverride the response encoding for terminal display purposes, e.g.:\n\n    \\fB\\,--response-charset\\/\\fR=utf8\n    \\fB\\,--response-charset\\/\\fR=big5\n\n\n\n.IP \"\\fB\\,--response-mime\\/\\fR \\fI\\,MIME_TYPE\\/\\fR\"\n\n\nOverride the response mime type for coloring and formatting for the terminal, e.g.:\n\n    \\fB\\,--response-mime\\/\\fR=application/json\n    \\fB\\,--response-mime\\/\\fR=text/xml\n\n\n\n.IP \"\\fB\\,--format-options\\/\\fR\"\n\n\nControls output formatting. Only relevant when formatting is enabled\nthrough (explicit or implied) \\fB\\,--pretty\\/\\fR=all or \\fB\\,--pretty\\/\\fR=format.\nThe following are the default options:\n\n    headers.sort:true\n    json.format:true\n    json.indent:4\n    json.sort_keys:true\n    xml.format:true\n    xml.indent:2\n\nYou may use this option multiple times, as well as specify multiple\ncomma-separated options at the same time. For example, this modifies the\nsettings to disable the sorting of JSON keys, and sets the indent size to 2:\n\n    \\fB\\,--format-options\\/\\fR json.sort_keys:false,json.indent:2\n\nThis is something you will typically put into your config file.\n\n\n\n.PP\n.SH Output options\n.IP \"\\fB\\,--print\\/\\fR, \\fB\\,-p\\/\\fR \\fI\\,WHAT\\/\\fR\"\n\n\nString specifying what the output should contain:\n\n    \\[aq]H\\[aq] request headers\n    \\[aq]B\\[aq] request body\n    \\[aq]h\\[aq] response headers\n    \\[aq]b\\[aq] response body\n    \\[aq]m\\[aq] response metadata\n\nThe default behaviour is \\[aq]hb\\[aq] (i.e., the response\nheaders and body is printed), if standard output is not redirected.\nIf the output is piped to another program or to a file, then only the\nresponse body is printed by default.\n\n\n\n.IP \"\\fB\\,--headers\\/\\fR, \\fB\\,-h\\/\\fR\"\n\n\nPrint only the response headers. Shortcut for \\fB\\,--print\\/\\fR=h.\n\n\n\n.IP \"\\fB\\,--meta\\/\\fR, \\fB\\,-m\\/\\fR\"\n\n\nPrint only the response metadata. Shortcut for \\fB\\,--print\\/\\fR=m.\n\n\n\n.IP \"\\fB\\,--body\\/\\fR, \\fB\\,-b\\/\\fR\"\n\n\nPrint only the response body. Shortcut for \\fB\\,--print\\/\\fR=b.\n\n\n\n.IP \"\\fB\\,--verbose\\/\\fR, \\fB\\,-v\\/\\fR\"\n\n\nVerbose output. For the level one (with single `\\fB\\,-v\\/\\fR`/`\\fB\\,--verbose\\/\\fR`), print\nthe whole request as well as the response. Also print any intermediary\nrequests/responses (such as redirects). For the second level and higher,\nprint these as well as the response metadata.\n\nLevel one is a shortcut for: \\fB\\,--all\\/\\fR \\fB\\,--print\\/\\fR=BHbh\nLevel two is a shortcut for: \\fB\\,--all\\/\\fR \\fB\\,--print\\/\\fR=BHbhm\n\n\n.IP \"\\fB\\,--all\\/\\fR\"\n\n\nBy default, only the final request/response is shown. Use this flag to show\nany intermediary requests/responses as well. Intermediary requests include\nfollowed redirects (with \\fB\\,--follow\\/\\fR), the first unauthorized request when\nDigest auth is used (\\fB\\,--auth\\/\\fR=digest), etc.\n\n\n\n.IP \"\\fB\\,--stream\\/\\fR, \\fB\\,-S\\/\\fR\"\n\n\nAlways stream the response body by line, i.e., behave like `tail \\fB\\,-f\\/\\fR\\[aq].\n\nWithout \\fB\\,--stream\\/\\fR and with \\fB\\,--pretty\\/\\fR (either set or implied),\nHTTPie fetches the whole response before it outputs the processed data.\n\nSet this option when you want to continuously display a prettified\nlong-lived response, such as one from the Twitter streaming API.\n\nIt is useful also without \\fB\\,--pretty\\/\\fR: It ensures that the output is flushed\nmore often and in smaller chunks.\n\n\n\n.IP \"\\fB\\,--output\\/\\fR, \\fB\\,-o\\/\\fR \\fI\\,FILE\\/\\fR\"\n\n\nSave output to FILE instead of stdout. If \\fB\\,--download\\/\\fR is also set, then only\nthe response body is saved to FILE. Other parts of the HTTP exchange are\nprinted to stderr.\n\n\n\n.IP \"\\fB\\,--download\\/\\fR, \\fB\\,-d\\/\\fR\"\n\n\nDo not print the response body to stdout. Rather, download it and store it\nin a file. The filename is guessed unless specified with \\fB\\,--output\\/\\fR\n[filename]. This action is similar to the default behaviour of wget.\n\n\n\n.IP \"\\fB\\,--continue\\/\\fR, \\fB\\,-c\\/\\fR\"\n\n\nResume an interrupted download. Note that the \\fB\\,--output\\/\\fR option needs to be\nspecified as well.\n\n\n\n.IP \"\\fB\\,--quiet\\/\\fR, \\fB\\,-q\\/\\fR\"\n\n\nDo not print to stdout or stderr, except for errors and warnings when provided once.\nProvide twice to suppress warnings as well.\nstdout is still redirected if \\fB\\,--output\\/\\fR is specified.\nFlag doesn\\[aq]t affect behaviour of download beyond not printing to terminal.\n\n\n\n.PP\n.SH Sessions\n.IP \"\\fB\\,--session\\/\\fR \\fI\\,SESSION_NAME_OR_PATH\\/\\fR\"\n\n\nCreate, or reuse and update a session. Within a session, custom headers,\nauth credential, as well as any cookies sent by the server persist between\nrequests.\n\nSession files are stored in:\n\n    [HTTPIE_CONFIG_DIR]/<HOST>/<SESSION_NAME>.json.\n\nSee the following page to find out your default HTTPIE_CONFIG_DIR:\n\n    https://httpie.io/docs/cli/config-file-directory\n\n\n.IP \"\\fB\\,--session-read-only\\/\\fR \\fI\\,SESSION_NAME_OR_PATH\\/\\fR\"\n\n\nCreate or read a session without updating it form the request/response\nexchange.\n\n\n\n.PP\n.SH Authentication\n.IP \"\\fB\\,--auth\\/\\fR, \\fB\\,-a\\/\\fR \\fI\\,USER[:PASS] | TOKEN\\/\\fR\"\n\n\nFor username/password based authentication mechanisms (e.g\nbasic auth or digest auth) if only the username is provided\n(\\fB\\,-a\\/\\fR username), HTTPie will prompt for the password.\n\n\n\n.IP \"\\fB\\,--auth-type\\/\\fR, \\fB\\,-A\\/\\fR\"\n\n\nThe authentication mechanism to be used. Defaults to \\[dq]basic\\[dq].\n\n\\[dq]basic\\[dq]: Basic HTTP auth\n\n\\[dq]digest\\[dq]: Digest HTTP auth\n\n\\[dq]bearer\\[dq]: Bearer HTTP Auth\n\nTo see all available auth types on your system, including ones installed via plugins, run:\n\n$ http \\fB\\,--auth-type\\/\\fR\n\n.IP \"\\fB\\,--ignore-netrc\\/\\fR\"\n\n\nIgnore credentials from .netrc.\n\n\n.PP\n.SH Network\n.IP \"\\fB\\,--offline\\/\\fR\"\n\n\nBuild the request and print it but don\\(gat actually send it.\n\n\n.IP \"\\fB\\,--proxy\\/\\fR \\fI\\,PROTOCOL:PROXY_URL\\/\\fR\"\n\n\nString mapping protocol to the URL of the proxy\n(e.g. http:http://foo.bar:3128). You can specify multiple proxies with\ndifferent protocols. The environment variables $ALL_PROXY, $HTTP_PROXY,\nand $HTTPS_proxy are supported as well.\n\n\n\n.IP \"\\fB\\,--follow\\/\\fR, \\fB\\,-F\\/\\fR\"\n\n\nFollow 30x Location redirects.\n\n\n.IP \"\\fB\\,--max-redirects\\/\\fR\"\n\n\nBy default, requests have a limit of 30 redirects (works with \\fB\\,--follow\\/\\fR).\n\n\n\n.IP \"\\fB\\,--max-headers\\/\\fR\"\n\n\nThe maximum number of response headers to be read before giving up (default 0, i.e., no limit).\n\n\n.IP \"\\fB\\,--timeout\\/\\fR \\fI\\,SECONDS\\/\\fR\"\n\n\nThe connection timeout of the request in seconds.\nThe default value is 0, i.e., there is no timeout limit.\nThis is not a time limit on the entire response download;\nrather, an error is reported if the server has not issued a response for\ntimeout seconds (more precisely, if no bytes have been received on\nthe underlying socket for timeout seconds).\n\n\n\n.IP \"\\fB\\,--check-status\\/\\fR\"\n\n\nBy default, HTTPie exits with 0 when no network or other fatal errors\noccur. This flag instructs HTTPie to also check the HTTP status code and\nexit with an error if the status indicates one.\n\nWhen the server replies with a 4xx (Client Error) or 5xx (Server Error)\nstatus code, HTTPie exits with 4 or 5 respectively. If the response is a\n3xx (Redirect) and \\fB\\,--follow\\/\\fR hasn\\[aq]t been set, then the exit status is 3.\nAlso an error message is written to stderr if stdout is redirected.\n\n\n\n.IP \"\\fB\\,--path-as-is\\/\\fR\"\n\n\nBypass dot segment (/../ or /./) URL squashing.\n\n\n.IP \"\\fB\\,--chunked\\/\\fR\"\n\n\nEnable streaming via chunked transfer encoding. The Transfer-Encoding header is set to chunked.\n\n\n.PP\n.SH SSL\n.IP \"\\fB\\,--verify\\/\\fR\"\n\n\nSet to \\[dq]no\\[dq] (or \\[dq]false\\[dq]) to skip checking the host\\[aq]s SSL certificate.\nDefaults to \\[dq]yes\\[dq] (\\[dq]true\\[dq]). You can also pass the path to a CA_BUNDLE file\nfor private certs. (Or you can set the REQUESTS_CA_BUNDLE environment\nvariable instead.)\n\n\n.IP \"\\fB\\,--ssl\\/\\fR\"\n\n\nThe desired protocol version to use. This will default to\nSSL v2.3 which will negotiate the highest protocol that both\nthe server and your installation of OpenSSL support. Available protocols\nmay vary depending on OpenSSL installation (only the supported ones\nare shown here).\n\n\n\n.IP \"\\fB\\,--ciphers\\/\\fR\"\n\n\n\nA string in the OpenSSL cipher list format.\n\n\nSee `http \\fB\\,--help\\/\\fR` for the default ciphers list on you system.\n\n\n\n\n\n.IP \"\\fB\\,--cert\\/\\fR\"\n\n\nYou can specify a local cert to use as client side SSL certificate.\nThis file may either contain both private key and certificate or you may\nspecify \\fB\\,--cert-key\\/\\fR separately.\n\n\n\n.IP \"\\fB\\,--cert-key\\/\\fR\"\n\n\nThe private key to use with SSL. Only needed if \\fB\\,--cert\\/\\fR is given and the\ncertificate file does not contain the private key.\n\n\n\n.IP \"\\fB\\,--cert-key-pass\\/\\fR\"\n\n\nThe passphrase to be used to with the given private key. Only needed if \\fB\\,--cert-key\\/\\fR\nis given and the key file requires a passphrase.\nIf not provided, you\\(gall be prompted interactively.\n\n\n.PP\n.SH Troubleshooting\n.IP \"\\fB\\,--ignore-stdin\\/\\fR, \\fB\\,-I\\/\\fR\"\n\n\nDo not attempt to read stdin\n\n\n.IP \"\\fB\\,--help\\/\\fR\"\n\n\nShow this help message and exit.\n\n\n.IP \"\\fB\\,--manual\\/\\fR\"\n\n\nShow the full manual.\n\n\n.IP \"\\fB\\,--version\\/\\fR\"\n\n\nShow version and exit.\n\n\n.IP \"\\fB\\,--traceback\\/\\fR\"\n\n\nPrints the exception traceback should one occur.\n\n\n.IP \"\\fB\\,--default-scheme\\/\\fR\"\n\n\nThe default scheme to use if not specified in the URL.\n\n\n.IP \"\\fB\\,--debug\\/\\fR\"\n\n\nPrints the exception traceback should one occur, as well as other\ninformation useful for debugging HTTPie itself and for reporting bugs.\n\n\n\n.PP\n.SH SEE ALSO\n\nFor every \\fB\\,--OPTION\\/\\fR there is also a \\fB\\,--no-OPTION\\/\\fR that reverts OPTION\nto its default value.\n\nSuggestions and bug reports are greatly appreciated:\nhttps://github.com/httpie/cli/issues"
  },
  {
    "path": "extras/man/httpie.1",
    "content": ".\\\" This file is auto-generated from the parser declaration in httpie/manager/cli.py by extras/scripts/generate_man_pages.py.\n.TH httpie 1 \"2024-11-01\" \"HTTPie 3.2.4\" \"HTTPie Manual\"\n.SH NAME\nhttpie\n.SH SYNOPSIS\nhttpie\n.SH DESCRIPTION\n\nManaging interface for the HTTPie itself. <https://httpie.io/docs#manager>\n\nBe aware that you might be looking for http/https commands for sending\nHTTP requests. This command is only available for managing the HTTTPie\nplugins and the configuration around it.\n\n\nIf you are looking for the man pages of http/https commands, try one of the following:\n    $ man http\n    $ man https\n\n\n.SH httpie cli export-args\nExport available options for the CLI\n.IP \"\\fB\\,-f\\/\\fR, \\fB\\,--format\\/\\fR\"\n\nFormat to export in.\n\n.PP\n.SH httpie cli check-updates\nCheck for updates\n.PP\n.SH httpie cli sessions upgrade\nUpgrade the given HTTPie session with the latest layout. A list of changes between different session versions can be found in the official documentation.\n.IP \"\\fB\\,HOSTNAME\\/\\fR\"\n\nThe host this session belongs.\n\n.IP \"\\fB\\,SESSION_NAME_OR_PATH\\/\\fR\"\n\nThe name or the path for the session that will be upgraded.\n\n.IP \"\\fB\\,--bind-cookies\\/\\fR\"\n\nBind domainless cookies to the host that session belongs.\n\n.PP\n.SH httpie cli sessions upgrade-all\nUpgrade all named sessions with the latest layout. A list of changes between different session versions can be found in the official documentation.\n.IP \"\\fB\\,--bind-cookies\\/\\fR\"\n\nBind domainless cookies to the host that session belongs.\n\n.PP\n.SH httpie cli plugins install\nInstall the given targets from PyPI or from a local paths.\n.IP \"\\fB\\,TARGET\\/\\fR\"\n\ntargets to install\n\n.PP\n.SH httpie cli plugins upgrade\nUpgrade the given plugins\n.IP \"\\fB\\,TARGET\\/\\fR\"\n\ntargets to upgrade\n\n.PP\n.SH httpie cli plugins uninstall\nUninstall the given HTTPie plugins.\n.IP \"\\fB\\,TARGET\\/\\fR\"\n\ntargets to install\n\n.PP\n.SH httpie cli plugins list\nList all installed HTTPie plugins.\n.PP\n.SH httpie plugins install\nInstall the given targets from PyPI or from a local paths.\n.IP \"\\fB\\,TARGET\\/\\fR\"\n\ntargets to install\n\n.PP\n.SH httpie plugins upgrade\nUpgrade the given plugins\n.IP \"\\fB\\,TARGET\\/\\fR\"\n\ntargets to upgrade\n\n.PP\n.SH httpie plugins uninstall\nUninstall the given HTTPie plugins.\n.IP \"\\fB\\,TARGET\\/\\fR\"\n\ntargets to install\n\n.PP\n.SH httpie plugins list\nList all installed HTTPie plugins.\n.PP"
  },
  {
    "path": "extras/man/https.1",
    "content": ".\\\" This file is auto-generated from the parser declaration in httpie/cli/definition.py by extras/scripts/generate_man_pages.py.\n.TH https 1 \"2024-11-01\" \"HTTPie 3.2.4\" \"HTTPie Manual\"\n.SH NAME\nhttps\n.SH SYNOPSIS\nhttps [METHOD] URL [REQUEST_ITEM ...]\n\n.SH DESCRIPTION\nHTTPie: modern, user-friendly command-line HTTP client for the API era. <https://httpie.io>\n.SH Positional arguments\n\nThese arguments come after any flags and in the order they are listed here.\nOnly URL is required.\n\n.IP \"\\fB\\,METHOD\\/\\fR\"\n\n\nThe HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).\n\nThis argument can be omitted in which case HTTPie will use POST if there\nis some data to be sent, otherwise GET:\n\n    $ http example.org               # => GET\n    $ http example.org hello=world   # => POST\n\n\n\n.IP \"\\fB\\,URL\\/\\fR\"\n\n\nThe request URL. Scheme defaults to \\[aq]http://\\[aq] if the URL\ndoes not include one. (You can override this with: \\fB\\,--default-scheme\\/\\fR=http/https)\n\nYou can also use a shorthand for localhost\n\n    $ http :3000                    # => http://localhost:3000\n    $ http :/foo                    # => http://localhost/foo\n\n\n\n.IP \"\\fB\\,REQUEST_ITEM\\/\\fR\"\n\n\nOptional key-value pairs to be included in the request. The separator used\ndetermines the type:\n\n\\[aq]:\\[aq] HTTP headers:\n\n    Referer:https://httpie.io  Cookie:foo=bar  User-Agent:bacon/1.0\n\n\\[aq]==\\[aq] URL parameters to be appended to the request URI:\n\n    search==httpie\n\n\\[aq]=\\[aq] Data fields to be serialized into a JSON object (with \\fB\\,--json\\/\\fR, \\fB\\,-j\\/\\fR)\n    or form data (with \\fB\\,--form\\/\\fR, \\fB\\,-f\\/\\fR):\n\n    name=HTTPie  language=Python  description=\\[aq]CLI HTTP client\\[aq]\n\n\\[aq]:=\\[aq] Non-string JSON data fields (only with \\fB\\,--json\\/\\fR, \\fB\\,-j\\/\\fR):\n\n    awesome:=true  amount:=42  colors:=\\[aq][\\[dq]red\\[dq], \\[dq]green\\[dq], \\[dq]blue\\[dq]]\\[aq]\n\n\\[aq]@\\[aq] Form file fields (only with \\fB\\,--form\\/\\fR or \\fB\\,--multipart\\/\\fR):\n\n    cv@\\(ti/Documents/CV.pdf\n    cv@\\[aq]\\(ti/Documents/CV.pdf;type=application/pdf\\[aq]\n\n\\[aq]=@\\[aq] A data field like \\[aq]=\\[aq], but takes a file path and embeds its content:\n\n    essay=@Documents/essay.txt\n\n\\[aq]:=@\\[aq] A raw JSON field like \\[aq]:=\\[aq], but takes a file path and embeds its content:\n\n    package:=@./package.json\n\nYou can use a backslash to escape a colliding separator in the field name:\n\n    field-name-with\\e:colon=value\n\n\n\n.PP\n.SH Predefined content types\n.IP \"\\fB\\,--json\\/\\fR, \\fB\\,-j\\/\\fR\"\n\n\n(default) Data items from the command line are serialized as a JSON object.\nThe Content-Type and Accept headers are set to application/json\n(if not specified).\n\n\n\n.IP \"\\fB\\,--form\\/\\fR, \\fB\\,-f\\/\\fR\"\n\n\nData items from the command line are serialized as form fields.\n\nThe Content-Type is set to application/x-www-form-urlencoded (if not\nspecified). The presence of any file fields results in a\nmultipart/form-data request.\n\n\n\n.IP \"\\fB\\,--multipart\\/\\fR\"\n\n\nSimilar to \\fB\\,--form\\/\\fR, but always sends a multipart/form-data request (i.e., even without files).\n\n\n.IP \"\\fB\\,--boundary\\/\\fR\"\n\n\nSpecify a custom boundary string for multipart/form-data requests. Only has effect only together with \\fB\\,--form\\/\\fR.\n\n\n.IP \"\\fB\\,--raw\\/\\fR\"\n\n\nThis option allows you to pass raw request data without extra processing\n(as opposed to the structured request items syntax):\n\n    $ http \\fB\\,--raw\\/\\fR=\\[aq]data\\[aq] pie.dev/post\n\nYou can achieve the same by piping the data via stdin:\n\n    $ echo data | http pie.dev/post\n\nOr have HTTPie load the raw data from a file:\n\n    $ http pie.dev/post @data.txt\n\n\n\n\n.PP\n.SH Content processing options\n.IP \"\\fB\\,--compress\\/\\fR, \\fB\\,-x\\/\\fR\"\n\n\nContent compressed (encoded) with Deflate algorithm.\nThe Content-Encoding header is set to deflate.\n\nCompression is skipped if it appears that compression ratio is\nnegative. Compression can be forced by repeating the argument.\n\n\n\n.PP\n.SH Output processing\n.IP \"\\fB\\,--pretty\\/\\fR\"\n\n\nControls output processing. The value can be \\[dq]none\\[dq] to not prettify\nthe output (default for redirected output), \\[dq]all\\[dq] to apply both colors\nand formatting (default for terminal output), \\[dq]colors\\[dq], or \\[dq]format\\[dq].\n\n\n\n.IP \"\\fB\\,--style\\/\\fR, \\fB\\,-s\\/\\fR \\fI\\,STYLE\\/\\fR\"\n\n\nOutput coloring style (default is \\[dq]auto\\[dq]). It can be one of:\n\n    auto, pie, pie-dark, pie-light, solarized\n\n\nFor finding out all available styles in your system, try:\n\n$ http \\fB\\,--style\\/\\fR\n\nThe \\[dq]auto\\[dq] style follows your terminal\\[aq]s ANSI color styles.\nFor non-auto styles to work properly, please make sure that the\n$TERM environment variable is set to \\[dq]xterm-256color\\[dq] or similar\n(e.g., via `export TERM=xterm-256color\\[aq] in your \\(ti/.bashrc).\n\n.IP \"\\fB\\,--unsorted\\/\\fR\"\n\n\nDisables all sorting while formatting output. It is a shortcut for:\n\n    \\fB\\,--format-options\\/\\fR=headers.sort:false,json.sort_keys:false\n\n\n\n.IP \"\\fB\\,--sorted\\/\\fR\"\n\n\nRe-enables all sorting options while formatting output. It is a shortcut for:\n\n    \\fB\\,--format-options\\/\\fR=headers.sort:true,json.sort_keys:true\n\n\n\n.IP \"\\fB\\,--response-charset\\/\\fR \\fI\\,ENCODING\\/\\fR\"\n\n\nOverride the response encoding for terminal display purposes, e.g.:\n\n    \\fB\\,--response-charset\\/\\fR=utf8\n    \\fB\\,--response-charset\\/\\fR=big5\n\n\n\n.IP \"\\fB\\,--response-mime\\/\\fR \\fI\\,MIME_TYPE\\/\\fR\"\n\n\nOverride the response mime type for coloring and formatting for the terminal, e.g.:\n\n    \\fB\\,--response-mime\\/\\fR=application/json\n    \\fB\\,--response-mime\\/\\fR=text/xml\n\n\n\n.IP \"\\fB\\,--format-options\\/\\fR\"\n\n\nControls output formatting. Only relevant when formatting is enabled\nthrough (explicit or implied) \\fB\\,--pretty\\/\\fR=all or \\fB\\,--pretty\\/\\fR=format.\nThe following are the default options:\n\n    headers.sort:true\n    json.format:true\n    json.indent:4\n    json.sort_keys:true\n    xml.format:true\n    xml.indent:2\n\nYou may use this option multiple times, as well as specify multiple\ncomma-separated options at the same time. For example, this modifies the\nsettings to disable the sorting of JSON keys, and sets the indent size to 2:\n\n    \\fB\\,--format-options\\/\\fR json.sort_keys:false,json.indent:2\n\nThis is something you will typically put into your config file.\n\n\n\n.PP\n.SH Output options\n.IP \"\\fB\\,--print\\/\\fR, \\fB\\,-p\\/\\fR \\fI\\,WHAT\\/\\fR\"\n\n\nString specifying what the output should contain:\n\n    \\[aq]H\\[aq] request headers\n    \\[aq]B\\[aq] request body\n    \\[aq]h\\[aq] response headers\n    \\[aq]b\\[aq] response body\n    \\[aq]m\\[aq] response metadata\n\nThe default behaviour is \\[aq]hb\\[aq] (i.e., the response\nheaders and body is printed), if standard output is not redirected.\nIf the output is piped to another program or to a file, then only the\nresponse body is printed by default.\n\n\n\n.IP \"\\fB\\,--headers\\/\\fR, \\fB\\,-h\\/\\fR\"\n\n\nPrint only the response headers. Shortcut for \\fB\\,--print\\/\\fR=h.\n\n\n\n.IP \"\\fB\\,--meta\\/\\fR, \\fB\\,-m\\/\\fR\"\n\n\nPrint only the response metadata. Shortcut for \\fB\\,--print\\/\\fR=m.\n\n\n\n.IP \"\\fB\\,--body\\/\\fR, \\fB\\,-b\\/\\fR\"\n\n\nPrint only the response body. Shortcut for \\fB\\,--print\\/\\fR=b.\n\n\n\n.IP \"\\fB\\,--verbose\\/\\fR, \\fB\\,-v\\/\\fR\"\n\n\nVerbose output. For the level one (with single `\\fB\\,-v\\/\\fR`/`\\fB\\,--verbose\\/\\fR`), print\nthe whole request as well as the response. Also print any intermediary\nrequests/responses (such as redirects). For the second level and higher,\nprint these as well as the response metadata.\n\nLevel one is a shortcut for: \\fB\\,--all\\/\\fR \\fB\\,--print\\/\\fR=BHbh\nLevel two is a shortcut for: \\fB\\,--all\\/\\fR \\fB\\,--print\\/\\fR=BHbhm\n\n\n.IP \"\\fB\\,--all\\/\\fR\"\n\n\nBy default, only the final request/response is shown. Use this flag to show\nany intermediary requests/responses as well. Intermediary requests include\nfollowed redirects (with \\fB\\,--follow\\/\\fR), the first unauthorized request when\nDigest auth is used (\\fB\\,--auth\\/\\fR=digest), etc.\n\n\n\n.IP \"\\fB\\,--stream\\/\\fR, \\fB\\,-S\\/\\fR\"\n\n\nAlways stream the response body by line, i.e., behave like `tail \\fB\\,-f\\/\\fR\\[aq].\n\nWithout \\fB\\,--stream\\/\\fR and with \\fB\\,--pretty\\/\\fR (either set or implied),\nHTTPie fetches the whole response before it outputs the processed data.\n\nSet this option when you want to continuously display a prettified\nlong-lived response, such as one from the Twitter streaming API.\n\nIt is useful also without \\fB\\,--pretty\\/\\fR: It ensures that the output is flushed\nmore often and in smaller chunks.\n\n\n\n.IP \"\\fB\\,--output\\/\\fR, \\fB\\,-o\\/\\fR \\fI\\,FILE\\/\\fR\"\n\n\nSave output to FILE instead of stdout. If \\fB\\,--download\\/\\fR is also set, then only\nthe response body is saved to FILE. Other parts of the HTTP exchange are\nprinted to stderr.\n\n\n\n.IP \"\\fB\\,--download\\/\\fR, \\fB\\,-d\\/\\fR\"\n\n\nDo not print the response body to stdout. Rather, download it and store it\nin a file. The filename is guessed unless specified with \\fB\\,--output\\/\\fR\n[filename]. This action is similar to the default behaviour of wget.\n\n\n\n.IP \"\\fB\\,--continue\\/\\fR, \\fB\\,-c\\/\\fR\"\n\n\nResume an interrupted download. Note that the \\fB\\,--output\\/\\fR option needs to be\nspecified as well.\n\n\n\n.IP \"\\fB\\,--quiet\\/\\fR, \\fB\\,-q\\/\\fR\"\n\n\nDo not print to stdout or stderr, except for errors and warnings when provided once.\nProvide twice to suppress warnings as well.\nstdout is still redirected if \\fB\\,--output\\/\\fR is specified.\nFlag doesn\\[aq]t affect behaviour of download beyond not printing to terminal.\n\n\n\n.PP\n.SH Sessions\n.IP \"\\fB\\,--session\\/\\fR \\fI\\,SESSION_NAME_OR_PATH\\/\\fR\"\n\n\nCreate, or reuse and update a session. Within a session, custom headers,\nauth credential, as well as any cookies sent by the server persist between\nrequests.\n\nSession files are stored in:\n\n    [HTTPIE_CONFIG_DIR]/<HOST>/<SESSION_NAME>.json.\n\nSee the following page to find out your default HTTPIE_CONFIG_DIR:\n\n    https://httpie.io/docs/cli/config-file-directory\n\n\n.IP \"\\fB\\,--session-read-only\\/\\fR \\fI\\,SESSION_NAME_OR_PATH\\/\\fR\"\n\n\nCreate or read a session without updating it form the request/response\nexchange.\n\n\n\n.PP\n.SH Authentication\n.IP \"\\fB\\,--auth\\/\\fR, \\fB\\,-a\\/\\fR \\fI\\,USER[:PASS] | TOKEN\\/\\fR\"\n\n\nFor username/password based authentication mechanisms (e.g\nbasic auth or digest auth) if only the username is provided\n(\\fB\\,-a\\/\\fR username), HTTPie will prompt for the password.\n\n\n\n.IP \"\\fB\\,--auth-type\\/\\fR, \\fB\\,-A\\/\\fR\"\n\n\nThe authentication mechanism to be used. Defaults to \\[dq]basic\\[dq].\n\n\\[dq]basic\\[dq]: Basic HTTP auth\n\n\\[dq]digest\\[dq]: Digest HTTP auth\n\n\\[dq]bearer\\[dq]: Bearer HTTP Auth\n\nTo see all available auth types on your system, including ones installed via plugins, run:\n\n$ http \\fB\\,--auth-type\\/\\fR\n\n.IP \"\\fB\\,--ignore-netrc\\/\\fR\"\n\n\nIgnore credentials from .netrc.\n\n\n.PP\n.SH Network\n.IP \"\\fB\\,--offline\\/\\fR\"\n\n\nBuild the request and print it but don\\(gat actually send it.\n\n\n.IP \"\\fB\\,--proxy\\/\\fR \\fI\\,PROTOCOL:PROXY_URL\\/\\fR\"\n\n\nString mapping protocol to the URL of the proxy\n(e.g. http:http://foo.bar:3128). You can specify multiple proxies with\ndifferent protocols. The environment variables $ALL_PROXY, $HTTP_PROXY,\nand $HTTPS_proxy are supported as well.\n\n\n\n.IP \"\\fB\\,--follow\\/\\fR, \\fB\\,-F\\/\\fR\"\n\n\nFollow 30x Location redirects.\n\n\n.IP \"\\fB\\,--max-redirects\\/\\fR\"\n\n\nBy default, requests have a limit of 30 redirects (works with \\fB\\,--follow\\/\\fR).\n\n\n\n.IP \"\\fB\\,--max-headers\\/\\fR\"\n\n\nThe maximum number of response headers to be read before giving up (default 0, i.e., no limit).\n\n\n.IP \"\\fB\\,--timeout\\/\\fR \\fI\\,SECONDS\\/\\fR\"\n\n\nThe connection timeout of the request in seconds.\nThe default value is 0, i.e., there is no timeout limit.\nThis is not a time limit on the entire response download;\nrather, an error is reported if the server has not issued a response for\ntimeout seconds (more precisely, if no bytes have been received on\nthe underlying socket for timeout seconds).\n\n\n\n.IP \"\\fB\\,--check-status\\/\\fR\"\n\n\nBy default, HTTPie exits with 0 when no network or other fatal errors\noccur. This flag instructs HTTPie to also check the HTTP status code and\nexit with an error if the status indicates one.\n\nWhen the server replies with a 4xx (Client Error) or 5xx (Server Error)\nstatus code, HTTPie exits with 4 or 5 respectively. If the response is a\n3xx (Redirect) and \\fB\\,--follow\\/\\fR hasn\\[aq]t been set, then the exit status is 3.\nAlso an error message is written to stderr if stdout is redirected.\n\n\n\n.IP \"\\fB\\,--path-as-is\\/\\fR\"\n\n\nBypass dot segment (/../ or /./) URL squashing.\n\n\n.IP \"\\fB\\,--chunked\\/\\fR\"\n\n\nEnable streaming via chunked transfer encoding. The Transfer-Encoding header is set to chunked.\n\n\n.PP\n.SH SSL\n.IP \"\\fB\\,--verify\\/\\fR\"\n\n\nSet to \\[dq]no\\[dq] (or \\[dq]false\\[dq]) to skip checking the host\\[aq]s SSL certificate.\nDefaults to \\[dq]yes\\[dq] (\\[dq]true\\[dq]). You can also pass the path to a CA_BUNDLE file\nfor private certs. (Or you can set the REQUESTS_CA_BUNDLE environment\nvariable instead.)\n\n\n.IP \"\\fB\\,--ssl\\/\\fR\"\n\n\nThe desired protocol version to use. This will default to\nSSL v2.3 which will negotiate the highest protocol that both\nthe server and your installation of OpenSSL support. Available protocols\nmay vary depending on OpenSSL installation (only the supported ones\nare shown here).\n\n\n\n.IP \"\\fB\\,--ciphers\\/\\fR\"\n\n\n\nA string in the OpenSSL cipher list format.\n\n\nSee `http \\fB\\,--help\\/\\fR` for the default ciphers list on you system.\n\n\n\n\n\n.IP \"\\fB\\,--cert\\/\\fR\"\n\n\nYou can specify a local cert to use as client side SSL certificate.\nThis file may either contain both private key and certificate or you may\nspecify \\fB\\,--cert-key\\/\\fR separately.\n\n\n\n.IP \"\\fB\\,--cert-key\\/\\fR\"\n\n\nThe private key to use with SSL. Only needed if \\fB\\,--cert\\/\\fR is given and the\ncertificate file does not contain the private key.\n\n\n\n.IP \"\\fB\\,--cert-key-pass\\/\\fR\"\n\n\nThe passphrase to be used to with the given private key. Only needed if \\fB\\,--cert-key\\/\\fR\nis given and the key file requires a passphrase.\nIf not provided, you\\(gall be prompted interactively.\n\n\n.PP\n.SH Troubleshooting\n.IP \"\\fB\\,--ignore-stdin\\/\\fR, \\fB\\,-I\\/\\fR\"\n\n\nDo not attempt to read stdin\n\n\n.IP \"\\fB\\,--help\\/\\fR\"\n\n\nShow this help message and exit.\n\n\n.IP \"\\fB\\,--manual\\/\\fR\"\n\n\nShow the full manual.\n\n\n.IP \"\\fB\\,--version\\/\\fR\"\n\n\nShow version and exit.\n\n\n.IP \"\\fB\\,--traceback\\/\\fR\"\n\n\nPrints the exception traceback should one occur.\n\n\n.IP \"\\fB\\,--default-scheme\\/\\fR\"\n\n\nThe default scheme to use if not specified in the URL.\n\n\n.IP \"\\fB\\,--debug\\/\\fR\"\n\n\nPrints the exception traceback should one occur, as well as other\ninformation useful for debugging HTTPie itself and for reporting bugs.\n\n\n\n.PP\n.SH SEE ALSO\n\nFor every \\fB\\,--OPTION\\/\\fR there is also a \\fB\\,--no-OPTION\\/\\fR that reverts OPTION\nto its default value.\n\nSuggestions and bug reports are greatly appreciated:\nhttps://github.com/httpie/cli/issues"
  },
  {
    "path": "extras/packaging/linux/Dockerfile",
    "content": "# Use the oldest (but still supported) Ubuntu as the base for PyInstaller\n# packages. This will prevent stuff like glibc from conflicting.\nFROM ubuntu:18.04\n\nRUN apt-get update\nRUN apt-get install -y software-properties-common binutils\nRUN apt-get install -y ruby-dev\nRUN gem install fpm\n\n# Use deadsnakes for the latest Pythons (e.g 3.9)\nRUN add-apt-repository ppa:deadsnakes/ppa\nRUN apt-get update && apt-get install -y python3.9 python3.9-dev python3.9-venv\n\n# Install rpm as well, since we are going to build fedora dists too\nRUN apt-get install -y rpm\n\nADD . /app\nWORKDIR /app/extras/packaging/linux\n\nENV VIRTUAL_ENV=/opt/venv\nRUN python3.9 -m venv $VIRTUAL_ENV\nENV PATH=\"$VIRTUAL_ENV/bin:$PATH\"\n\n# Ensure that pip is renewed, otherwise we would be using distro-provided pip\n# which strips vendored packages and doesn't work with PyInstaller.\nRUN python -m pip install /app\nRUN python -m pip install pyinstaller wheel\nRUN python -m pip install --force-reinstall --upgrade pip\n\nRUN echo 'BUILD_CHANNEL=\"pypi\"' > /app/httpie/internal/__build_channel__.py\nRUN python build.py\n\nENTRYPOINT [\"mv\", \"/app/extras/packaging/linux/dist/\", \"/artifacts\"]\n"
  },
  {
    "path": "extras/packaging/linux/README.md",
    "content": "# Standalone Linux Packages\n\n![packaging.png](https://user-images.githubusercontent.com/47358913/159950478-2d090d1b-69b9-4914-a1b4-d3e3d8e25fe0.png)\n\nThis directory contains the build scripts for creating:\n\n- A self-contained binary executable for the HTTPie itself\n- `httpie.deb` and `httpie.rpm` packages for Debian and Fedora.\n\nThe process of constructing them are fully automated, and can be easily done through the [`Release as Standalone Linux Package`](https://github.com/httpie/cli/actions/workflows/release-linux-standalone.yml)\naction. Once it finishes, the release artifacts will be attached in the summary page of the triggered run.\n\n\n## Hacking\n\nThe main entry point for the package builder is the [`build.py`](https://github.com/httpie/cli/blob/master/extras/packaging/linux/build.py). It\ncontains 2 major methods:\n\n- `build_binaries`, for the self-contained executables\n- `build_packages`, for the OS-specific packages (which wrap the binaries)\n\n### `build_binaries`\n\nWe use [PyInstaller](https://pyinstaller.readthedocs.io/en/stable/) for the binaries. Normally pyinstaller offers two different modes:\n\n- Single directory (harder to distribute, low redundancy. Library files are shared across different executables)\n- Single binary (easier to distribute, higher redundancy. Same libraries are statically linked to different executables, so higher total size)\n\nSince our binary size (in total 20 MiBs) is not that big, we have decided to choose the single binary mode for the sake of easier distribution.\n\nWe also disable `UPX`, which is a runtime decompression method since it adds some startup cost.\n\n### `build_packages`\n\nWe build our OS-specific packages with [FPM](https://github.com/jordansissel/fpm) which offers a really nice abstraction. We use the `dir` mode,\nand package `http`, `https` and `httpie` commands. More can be added to the `files` option.\n\nSince the `httpie` depends on having a pip executable, we explicitly depend on the system Python even though the core does not use it.\n\n### Docker Image\n\nThis directory also contains a [docker image](https://github.com/httpie/cli/blob/master/extras/packaging/linux/Dockerfile) which helps\nbuilding our standalone binaries in an isolated environment with the lowest possible library versions. This is important, since even though\nthe executables are standalone they still depend on some main system C libraries (like `glibc`) so we need to create our executables inside\nan environment with a very old (but not deprecated) glibc version. It makes us soundproof for all active Ubuntu/Debian versions.\n\nIt also contains the Python version we package our HTTPie with, so it is the place if you need to change it.\n\n### `./get_release_artifacts.sh`\n\nIf you make a change in the `build.py`, run the following script to test it out. It will return multiple files under `artifacts/dist` which\nthen you can test out and ensure their quality (it is also the script that we use in our automation).\n"
  },
  {
    "path": "extras/packaging/linux/build.py",
    "content": "import stat\nimport subprocess\nfrom pathlib import Path\nfrom typing import Iterator, Tuple\n\nBUILD_DIR = Path(__file__).parent\nHTTPIE_DIR = BUILD_DIR.parent.parent.parent\n\nEXTRAS_DIR = HTTPIE_DIR / 'extras'\nMAN_PAGES_DIR = EXTRAS_DIR /  'man'\n\nSCRIPT_DIR = BUILD_DIR / Path('scripts')\nHOOKS_DIR = SCRIPT_DIR / 'hooks'\n\nDIST_DIR = BUILD_DIR / 'dist'\n\nTARGET_SCRIPTS = {\n    SCRIPT_DIR / 'http_cli.py': [],\n    SCRIPT_DIR / 'httpie_cli.py': ['--hidden-import=pip'],\n}\n\n\ndef build_binaries() -> Iterator[Tuple[str, Path]]:\n    for target_script, extra_args in TARGET_SCRIPTS.items():\n        subprocess.check_call(\n            [\n                'pyinstaller',\n                '--onefile',\n                '--noupx',\n                '-p',\n                HTTPIE_DIR,\n                '--additional-hooks-dir',\n                HOOKS_DIR,\n                *extra_args,\n                target_script,\n            ]\n        )\n\n    for executable_path in DIST_DIR.iterdir():\n        if executable_path.suffix:\n            continue\n        stat_r = executable_path.stat()\n        executable_path.chmod(stat_r.st_mode | stat.S_IEXEC)\n        yield executable_path.stem, executable_path\n\n\ndef build_packages(http_binary: Path, httpie_binary: Path) -> None:\n    import httpie\n\n    # Mapping of src_file -> dst_file\n    files = [\n        (http_binary, '/usr/bin/http'),\n        (http_binary, '/usr/bin/https'),\n        (httpie_binary, '/usr/bin/httpie'),\n    ]\n    files.extend(\n        (man_page, f'/usr/share/man/man1/{man_page.name}')\n        for man_page in MAN_PAGES_DIR.glob('*.1')\n    )\n\n    # A list of additional dependencies\n    deps = [\n        'python3 >= 3.7',\n        'python3-pip'\n    ]\n\n    processed_deps = [\n        f'--depends={dep}'\n        for dep in deps\n    ]\n    processed_files = [\n        '='.join([str(src.resolve()), dst]) for src, dst in files\n    ]\n    for target in ['deb', 'rpm']:\n        subprocess.check_call(\n            [\n                'fpm',\n                '--force',\n                '-s',\n                'dir',\n                '-t',\n                target,\n                '--name',\n                'httpie',\n                '--version',\n                httpie.__version__,\n                '--description',\n                httpie.__doc__.strip(),\n                '--license',\n                httpie.__licence__,\n                *processed_deps,\n                *processed_files,\n            ],\n            cwd=DIST_DIR,\n        )\n\n\ndef main():\n    binaries = dict(build_binaries())\n    build_packages(binaries['http_cli'], binaries['httpie_cli'])\n\n    # Rename http_cli/httpie_cli to http/httpie\n    binaries['http_cli'].rename(DIST_DIR / 'http')\n    binaries['httpie_cli'].rename(DIST_DIR / 'httpie')\n\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "extras/packaging/linux/get_release_artifacts.sh",
    "content": "#!/bin/bash\n\nset -xe\n\nREPO_ROOT=../../../\nARTIFACTS_DIR=$(pwd)/artifacts\n\n# Reset the ARTIFACTS_DIR.\nrm -rf $ARTIFACTS_DIR\nmkdir -p $ARTIFACTS_DIR\n\n# Operate on the repository root to have the proper\n# docker context.\npushd $REPO_ROOT\n\n# Build the PyInstaller image\ndocker build -t pyinstaller-httpie -f extras/packaging/linux/Dockerfile .\n\n# Copy the artifacts to the designated directory.\ndocker run --rm -i -v $ARTIFACTS_DIR:/artifacts pyinstaller-httpie:latest\n\npopd\n"
  },
  {
    "path": "extras/packaging/linux/scripts/hooks/hook-pip.py",
    "content": "from pathlib import Path\nfrom PyInstaller.utils.hooks import collect_all\n\ndef hook(hook_api):\n    for pkg in [\n        'pip',\n        'setuptools',\n        'distutils',\n        'pkg_resources'\n    ]:\n        datas, binaries, hiddenimports = collect_all(pkg)\n        hook_api.add_datas(datas)\n        hook_api.add_binaries(binaries)\n        hook_api.add_imports(*hiddenimports)\n"
  },
  {
    "path": "extras/packaging/linux/scripts/http_cli.py",
    "content": "from httpie.__main__ import main\n\nif __name__ == '__main__':\n    import sys\n    sys.exit(main())\n"
  },
  {
    "path": "extras/packaging/linux/scripts/httpie_cli.py",
    "content": "from httpie.manager.__main__ import main\n\nif __name__ == '__main__':\n    import sys\n    sys.exit(main())\n"
  },
  {
    "path": "extras/profiling/README.md",
    "content": "# HTTPie Benchmarking Infrastructure\n\nThis directory includes the benchmarks we use for testing HTTPie's speed and the\ninfrastructure to automate this testing across versions.\n\n## Usage\n\nEnsure the following requirements are satisfied:\n\n- Python 3.7+\n- `pyperf`\n\nThen, run the `extras/profiling/run.py`:\n\n```console\n$ python extras/profiling/run.py\n```\n\nWithout any options, this command will initially create an isolated environment\nand install `httpie` from the latest commit. Then it will create a second\nenvironment with the `master` of the current repository and run the benchmarks\non both of them. It will compare the results and print it as a markdown table:\n\n| Benchmark                              | master |     this_branch      |\n| -------------------------------------- | :----: | :------------------: |\n| `http --version` (startup)             | 201 ms | 174 ms: 1.16x faster |\n| `http --offline pie.dev/get` (startup) | 200 ms | 174 ms: 1.15x faster |\n| Geometric mean                         | (ref)  |     1.10x faster     |\n\nIf your `master` branch is not up-to-date, you can get a fresh clone by passing\n`--fresh` option. This way, the benchmark runner will clone the `httpie/cli`\nrepo from `GitHub` and use it as the baseline.\n\nYou can customize these branches by passing `--local-repo`/`--target-branch`,\nand customize the repos by passing `--local-repo`/`--target-repo` (can either\ntake a URL or a path).\n\nIf you want to run a third environment with additional dependencies (such as\n`pyOpenSSL`), you can pass `--complex`.\n"
  },
  {
    "path": "extras/profiling/benchmarks.py",
    "content": "\"\"\"\nThis file is the declaration of benchmarks for HTTPie. It\nis also used to run them with the current environment.\n\nEach instance of BaseRunner class will be an individual\nbenchmark. And if run without any arguments, this file\nwill execute every benchmark instance and report the\ntimings.\n\nThe benchmarks are run through 'pyperf', which allows to\ndo get very precise results. For micro-benchmarks like startup,\nplease run `pyperf system tune` to get even more accurate results.\n\nExamples:\n\n    # Run everything as usual, the default is that we do 3 warm-up runs\n    # and 5 actual runs.\n    $ python extras/profiling/benchmarks.py\n\n    # For retrieving results faster, pass --fast\n    $ python extras/profiling/benchmarks.py --fast\n\n    # For verify everything works as expected, pass --debug-single-value.\n    # It will only run everything once, so the resuls are not reliable. But\n    # very useful when iterating on a benchmark\n    $ python extras/profiling/benchmarks.py --debug-single-value\n\n    # If you want to run with a custom HTTPie command (for example with\n    # and HTTPie instance installed in another virtual environment),\n    # pass HTTPIE_COMMAND variable.\n    $ HTTPIE_COMMAND=\"/my/python /my/httpie\" python extras/profiling/benchmarks.py\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport shlex\nimport subprocess\nimport sys\nimport threading\nfrom contextlib import ExitStack, contextmanager\nfrom dataclasses import dataclass, field\nfrom functools import cached_property, partial\nfrom http.server import HTTPServer, SimpleHTTPRequestHandler\nfrom tempfile import TemporaryDirectory\nfrom typing import ClassVar, Final, List\n\nimport pyperf\n\n# For download benchmarks, define a set of files.\n# file: (block_size, count) => total_size = block_size * count\nPREDEFINED_FILES: Final = {'3G': (3 * 1024 ** 2, 1024)}\n\n\nclass QuietSimpleHTTPServer(SimpleHTTPRequestHandler):\n    def log_message(self, *args, **kwargs):\n        pass\n\n\n@contextmanager\ndef start_server():\n    \"\"\"Create a server to serve local files. It will create the\n    PREDEFINED_FILES through dd.\"\"\"\n    with TemporaryDirectory() as directory:\n        for file_name, (block_size, count) in PREDEFINED_FILES.items():\n            subprocess.check_call(\n                [\n                    'dd',\n                    'if=/dev/zero',\n                    f'of={file_name}',\n                    f'bs={block_size}',\n                    f'count={count}',\n                ],\n                cwd=directory,\n                stdout=subprocess.DEVNULL,\n                stderr=subprocess.DEVNULL,\n            )\n\n        handler = partial(QuietSimpleHTTPServer, directory=directory)\n        server = HTTPServer(('localhost', 0), handler)\n\n        thread = threading.Thread(target=server.serve_forever)\n        thread.start()\n        yield '{}:{}'.format(*server.socket.getsockname())\n        server.shutdown()\n        thread.join(timeout=0.5)\n\n\n@dataclass\nclass Context:\n    benchmarks: ClassVar[List[BaseRunner]] = []\n    stack: ExitStack = field(default_factory=ExitStack)\n    runner: pyperf.Runner = field(default_factory=pyperf.Runner)\n\n    def run(self) -> pyperf.BenchmarkSuite:\n        results = [benchmark.run(self) for benchmark in self.benchmarks]\n        return pyperf.BenchmarkSuite(results)\n\n    @property\n    def cmd(self) -> List[str]:\n        if cmd := os.getenv('HTTPIE_COMMAND'):\n            return shlex.split(cmd)\n\n        http = os.path.join(os.path.dirname(sys.executable), 'http')\n        assert os.path.exists(http)\n        return [sys.executable, http]\n\n    @cached_property\n    def server(self) -> str:\n        return self.stack.enter_context(start_server())\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, *exc_info):\n        self.stack.close()\n\n\n@dataclass\nclass BaseRunner:\n    \"\"\"\n    An individual benchmark case. By default it has the category\n    (e.g like startup or download) and a name.\n    \"\"\"\n\n    category: str\n    title: str\n\n    def __post_init__(self):\n        Context.benchmarks.append(self)\n\n    def run(self, context: Context) -> pyperf.Benchmark:\n        raise NotImplementedError\n\n    @property\n    def name(self) -> str:\n        return f'{self.title} ({self.category})'\n\n\n@dataclass\nclass CommandRunner(BaseRunner):\n    \"\"\"\n    Run a single command, and benchmark it.\n    \"\"\"\n\n    args: List[str]\n\n    def run(self, context: Context) -> pyperf.Benchmark:\n        return context.runner.bench_command(self.name, [*context.cmd, *self.args])\n\n\n@dataclass\nclass DownloadRunner(BaseRunner):\n    \"\"\"\n    Benchmark downloading a single file from the\n    remote server.\n    \"\"\"\n\n    file_name: str\n\n    def run(self, context: Context) -> pyperf.Benchmark:\n        return context.runner.bench_command(\n            self.name,\n            [\n                *context.cmd,\n                '--download',\n                'GET',\n                f'{context.server}/{self.file_name}',\n            ],\n        )\n\n\nCommandRunner('startup', '`http --version`', ['--version'])\nCommandRunner('startup', '`http --offline pie.dev/get`', ['--offline', 'pie.dev/get'])\nfor pretty in ['all', 'none']:\n    CommandRunner(\n        'startup',\n        f'`http --pretty={pretty} pie.dev/stream/1000`',\n        [\n            '--print=HBhb',\n            f'--pretty={pretty}',\n            'httpbin.org/stream/1000'\n        ]\n    )\nDownloadRunner('download', '`http --download :/big_file.txt` (3GB)', '3G')\n\n\ndef main() -> None:\n    # PyPerf will bring it's own argument parser, so configure the script.\n    # The somewhat fast and also precise enough configuration is this. We run\n    # benchmarks 3 times to warm up (e.g especially for download benchmark, this\n    # is important). And then 5 actual runs where we record.\n    sys.argv.extend(\n        ['--worker', '--loops=1', '--warmup=3', '--values=5', '--processes=2']\n    )\n\n    with Context() as context:\n        context.run()\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "extras/profiling/run.py",
    "content": "\"\"\"\nRun the HTTPie benchmark suite with multiple environments.\n\nThis script is configured in a way that, it will create\ntwo (or more) isolated environments and compare the *last\ncommit* of this repository with it's master.\n\n> If you didn't commit yet, it won't be showing results.\n\nYou can also pass --fresh, which would test the *last\ncommit* of this repository with a fresh copy of HTTPie\nitself. This way even if you don't have an up-to-date\nmaster branch, you can still compare it with the upstream's\nmaster.\n\nYou can also pass --complex to add 2 additional environments,\nwhich would include additional dependencies like pyOpenSSL.\n\nExamples:\n\n    # Run everything as usual, and compare last commit with master\n    $ python extras/profiling/run.py\n\n    # Include complex environments\n    $ python extras/profiling/run.py --complex\n\n    # Compare against a fresh copy\n    $ python extras/profiling/run.py --fresh\n\n    # Compare against a custom branch of a custom repo\n    $ python extras/profiling/run.py --target-repo my_repo --target-branch my_branch\n\n    # Debug changes made on this script (only run benchmarks once)\n    $ python extras/profiling/run.py --debug\n\"\"\"\n\nimport dataclasses\nimport shlex\nimport subprocess\nimport sys\nimport tempfile\nimport venv\nfrom argparse import ArgumentParser, FileType\nfrom contextlib import contextmanager\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom typing import (IO, Dict, Generator, Iterable, List, Optional,\n                    Tuple)\n\nBENCHMARK_SCRIPT = Path(__file__).parent / 'benchmarks.py'\nCURRENT_REPO = Path(__file__).parent.parent.parent\n\nGITHUB_URL = 'https://github.com/httpie/cli.git'\nTARGET_BRANCH = 'master'\n\n# Additional dependencies for --complex\nADDITIONAL_DEPS = ('pyOpenSSL',)\n\n\ndef call(*args, **kwargs):\n    kwargs.setdefault('stdout', subprocess.DEVNULL)\n    return subprocess.check_call(*args, **kwargs)\n\n\nclass Environment:\n    \"\"\"\n    Each environment defines how to create an isolated instance\n    where we could install HTTPie and run benchmarks without any\n    environmental factors.\n    \"\"\"\n\n    @contextmanager\n    def on_repo(self) -> Generator[Tuple[Path, Dict[str, str]], None, None]:\n        \"\"\"\n        Return the path to the python interpreter and the\n        environment variables (e.g HTTPIE_COMMAND) to be\n        used on the benchmarks.\n        \"\"\"\n        raise NotImplementedError\n\n\n@dataclass\nclass HTTPieEnvironment(Environment):\n    repo_url: str\n    branch: Optional[str] = None\n    dependencies: Iterable[str] = ()\n\n    @contextmanager\n    def on_repo(self) -> Generator[Path, None, None]:\n        with tempfile.TemporaryDirectory() as directory_path:\n            directory = Path(directory_path)\n\n            # Clone the repo\n            repo_path = directory / 'httpie'\n            call(\n                ['git', 'clone', self.repo_url, repo_path],\n                stderr=subprocess.DEVNULL,\n            )\n\n            if self.branch is not None:\n                call(\n                    ['git', 'checkout', self.branch],\n                    cwd=repo_path,\n                    stderr=subprocess.DEVNULL,\n                )\n\n            # Prepare the environment\n            venv_path = directory / '.venv'\n            venv.create(venv_path, with_pip=True)\n\n            # Install basic dependencies\n            python = venv_path / 'bin' / 'python'\n            call(\n                [\n                    python,\n                    '-m',\n                    'pip',\n                    'install',\n                    'wheel',\n                    'pyperf==2.3.0',\n                    *self.dependencies,\n                ]\n            )\n\n            # Create a wheel distribution of HTTPie\n            call([python, 'setup.py', 'bdist_wheel'], cwd=repo_path)\n\n            # Install httpie\n            distribution_path = next((repo_path / 'dist').iterdir())\n            call(\n                [python, '-m', 'pip', 'install', distribution_path],\n                cwd=repo_path,\n            )\n\n            http = venv_path / 'bin' / 'http'\n            yield python, {'HTTPIE_COMMAND': shlex.join([str(python), str(http)])}\n\n\n@dataclass\nclass LocalCommandEnvironment(Environment):\n    local_command: str\n\n    @contextmanager\n    def on_repo(self) -> Generator[Path, None, None]:\n        yield sys.executable, {'HTTPIE_COMMAND': self.local_command}\n\n\ndef dump_results(\n    results: List[str],\n    file: IO[str],\n    min_speed: Optional[str] = None\n) -> None:\n    for result in results:\n        lines = result.strip().splitlines()\n        if min_speed is not None and \"hidden\" in lines[-1]:\n            lines[-1] = (\n                'Some benchmarks were hidden from this list '\n                'because their timings did not change in a '\n                'significant way (change was within the error '\n                'margin ±{margin}%).'\n            ).format(margin=min_speed)\n            result = '\\n'.join(lines)\n\n        print(result, file=file)\n        print(\"\\n---\\n\", file=file)\n\n\ndef compare(*args, directory: Path, min_speed: Optional[str] = None):\n    compare_args = ['pyperf', 'compare_to', '--table', '--table-format=md', *args]\n    if min_speed:\n        compare_args.extend(['--min-speed', min_speed])\n    return subprocess.check_output(\n        compare_args,\n        cwd=directory,\n        text=True,\n    )\n\n\ndef run(\n    configs: List[Dict[str, Environment]],\n    file: IO[str],\n    debug: bool = False,\n    min_speed: Optional[str] = None,\n) -> None:\n    result_directory = Path(tempfile.mkdtemp())\n    results = []\n\n    current = 1\n    total = sum(1 for config in configs for _ in config.items())\n\n    def iterate(env_name, status):\n        print(\n            f'Iteration: {env_name} ({current}/{total}) ({status})' + ' ' * 10,\n            end='\\r',\n            flush=True,\n        )\n\n    for config in configs:\n        for env_name, env in config.items():\n            iterate(env_name, 'setting up')\n            with env.on_repo() as (python, env_vars):\n                iterate(env_name, 'running benchmarks')\n                args = [python, BENCHMARK_SCRIPT, '-o', env_name]\n                if debug:\n                    args.append('--debug-single-value')\n                call(\n                    args,\n                    cwd=result_directory,\n                    env=env_vars,\n                )\n            current += 1\n\n        results.append(compare(\n            *config.keys(),\n            directory=result_directory,\n            min_speed=min_speed\n        ))\n\n    dump_results(results, file=file, min_speed=min_speed)\n    print('Results are available at:', result_directory)\n\n\ndef main() -> None:\n    parser = ArgumentParser()\n    parser.add_argument('--local-repo', default=CURRENT_REPO)\n    parser.add_argument('--local-branch', default=None)\n    parser.add_argument('--target-repo', default=CURRENT_REPO)\n    parser.add_argument('--target-branch', default=TARGET_BRANCH)\n    parser.add_argument(\n        '--fresh',\n        action='store_const',\n        const=GITHUB_URL,\n        dest='target_repo',\n        help='Clone the target repo from upstream GitHub URL',\n    )\n    parser.add_argument(\n        '--complex',\n        action='store_true',\n        help='Add a second run, with a complex python environment.',\n    )\n    parser.add_argument(\n        '--local-bin',\n        help='Run the suite with the given local binary in addition to'\n        ' existing runners. (E.g --local-bin $(command -v xh))',\n    )\n    parser.add_argument(\n        '--file',\n        type=FileType('w'),\n        default=sys.stdout,\n        help='File to print the actual results',\n    )\n    parser.add_argument(\n        '--min-speed',\n        help='Minimum of speed in percent to consider that a '\n             'benchmark is significant'\n    )\n    parser.add_argument(\n        '--debug',\n        action='store_true',\n    )\n\n    options = parser.parse_args()\n\n    configs = []\n\n    base_config = {\n        options.target_branch: HTTPieEnvironment(options.target_repo, options.target_branch),\n        'this_branch': HTTPieEnvironment(options.local_repo, options.local_branch),\n    }\n    configs.append(base_config)\n\n    if options.complex:\n        complex_config = {\n            env_name\n            + '-complex': dataclasses.replace(env, dependencies=ADDITIONAL_DEPS)\n            for env_name, env in base_config.items()\n        }\n        configs.append(complex_config)\n\n    if options.local_bin:\n        base_config['binary'] = LocalCommandEnvironment(options.local_bin)\n\n    run(configs, file=options.file, debug=options.debug, min_speed=options.min_speed)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "extras/scripts/generate_man_pages.py",
    "content": "import os\nimport re\nfrom contextlib import contextmanager\nfrom pathlib import Path\nfrom typing import Optional, Iterator, Iterable\n\n\n# So that httpie.cli.definition can provide man-page-specific output. Must be set before importing httpie.\nos.environ['HTTPIE_BUILDING_MAN_PAGES'] = '1'\n\nimport httpie\nfrom httpie.cli.definition import options as core_options, IS_MAN_PAGE\nfrom httpie.cli.options import ParserSpec\nfrom httpie.manager.cli import options as manager_options\nfrom httpie.output.ui.rich_help import OptionsHighlighter, to_usage\nfrom httpie.output.ui.rich_utils import render_as_string\n\n\nassert IS_MAN_PAGE, 'CLI definition does not understand we’re building man pages'\n\n# Escape certain characters, so they are rendered properly on all terminals.\n# <https://man7.org/linux/man-pages/man7/groff_char.7.html>\nESCAPE_MAP = {\n    '\"': '\\[dq]',\n    \"'\": '\\[aq]',\n    '~': '\\(ti',\n    '’': \"\\(ga\",\n    '\\\\': '\\e',\n}\nESCAPE_MAP = {ord(key): value for key, value in ESCAPE_MAP.items()}\n\nEXTRAS_DIR = Path(__file__).parent.parent\nMAN_PAGE_PATH = EXTRAS_DIR / 'man'\nPROJECT_ROOT = EXTRAS_DIR.parent\n\nOPTION_HIGHLIGHT_RE = re.compile(\n    OptionsHighlighter.highlights[0]\n)\n\n\nclass ManPageBuilder:\n    def __init__(self):\n        self.source = []\n\n    def title_line(\n        self,\n        full_name: str,\n        program_name: str,\n        program_version: str,\n        last_edit_date: str,\n    ) -> None:\n        self.source.append(\n            f'.TH {program_name} 1 \"{last_edit_date}\" '\n            f'\"{full_name} {program_version}\" \"{full_name} Manual\"'\n        )\n\n    def set_name(self, program_name: str) -> None:\n        with self.section('NAME'):\n            self.write(program_name)\n\n    def write(self, text: str, *, bold: bool = False) -> None:\n        if bold:\n            text = '.B ' + text\n        self.source.append(text)\n\n    def separate(self) -> None:\n        self.source.append('.PP')\n\n    def format_desc(self, desc: str) -> str:\n        description = _escape_and_dedent(desc)\n        description = OPTION_HIGHLIGHT_RE.sub(\n            # Boldify the option part, but don't remove the prefix (start of the match).\n            lambda match: match[1] + self.boldify(match['option']),\n            description\n        )\n        return description\n\n    def add_comment(self, comment: str) -> None:\n        self.source.append(f'.\\\\\" {comment}')\n\n    def add_options(self, options: Iterable[str], *, metavar: Optional[str] = None) -> None:\n        text = \", \".join(map(self.boldify, options))\n        if metavar:\n            text += f' {self.underline(metavar)}'\n        self.write(f'.IP \"{text}\"')\n\n    def build(self) -> str:\n        return '\\n'.join(self.source)\n\n    @contextmanager\n    def section(self, section_name: str) -> Iterator[None]:\n        self.write(f'.SH {section_name}')\n        self.in_section = True\n        yield\n        self.in_section = False\n\n    def underline(self, text: str) -> str:\n        return r'\\fI\\,{}\\/\\fR'.format(text)\n\n    def boldify(self, text: str) -> str:\n        return r'\\fB\\,{}\\/\\fR'.format(text)\n\n\ndef _escape_and_dedent(text: str) -> str:\n    lines = []\n    for should_act, line in enumerate(text.splitlines()):\n        # Only dedent after the first line.\n        if should_act:\n            if line.startswith('    '):\n                line = line[4:]\n\n        lines.append(line)\n    return '\\n'.join(lines).translate(ESCAPE_MAP)\n\n\ndef to_man_page(program_name: str, spec: ParserSpec, *, is_top_level_cmd: bool = False) -> str:\n    builder = ManPageBuilder()\n    builder.add_comment(\n        f\"This file is auto-generated from the parser declaration \"\n        + (f\"in {Path(spec.source_file).relative_to(PROJECT_ROOT)} \" if spec.source_file else \"\")\n        + f\"by {Path(__file__).relative_to(PROJECT_ROOT)}.\"\n    )\n\n    builder.title_line(\n        full_name='HTTPie',\n        program_name=program_name,\n        program_version=httpie.__version__,\n        last_edit_date=httpie.__date__,\n    )\n    builder.set_name(program_name)\n\n    with builder.section('SYNOPSIS'):\n        # `http` and `https` are commands that can be directly used, so they can have\n        # a valid usage. But `httpie` is a top-level command with multiple sub commands,\n        # so for the synopsis we'll only reference the `httpie` name.\n        if is_top_level_cmd:\n            synopsis = program_name\n        else:\n            synopsis = render_as_string(to_usage(spec, program_name=program_name))\n        builder.write(synopsis)\n\n    with builder.section('DESCRIPTION'):\n        builder.write(spec.description)\n        if spec.man_page_hint:\n            builder.write(spec.man_page_hint)\n\n    for index, group in enumerate(spec.groups, 1):\n        with builder.section(group.name):\n            if group.description:\n                builder.write(group.description)\n\n            for argument in group.arguments:\n                if argument.is_hidden:\n                    continue\n\n                raw_arg = argument.serialize(isolation_mode=True)\n\n                metavar = raw_arg.get('metavar')\n                if raw_arg.get('is_positional'):\n                    # In case of positional arguments, metavar is always equal\n                    # to the list of options (e.g `METHOD`).\n                    metavar = None\n                builder.add_options(raw_arg['options'], metavar=metavar)\n\n                desc = builder.format_desc(raw_arg.get('description', ''))\n                builder.write('\\n' + desc + '\\n')\n\n            builder.separate()\n\n    if spec.epilog:\n        with builder.section('SEE ALSO'):\n            builder.write(builder.format_desc(spec.epilog))\n\n    return builder.build()\n\n\ndef main() -> None:\n    for program_name, spec, config in [\n        ('http', core_options, {}),\n        ('https', core_options, {}),\n        ('httpie', manager_options, {'is_top_level_cmd': True}),\n    ]:\n        with open((MAN_PAGE_PATH / program_name).with_suffix('.1'), 'w') as stream:\n            stream.write(to_man_page(program_name, spec, **config))\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "httpie/__init__.py",
    "content": "\"\"\"\nHTTPie: modern, user-friendly command-line HTTP client for the API era.\n\n\"\"\"\n\n__version__ = '3.2.4'\n__date__ = '2024-11-01'\n__author__ = 'Jakub Roztocil'\n__licence__ = 'BSD'\n"
  },
  {
    "path": "httpie/__main__.py",
    "content": "\"\"\"The main entry point. Invoke as `http' or `python -m httpie'.\n\n\"\"\"\n\n\ndef main():\n    try:\n        from httpie.core import main\n        exit_status = main()\n    except KeyboardInterrupt:\n        from httpie.status import ExitStatus\n        exit_status = ExitStatus.ERROR_CTRL_C\n\n    return exit_status.value\n\n\nif __name__ == '__main__':  # pragma: nocover\n    import sys\n    sys.exit(main())\n"
  },
  {
    "path": "httpie/adapters.py",
    "content": "from httpie.cli.dicts import HTTPHeadersDict\nfrom requests.adapters import HTTPAdapter\n\n\nclass HTTPieHTTPAdapter(HTTPAdapter):\n\n    def build_response(self, req, resp):\n        \"\"\"Wrap the original headers with the `HTTPHeadersDict`\n        to preserve multiple headers that have the same name\"\"\"\n\n        response = super().build_response(req, resp)\n        response.headers = HTTPHeadersDict(getattr(resp, 'headers', {}))\n        return response\n"
  },
  {
    "path": "httpie/cli/__init__.py",
    "content": ""
  },
  {
    "path": "httpie/cli/argparser.py",
    "content": "import argparse\nimport errno\nimport os\nimport re\nimport sys\nfrom argparse import RawDescriptionHelpFormatter\nfrom textwrap import dedent\nfrom urllib.parse import urlsplit\n\nfrom requests.utils import get_netrc_auth\n\nfrom .argtypes import (\n    AuthCredentials, SSLCredentials, KeyValueArgType,\n    PARSED_DEFAULT_FORMAT_OPTIONS,\n    parse_auth,\n    parse_format_options,\n)\nfrom .constants import (\n    HTTP_GET, HTTP_POST, BASE_OUTPUT_OPTIONS, OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT,\n    OUTPUT_OPTIONS_DEFAULT_OFFLINE, OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED,\n    OUT_RESP_BODY, PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY, RequestType,\n    SEPARATOR_CREDENTIALS,\n    SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_GROUP_DATA_ITEMS, URL_SCHEME_RE,\n)\nfrom .exceptions import ParseError\nfrom .requestitems import RequestItems\nfrom ..context import Environment\nfrom ..plugins.registry import plugin_manager\nfrom ..utils import ExplicitNullAuth, get_content_type\n\n\nclass HTTPieHelpFormatter(RawDescriptionHelpFormatter):\n    \"\"\"A nicer help formatter.\n\n    Help for arguments can be indented and contain new lines.\n    It will be de-dented and arguments in the help\n    will be separated by a blank line for better readability.\n\n\n    \"\"\"\n\n    def __init__(self, max_help_position=6, *args, **kwargs):\n        # A smaller indent for args help.\n        kwargs['max_help_position'] = max_help_position\n        super().__init__(*args, **kwargs)\n\n    def _split_lines(self, text, width):\n        text = dedent(text).strip() + '\\n\\n'\n        return text.splitlines()\n\n    def add_usage(self, usage, actions, groups, prefix=None):\n        # Only display the positional arguments\n        displayed_actions = [\n            action\n            for action in actions\n            if not action.option_strings\n        ]\n\n        _, exception, _ = sys.exc_info()\n        if (\n            isinstance(exception, argparse.ArgumentError)\n            and len(exception.args) >= 1\n            and isinstance(exception.args[0], argparse.Action)\n        ):\n            # add_usage path is also taken when you pass an invalid option,\n            # e.g --style=invalid. If something like that happens, we want\n            # to include to action that caused to the invalid usage into\n            # the list of actions we are displaying.\n            displayed_actions.insert(0, exception.args[0])\n\n        super().add_usage(\n            usage,\n            displayed_actions,\n            groups,\n            prefix=\"usage:\\n    \"\n        )\n\n\n# TODO: refactor and design type-annotated data structures\n#       for raw args + parsed args and keep things immutable.\nclass BaseHTTPieArgumentParser(argparse.ArgumentParser):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.env = None\n        self.args = None\n        self.has_stdin_data = False\n        self.has_input_data = False\n\n    # noinspection PyMethodOverriding\n    def parse_args(\n        self,\n        env: Environment,\n        args=None,\n        namespace=None\n    ) -> argparse.Namespace:\n        self.env = env\n        self.args, no_options = self.parse_known_args(args, namespace)\n        if self.args.debug:\n            self.args.traceback = True\n        self.has_stdin_data = (\n            self.env.stdin\n            and not getattr(self.args, 'ignore_stdin', False)\n            and not self.env.stdin_isatty\n        )\n        self.has_input_data = self.has_stdin_data or getattr(self.args, 'raw', None) is not None\n        return self.args\n\n    # noinspection PyShadowingBuiltins\n    def _print_message(self, message, file=None):\n        # Sneak in our stderr/stdout.\n        if hasattr(self, 'root'):\n            env = self.root.env\n        else:\n            env = self.env\n\n        if env is not None:\n            file = {\n                sys.stdout: env.stdout,\n                sys.stderr: env.stderr,\n                None: env.stderr\n            }.get(file, file)\n\n        if not hasattr(file, 'buffer') and isinstance(message, str):\n            message = message.encode(env.stdout_encoding)\n        super()._print_message(message, file)\n\n\nclass HTTPieManagerArgumentParser(BaseHTTPieArgumentParser):\n    def parse_known_args(self, args=None, namespace=None):\n        try:\n            return super().parse_known_args(args, namespace)\n        except SystemExit as exc:\n            if not hasattr(self, 'root') and exc.code == 2:  # Argument Parser Error\n                raise argparse.ArgumentError(None, None)\n            raise\n\n\nclass HTTPieArgumentParser(BaseHTTPieArgumentParser):\n    \"\"\"Adds additional logic to `argparse.ArgumentParser`.\n\n    Handles all input (CLI args, file args, stdin), applies defaults,\n    and performs extra validation.\n\n    \"\"\"\n\n    def __init__(self, *args, formatter_class=HTTPieHelpFormatter, **kwargs):\n        kwargs.setdefault('add_help', False)\n        super().__init__(*args, formatter_class=formatter_class, **kwargs)\n\n    # noinspection PyMethodOverriding\n    def parse_args(\n        self,\n        env: Environment,\n        args=None,\n        namespace=None\n    ) -> argparse.Namespace:\n        self.env = env\n        self.env.args = namespace = namespace or argparse.Namespace()\n        self.args, no_options = super().parse_known_args(args, namespace)\n        if self.args.debug:\n            self.args.traceback = True\n        self.has_stdin_data = (\n            self.env.stdin\n            and not self.args.ignore_stdin\n            and not self.env.stdin_isatty\n        )\n        self.has_input_data = self.has_stdin_data or self.args.raw is not None\n        # Arguments processing and environment setup.\n        self._apply_no_options(no_options)\n        self._process_request_type()\n        self._process_download_options()\n        self._setup_standard_streams()\n        self._process_output_options()\n        self._process_pretty_options()\n        self._process_format_options()\n        self._guess_method()\n        self._parse_items()\n        self._process_url()\n        self._process_auth()\n        self._process_ssl_cert()\n\n        if self.args.raw is not None:\n            self._body_from_input(self.args.raw)\n        elif self.has_stdin_data:\n            self._body_from_file(self.env.stdin)\n\n        if self.args.compress:\n            # TODO: allow --compress with --chunked / --multipart\n            if self.args.chunked:\n                self.error('cannot combine --compress and --chunked')\n            if self.args.multipart:\n                self.error('cannot combine --compress and --multipart')\n\n        return self.args\n\n    def _process_request_type(self):\n        request_type = self.args.request_type\n        self.args.json = request_type is RequestType.JSON\n        self.args.multipart = request_type is RequestType.MULTIPART\n        self.args.form = request_type in {\n            RequestType.FORM,\n            RequestType.MULTIPART,\n        }\n\n    def _process_url(self):\n        if self.args.url.startswith('://'):\n            # Paste URL & add space shortcut: `http ://pie.dev` → `http://pie.dev`\n            self.args.url = self.args.url[3:]\n        if not URL_SCHEME_RE.match(self.args.url):\n            if os.path.basename(self.env.program_name) == 'https':\n                scheme = 'https://'\n            else:\n                scheme = self.args.default_scheme + '://'\n\n            # See if we're using curl style shorthand for localhost (:3000/foo)\n            shorthand = re.match(r'^:(?!:)(\\d*)(/?.*)$', self.args.url)\n            if shorthand:\n                port = shorthand.group(1)\n                rest = shorthand.group(2)\n                self.args.url = scheme + 'localhost'\n                if port:\n                    self.args.url += ':' + port\n                self.args.url += rest\n            else:\n                self.args.url = scheme + self.args.url\n\n    def _setup_standard_streams(self):\n        \"\"\"\n        Modify `env.stdout` and `env.stdout_isatty` based on args, if needed.\n\n        \"\"\"\n\n        self.args.output_file_specified = bool(self.args.output_file)\n        if self.args.download:\n            # FIXME: Come up with a cleaner solution.\n            if not self.args.output_file and not self.env.stdout_isatty:\n                # Use stdout as the download output file.\n                self.args.output_file = self.env.stdout\n            # With `--download`, we write everything that would normally go to\n            # `stdout` to `stderr` instead. Let's replace the stream so that\n            # we don't have to use many `if`s throughout the codebase.\n            # The response body will be treated separately.\n            self.env.stdout = self.env.stderr\n            self.env.stdout_isatty = self.env.stderr_isatty\n\n        elif self.args.output_file:\n            # When not `--download`ing, then `--output` simply replaces\n            # `stdout`. The file is opened for appending, which isn't what\n            # we want in this case.\n            self.args.output_file.seek(0)\n            try:\n                self.args.output_file.truncate()\n            except OSError as e:\n                if e.errno == errno.EINVAL:\n                    # E.g. /dev/null on Linux.\n                    pass\n                else:\n                    raise\n            self.env.stdout = self.args.output_file\n            self.env.stdout_isatty = False\n\n        if self.args.quiet:\n            self.env.quiet = self.args.quiet\n            self.env.stderr = self.env.devnull\n            if not (self.args.output_file_specified and not self.args.download):\n                self.env.stdout = self.env.devnull\n            self.env.apply_warnings_filter()\n\n    def _process_ssl_cert(self):\n        from httpie.ssl_ import _is_key_file_encrypted\n\n        if self.args.cert_key_pass is None:\n            self.args.cert_key_pass = SSLCredentials(None)\n\n        if (\n            self.args.cert_key is not None\n            and self.args.cert_key_pass.value is None\n            and _is_key_file_encrypted(self.args.cert_key)\n        ):\n            self.args.cert_key_pass.prompt_password(self.args.cert_key)\n\n    def _process_auth(self):\n        # TODO: refactor & simplify this method.\n        self.args.auth_plugin = None\n        default_auth_plugin = plugin_manager.get_auth_plugins()[0]\n        auth_type_set = self.args.auth_type is not None\n        url = urlsplit(self.args.url)\n\n        if self.args.auth is None and not auth_type_set:\n            if url.username is not None:\n                # Handle http://username:password@hostname/\n                username = url.username\n                password = url.password or ''\n                self.args.auth = AuthCredentials(\n                    key=username,\n                    value=password,\n                    sep=SEPARATOR_CREDENTIALS,\n                    orig=SEPARATOR_CREDENTIALS.join([username, password])\n                )\n\n        if self.args.auth is not None or auth_type_set:\n            if not self.args.auth_type:\n                self.args.auth_type = default_auth_plugin.auth_type\n            plugin = plugin_manager.get_auth_plugin(self.args.auth_type)()\n\n            if (not self.args.ignore_netrc\n                    and self.args.auth is None\n                    and plugin.netrc_parse):\n                # Only host needed, so it’s OK URL not finalized.\n                netrc_credentials = get_netrc_auth(self.args.url)\n                if netrc_credentials:\n                    self.args.auth = AuthCredentials(\n                        key=netrc_credentials[0],\n                        value=netrc_credentials[1],\n                        sep=SEPARATOR_CREDENTIALS,\n                        orig=SEPARATOR_CREDENTIALS.join(netrc_credentials)\n                    )\n\n            if plugin.auth_require and self.args.auth is None:\n                self.error('--auth required')\n\n            plugin.raw_auth = self.args.auth\n            self.args.auth_plugin = plugin\n            already_parsed = isinstance(self.args.auth, AuthCredentials)\n\n            if self.args.auth is None or not plugin.auth_parse:\n                self.args.auth = plugin.get_auth()\n            else:\n                if already_parsed:\n                    # from the URL\n                    credentials = self.args.auth\n                else:\n                    credentials = parse_auth(self.args.auth)\n\n                if (not credentials.has_password()\n                        and plugin.prompt_password):\n                    if self.args.ignore_stdin:\n                        # Non-tty stdin read by now\n                        self.error(\n                            'Unable to prompt for passwords because'\n                            ' --ignore-stdin is set.'\n                        )\n                    credentials.prompt_password(url.netloc)\n\n                if (credentials.key and credentials.value):\n                    plugin.raw_auth = credentials.key + \":\" + credentials.value\n\n                self.args.auth = plugin.get_auth(\n                    username=credentials.key,\n                    password=credentials.value,\n                )\n        if not self.args.auth and self.args.ignore_netrc:\n            # Set a no-op auth to force requests to ignore .netrc\n            # <https://github.com/psf/requests/issues/2773#issuecomment-174312831>\n            self.args.auth = ExplicitNullAuth()\n\n    def _apply_no_options(self, no_options):\n        \"\"\"For every `--no-OPTION` in `no_options`, set `args.OPTION` to\n        its default value. This allows for un-setting of options, e.g.,\n        specified in config.\n\n        \"\"\"\n        invalid = []\n\n        for option in no_options:\n            if not option.startswith('--no-'):\n                invalid.append(option)\n                continue\n\n            # --no-option => --option\n            inverted = '--' + option[5:]\n            for action in self._actions:\n                if inverted in action.option_strings:\n                    setattr(self.args, action.dest, action.default)\n                    break\n            else:\n                invalid.append(option)\n\n        if invalid:\n            self.error(f'unrecognized arguments: {\" \".join(invalid)}')\n\n    def _body_from_file(self, fd):\n        \"\"\"Read the data from a file-like object.\n\n        Bytes are always read.\n\n        \"\"\"\n        self._ensure_one_data_source(self.args.data, self.args.files)\n        self.args.data = getattr(fd, 'buffer', fd)\n\n    def _body_from_input(self, data):\n        \"\"\"Read the data from the CLI.\n\n        \"\"\"\n        self._ensure_one_data_source(self.has_stdin_data, self.args.data,\n                                     self.args.files)\n        self.args.data = data.encode()\n\n    def _ensure_one_data_source(self, *other_sources):\n        \"\"\"There can only be one source of input request data.\n\n        \"\"\"\n        if any(other_sources):\n            self.error('Request body (from stdin, --raw or a file) and request '\n                       'data (key=value) cannot be mixed. Pass '\n                       '--ignore-stdin to let key/value take priority. '\n                       'See https://httpie.io/docs#scripting for details.')\n\n    def _guess_method(self):\n        \"\"\"Set `args.method` if not specified to either POST or GET\n        based on whether the request has data or not.\n\n        \"\"\"\n        if self.args.method is None:\n            # Invoked as `http URL'.\n            assert not self.args.request_items\n            if self.has_input_data:\n                self.args.method = HTTP_POST\n            else:\n                self.args.method = HTTP_GET\n\n        # FIXME: False positive, e.g., \"localhost\" matches but is a valid URL.\n        elif not re.match('^[a-zA-Z]+$', self.args.method):\n            # Invoked as `http URL item+'. The URL is now in `args.method`\n            # and the first ITEM is now incorrectly in `args.url`.\n            try:\n                # Parse the URL as an ITEM and store it as the first ITEM arg.\n                self.args.request_items.insert(0, KeyValueArgType(\n                    *SEPARATOR_GROUP_ALL_ITEMS).__call__(self.args.url))\n\n            except argparse.ArgumentTypeError as e:\n                if self.args.traceback:\n                    raise\n                self.error(e.args[0])\n\n            else:\n                # Set the URL correctly\n                self.args.url = self.args.method\n                # Infer the method\n                has_data = (\n                    self.has_input_data\n                    or any(\n                        item.sep in SEPARATOR_GROUP_DATA_ITEMS\n                        for item in self.args.request_items)\n                )\n                self.args.method = HTTP_POST if has_data else HTTP_GET\n\n    def _parse_items(self):\n        \"\"\"\n        Parse `args.request_items` into `args.headers`, `args.data`,\n        `args.params`, and `args.files`.\n\n        \"\"\"\n        try:\n            request_items = RequestItems.from_args(\n                request_item_args=self.args.request_items,\n                request_type=self.args.request_type,\n            )\n        except ParseError as e:\n            if self.args.traceback:\n                raise\n            self.error(e.args[0])\n        else:\n            self.args.headers = request_items.headers\n            self.args.data = request_items.data\n            self.args.files = request_items.files\n            self.args.params = request_items.params\n            self.args.multipart_data = request_items.multipart_data\n\n        if self.args.files and not self.args.form:\n            # `http url @/path/to/file`\n            request_file = None\n            for key, file in self.args.files.items():\n                if key != '':\n                    self.error(\n                        'Invalid file fields (perhaps you meant --form?):'\n                        f' {\",\".join(self.args.files.keys())}')\n                if request_file is not None:\n                    self.error(\"Can't read request from multiple files\")\n                request_file = file\n\n            fn, fd, ct = request_file\n            self.args.files = {}\n\n            self._body_from_file(fd)\n\n            if 'Content-Type' not in self.args.headers:\n                content_type = get_content_type(fn)\n                if content_type:\n                    self.args.headers['Content-Type'] = content_type\n\n    def _process_output_options(self):\n        \"\"\"Apply defaults to output options, or validate the provided ones.\n\n        The default output options are stdout-type-sensitive.\n\n        \"\"\"\n\n        def check_options(value, option):\n            unknown = set(value) - OUTPUT_OPTIONS\n            if unknown:\n                self.error(f'Unknown output options: {option}={\",\".join(unknown)}')\n\n        if self.args.verbose:\n            self.args.all = True\n\n        if self.args.output_options is None:\n            if self.args.verbose >= 2:\n                self.args.output_options = ''.join(OUTPUT_OPTIONS)\n            elif self.args.verbose == 1:\n                self.args.output_options = ''.join(BASE_OUTPUT_OPTIONS)\n            elif self.args.offline:\n                self.args.output_options = OUTPUT_OPTIONS_DEFAULT_OFFLINE\n            elif not self.env.stdout_isatty:\n                self.args.output_options = OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED\n            else:\n                self.args.output_options = OUTPUT_OPTIONS_DEFAULT\n\n        if self.args.output_options_history is None:\n            self.args.output_options_history = self.args.output_options\n\n        check_options(self.args.output_options, '--print')\n        check_options(self.args.output_options_history, '--history-print')\n\n        if self.args.download and OUT_RESP_BODY in self.args.output_options:\n            # Response body is always downloaded with --download and it goes\n            # through a different routine, so we remove it.\n            self.args.output_options = str(\n                set(self.args.output_options) - set(OUT_RESP_BODY))\n\n    def _process_pretty_options(self):\n        if self.args.prettify == PRETTY_STDOUT_TTY_ONLY:\n            self.args.prettify = PRETTY_MAP[\n                'all' if self.env.stdout_isatty else 'none']\n        elif (self.args.prettify and self.env.is_windows\n              and self.args.output_file):\n            self.error('Only terminal output can be colorized on Windows.')\n        else:\n            # noinspection PyTypeChecker\n            self.args.prettify = PRETTY_MAP[self.args.prettify]\n\n    def _process_download_options(self):\n        if self.args.offline:\n            self.args.download = False\n            self.args.download_resume = False\n            return\n        if not self.args.download:\n            if self.args.download_resume:\n                self.error('--continue only works with --download')\n        if self.args.download_resume and not (\n                self.args.download and self.args.output_file):\n            self.error('--continue requires --output to be specified')\n\n    def _process_format_options(self):\n        format_options = self.args.format_options or []\n        parsed_options = PARSED_DEFAULT_FORMAT_OPTIONS\n        for options_group in format_options:\n            parsed_options = parse_format_options(options_group, defaults=parsed_options)\n        self.args.format_options = parsed_options\n\n    def print_manual(self):\n        from httpie.output.ui import man_pages\n\n        if man_pages.is_available(self.env.program_name):\n            man_pages.display_for(self.env, self.env.program_name)\n            return None\n\n        text = self.format_help()\n        with self.env.rich_console.pager():\n            self.env.rich_console.print(\n                text,\n                highlight=False\n            )\n\n    def print_usage(self, file):\n        from rich.text import Text\n        from httpie.output.ui import rich_help\n\n        whitelist = set()\n        _, exception, _ = sys.exc_info()\n        if (\n            isinstance(exception, argparse.ArgumentError)\n            and len(exception.args) >= 1\n            and isinstance(exception.args[0], argparse.Action)\n            and exception.args[0].option_strings\n        ):\n            # add_usage path is also taken when you pass an invalid option,\n            # e.g --style=invalid. If something like that happens, we want\n            # to include to action that caused to the invalid usage into\n            # the list of actions we are displaying.\n            whitelist.add(exception.args[0].option_strings[0])\n\n        usage_text = Text('usage', style='bold')\n        usage_text.append(':\\n    ')\n        usage_text.append(rich_help.to_usage(self.spec, whitelist=whitelist))\n        self.env.rich_error_console.print(usage_text)\n\n    def error(self, message):\n        \"\"\"Prints a usage message incorporating the message to stderr and\n        exits.\"\"\"\n        self.print_usage(sys.stderr)\n        self.env.rich_error_console.print(\n            dedent(\n                f'''\n                [bold]error[/bold]:\n                    {message}\n\n                [bold]for more information[/bold]:\n                    run '{self.prog} --help' or visit https://httpie.io/docs/cli\n                '''.rstrip()\n            )\n        )\n        self.exit(2)\n"
  },
  {
    "path": "httpie/cli/argtypes.py",
    "content": "import argparse\nimport getpass\nimport os\nimport sys\nfrom copy import deepcopy\nfrom typing import List, Optional, Union\n\nfrom .constants import DEFAULT_FORMAT_OPTIONS, SEPARATOR_CREDENTIALS\nfrom ..sessions import VALID_SESSION_NAME_PATTERN\n\n\nclass KeyValueArg:\n    \"\"\"Base key-value pair parsed from CLI.\"\"\"\n\n    def __init__(self, key: str, value: Optional[str], sep: str, orig: str):\n        self.key = key\n        self.value = value\n        self.sep = sep\n        self.orig = orig\n\n    def __eq__(self, other: 'KeyValueArg'):\n        return self.__dict__ == other.__dict__\n\n    def __repr__(self):\n        return repr(self.__dict__)\n\n\nclass SessionNameValidator:\n\n    def __init__(self, error_message: str):\n        self.error_message = error_message\n\n    def __call__(self, value: str) -> str:\n        # Session name can be a path or just a name.\n        if (os.path.sep not in value\n                and not VALID_SESSION_NAME_PATTERN.search(value)):\n            raise argparse.ArgumentError(None, self.error_message)\n        return value\n\n\nclass Escaped(str):\n    \"\"\"Represents an escaped character.\"\"\"\n\n    def __repr__(self):\n        return f\"Escaped({repr(str(self))})\"\n\n\nclass KeyValueArgType:\n    \"\"\"A key-value pair argument type used with `argparse`.\n\n    Parses a key-value arg and constructs a `KeyValueArg` instance.\n    Used for headers, form data, and other key-value pair types.\n\n    \"\"\"\n\n    key_value_class = KeyValueArg\n\n    def __init__(self, *separators: str):\n        self.separators = separators\n        self.special_characters = set()\n        for separator in separators:\n            self.special_characters.update(separator)\n\n    def __call__(self, s: str) -> KeyValueArg:\n        \"\"\"Parse raw string arg and return `self.key_value_class` instance.\n\n        The best of `self.separators` is determined (first found, longest).\n        Back slash escaped characters aren't considered as separators\n        (or parts thereof). Literal back slash characters have to be escaped\n        as well (r'\\\\').\n\n        \"\"\"\n        tokens = self.tokenize(s)\n\n        # Sorting by length ensures that the longest one will be\n        # chosen as it will overwrite any shorter ones starting\n        # at the same position in the `found` dictionary.\n        separators = sorted(self.separators, key=len)\n\n        for i, token in enumerate(tokens):\n\n            if isinstance(token, Escaped):\n                continue\n\n            found = {}\n            for sep in separators:\n                pos = token.find(sep)\n                if pos != -1:\n                    found[pos] = sep\n\n            if found:\n                # Starting first, longest separator found.\n                sep = found[min(found.keys())]\n\n                key, value = token.split(sep, 1)\n\n                # Any preceding tokens are part of the key.\n                key = ''.join(tokens[:i]) + key\n\n                # Any following tokens are part of the value.\n                value += ''.join(tokens[i + 1:])\n\n                break\n\n        else:\n            raise argparse.ArgumentTypeError(f'{s!r} is not a valid value')\n\n        return self.key_value_class(key=key, value=value, sep=sep, orig=s)\n\n    def tokenize(self, s: str) -> List[Union[str, Escaped]]:\n        r\"\"\"Tokenize the raw arg string\n\n        There are only two token types - strings and escaped characters:\n\n        >>> KeyValueArgType('=').tokenize(r'foo\\=bar\\\\baz')\n        ['foo', Escaped('='), 'bar\\\\\\\\baz']\n\n        \"\"\"\n        tokens = ['']\n        characters = iter(s)\n        for char in characters:\n            if char == '\\\\':\n                char = next(characters, '')\n                if char not in self.special_characters:\n                    tokens[-1] += '\\\\' + char\n                else:\n                    tokens.extend([Escaped(char), ''])\n            else:\n                tokens[-1] += char\n        return tokens\n\n\nclass PromptMixin:\n    def _prompt_password(self, prompt: str) -> str:\n        prompt_text = f'http: {prompt}: '\n        try:\n            return self._getpass(prompt_text)\n        except (EOFError, KeyboardInterrupt):\n            sys.stderr.write('\\n')\n            sys.exit(0)\n\n    @staticmethod\n    def _getpass(prompt):\n        # To allow easy mocking.\n        return getpass.getpass(str(prompt))\n\n\nclass SSLCredentials(PromptMixin):\n    \"\"\"Represents the passphrase for the certificate's key.\"\"\"\n\n    def __init__(self, value: Optional[str]) -> None:\n        self.value = value\n\n    def prompt_password(self, key_file: str) -> None:\n        self.value = self._prompt_password(f'passphrase for {key_file}')\n\n\nclass AuthCredentials(KeyValueArg, PromptMixin):\n    \"\"\"Represents parsed credentials.\"\"\"\n\n    def has_password(self) -> bool:\n        return self.value is not None\n\n    def prompt_password(self, host: str) -> None:\n        self.value = self._prompt_password(f'password for {self.key}@{host}:')\n\n\nclass AuthCredentialsArgType(KeyValueArgType):\n    \"\"\"A key-value arg type that parses credentials.\"\"\"\n\n    key_value_class = AuthCredentials\n\n    def __call__(self, s):\n        \"\"\"Parse credentials from `s`.\n\n        (\"username\" or \"username:password\").\n\n        \"\"\"\n        try:\n            return super().__call__(s)\n        except argparse.ArgumentTypeError:\n            # No password provided, will prompt for it later.\n            return self.key_value_class(\n                key=s,\n                value=None,\n                sep=SEPARATOR_CREDENTIALS,\n                orig=s\n            )\n\n\nparse_auth = AuthCredentialsArgType(SEPARATOR_CREDENTIALS)\n\n\ndef readable_file_arg(filename):\n    try:\n        with open(filename, 'rb'):\n            return filename\n    except OSError as ex:\n        raise argparse.ArgumentTypeError(f'{ex.filename}: {ex.strerror}')\n\n\ndef parse_format_options(s: str, defaults: Optional[dict]) -> dict:\n    \"\"\"\n    Parse `s` and update `defaults` with the parsed values.\n\n    >>> parse_format_options(\n    ... defaults={'json': {'indent': 4, 'sort_keys': True}},\n    ... s='json.indent:2,json.sort_keys:False',\n    ... )\n    {'json': {'indent': 2, 'sort_keys': False}}\n\n    \"\"\"\n    value_map = {\n        'true': True,\n        'false': False,\n    }\n    options = deepcopy(defaults or {})\n    for option in s.split(','):\n        try:\n            path, value = option.lower().split(':')\n            section, key = path.split('.')\n        except ValueError:\n            raise argparse.ArgumentTypeError(f'invalid option {option!r}')\n\n        if value in value_map:\n            parsed_value = value_map[value]\n        else:\n            if value.isnumeric():\n                parsed_value = int(value)\n            else:\n                parsed_value = value\n\n        if defaults is None:\n            options.setdefault(section, {})\n        else:\n            try:\n                default_value = defaults[section][key]\n            except KeyError:\n                raise argparse.ArgumentTypeError(\n                    f'invalid key {path!r}')\n\n            default_type, parsed_type = type(default_value), type(parsed_value)\n            if parsed_type is not default_type:\n                raise argparse.ArgumentTypeError(\n                    'invalid value'\n                    f' {value!r} in {option!r}'\n                    f' (expected {default_type.__name__}'\n                    f' got {parsed_type.__name__})'\n                )\n\n        options[section][key] = parsed_value\n\n    return options\n\n\nPARSED_DEFAULT_FORMAT_OPTIONS = parse_format_options(\n    s=','.join(DEFAULT_FORMAT_OPTIONS),\n    defaults=None,\n)\n\n\ndef response_charset_type(encoding: str) -> str:\n    try:\n        ''.encode(encoding)\n    except LookupError:\n        raise argparse.ArgumentTypeError(\n            f'{encoding!r} is not a supported encoding')\n    return encoding\n\n\ndef response_mime_type(mime_type: str) -> str:\n    if mime_type.count('/') != 1:\n        raise argparse.ArgumentTypeError(\n            f'{mime_type!r} doesn’t look like a mime type; use type/subtype')\n    return mime_type\n"
  },
  {
    "path": "httpie/cli/constants.py",
    "content": "\"\"\"Parsing and processing of CLI input (args, auth credentials, files, stdin).\n\n\"\"\"\nimport enum\nimport re\n\n\nURL_SCHEME_RE = re.compile(r'^[a-z][a-z0-9.+-]*://', re.IGNORECASE)\n\nHTTP_POST = 'POST'\nHTTP_GET = 'GET'\nHTTP_OPTIONS = 'OPTIONS'\n\n# Various separators used in args\nSEPARATOR_HEADER = ':'\nSEPARATOR_HEADER_EMPTY = ';'\nSEPARATOR_CREDENTIALS = ':'\nSEPARATOR_PROXY = ':'\nSEPARATOR_HEADER_EMBED = ':@'\nSEPARATOR_DATA_STRING = '='\nSEPARATOR_DATA_RAW_JSON = ':='\nSEPARATOR_FILE_UPLOAD = '@'\nSEPARATOR_FILE_UPLOAD_TYPE = ';type='  # in already parsed file upload path only\nSEPARATOR_DATA_EMBED_FILE_CONTENTS = '=@'\nSEPARATOR_DATA_EMBED_RAW_JSON_FILE = ':=@'\nSEPARATOR_QUERY_PARAM = '=='\nSEPARATOR_QUERY_EMBED_FILE = '==@'\n\n# Separators that become request data\nSEPARATOR_GROUP_DATA_ITEMS = frozenset({\n    SEPARATOR_DATA_STRING,\n    SEPARATOR_DATA_RAW_JSON,\n    SEPARATOR_FILE_UPLOAD,\n    SEPARATOR_DATA_EMBED_FILE_CONTENTS,\n    SEPARATOR_DATA_EMBED_RAW_JSON_FILE\n})\n\nSEPARATORS_GROUP_MULTIPART = frozenset({\n    SEPARATOR_DATA_STRING,\n    SEPARATOR_DATA_EMBED_FILE_CONTENTS,\n    SEPARATOR_FILE_UPLOAD,\n})\n\n# Separators for items whose value is a filename to be embedded\nSEPARATOR_GROUP_DATA_EMBED_ITEMS = frozenset({\n    SEPARATOR_HEADER_EMBED,\n    SEPARATOR_QUERY_EMBED_FILE,\n    SEPARATOR_DATA_EMBED_FILE_CONTENTS,\n    SEPARATOR_DATA_EMBED_RAW_JSON_FILE,\n})\n\n# Separators for nested JSON items\nSEPARATOR_GROUP_NESTED_JSON_ITEMS = frozenset([\n    SEPARATOR_DATA_STRING,\n    SEPARATOR_DATA_RAW_JSON,\n    SEPARATOR_DATA_EMBED_FILE_CONTENTS,\n    SEPARATOR_DATA_EMBED_RAW_JSON_FILE,\n])\n\n# Separators allowed in ITEM arguments\nSEPARATOR_GROUP_ALL_ITEMS = frozenset({\n    SEPARATOR_HEADER,\n    SEPARATOR_HEADER_EMPTY,\n    SEPARATOR_HEADER_EMBED,\n    SEPARATOR_QUERY_PARAM,\n    SEPARATOR_QUERY_EMBED_FILE,\n    SEPARATOR_DATA_STRING,\n    SEPARATOR_DATA_RAW_JSON,\n    SEPARATOR_FILE_UPLOAD,\n    SEPARATOR_DATA_EMBED_FILE_CONTENTS,\n    SEPARATOR_DATA_EMBED_RAW_JSON_FILE,\n})\n\n# Output options\nOUT_REQ_HEAD = 'H'\nOUT_REQ_BODY = 'B'\nOUT_RESP_HEAD = 'h'\nOUT_RESP_BODY = 'b'\nOUT_RESP_META = 'm'\n\nBASE_OUTPUT_OPTIONS = frozenset({\n    OUT_REQ_HEAD,\n    OUT_REQ_BODY,\n    OUT_RESP_HEAD,\n    OUT_RESP_BODY,\n})\n\nOUTPUT_OPTIONS = frozenset({\n    *BASE_OUTPUT_OPTIONS,\n    OUT_RESP_META,\n})\n\n# Pretty\n\n\nclass PrettyOptions(enum.Enum):\n    STDOUT_TTY_ONLY = enum.auto()\n\n\nPRETTY_MAP = {\n    'all': ['format', 'colors'],\n    'colors': ['colors'],\n    'format': ['format'],\n    'none': []\n}\nPRETTY_STDOUT_TTY_ONLY = PrettyOptions.STDOUT_TTY_ONLY\n\n\nDEFAULT_FORMAT_OPTIONS = [\n    'headers.sort:true',\n    'json.format:true',\n    'json.indent:4',\n    'json.sort_keys:true',\n    'xml.format:true',\n    'xml.indent:2',\n]\nSORTED_FORMAT_OPTIONS = [\n    'headers.sort:true',\n    'json.sort_keys:true',\n]\nSORTED_FORMAT_OPTIONS_STRING = ','.join(SORTED_FORMAT_OPTIONS)\nUNSORTED_FORMAT_OPTIONS_STRING = ','.join(\n    option.replace('true', 'false') for option in SORTED_FORMAT_OPTIONS)\n\n# Defaults\nOUTPUT_OPTIONS_DEFAULT = OUT_RESP_HEAD + OUT_RESP_BODY\nOUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED = OUT_RESP_BODY\nOUTPUT_OPTIONS_DEFAULT_OFFLINE = OUT_REQ_HEAD + OUT_REQ_BODY\n\n\nclass RequestType(enum.Enum):\n    FORM = enum.auto()\n    MULTIPART = enum.auto()\n    JSON = enum.auto()\n"
  },
  {
    "path": "httpie/cli/definition.py",
    "content": "from __future__ import annotations\n\nimport os\nimport textwrap\nfrom argparse import FileType\n\nfrom httpie import __doc__, __version__\nfrom httpie.cli.argtypes import (KeyValueArgType, SessionNameValidator,\n                                 SSLCredentials, readable_file_arg,\n                                 response_charset_type, response_mime_type)\nfrom httpie.cli.constants import (BASE_OUTPUT_OPTIONS, DEFAULT_FORMAT_OPTIONS,\n                                  OUT_REQ_BODY, OUT_REQ_HEAD, OUT_RESP_BODY,\n                                  OUT_RESP_HEAD, OUT_RESP_META, OUTPUT_OPTIONS,\n                                  OUTPUT_OPTIONS_DEFAULT, PRETTY_MAP,\n                                  PRETTY_STDOUT_TTY_ONLY,\n                                  SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_PROXY,\n                                  SORTED_FORMAT_OPTIONS_STRING,\n                                  UNSORTED_FORMAT_OPTIONS_STRING, RequestType)\nfrom httpie.cli.options import ParserSpec, Qualifiers, to_argparse\nfrom httpie.output.formatters.colors import (AUTO_STYLE, DEFAULT_STYLE, BUNDLED_STYLES,\n                                             get_available_styles)\nfrom httpie.plugins.builtin import BuiltinAuthPlugin\nfrom httpie.plugins.registry import plugin_manager\nfrom httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS_STRING\n\n\n# Man pages are static (built when making a release).\n# We use this check to not include generated, system-specific information there (e.g., default --ciphers).\nIS_MAN_PAGE = bool(os.environ.get('HTTPIE_BUILDING_MAN_PAGES'))\n\n\noptions = ParserSpec(\n    'http',\n    description=f'{__doc__.strip()} <https://httpie.io>',\n    epilog=\"\"\"\n    For every --OPTION there is also a --no-OPTION that reverts OPTION\n    to its default value.\n\n    Suggestions and bug reports are greatly appreciated:\n        https://github.com/httpie/cli/issues\n    \"\"\",\n    source_file=__file__\n)\n\n#######################################################################\n# Positional arguments.\n#######################################################################\n\npositional_arguments = options.add_group(\n    'Positional arguments',\n    description=\"\"\"\n    These arguments come after any flags and in the order they are listed here.\n    Only URL is required.\n    \"\"\",\n)\n\npositional_arguments.add_argument(\n    dest='method',\n    metavar='METHOD',\n    nargs=Qualifiers.OPTIONAL,\n    default=None,\n    short_help='The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).',\n    help=\"\"\"\n    The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).\n\n    This argument can be omitted in which case HTTPie will use POST if there\n    is some data to be sent, otherwise GET:\n\n        $ http example.org               # => GET\n        $ http example.org hello=world   # => POST\n\n    \"\"\",\n)\npositional_arguments.add_argument(\n    dest='url',\n    metavar='URL',\n    short_help='The request URL.',\n    help=\"\"\"\n    The request URL. Scheme defaults to 'http://' if the URL\n    does not include one. (You can override this with: --default-scheme=http/https)\n\n    You can also use a shorthand for localhost\n\n        $ http :3000                    # => http://localhost:3000\n        $ http :/foo                    # => http://localhost/foo\n\n    \"\"\",\n)\npositional_arguments.add_argument(\n    dest='request_items',\n    metavar='REQUEST_ITEM',\n    nargs=Qualifiers.ZERO_OR_MORE,\n    default=None,\n    type=KeyValueArgType(*SEPARATOR_GROUP_ALL_ITEMS),\n    short_help=(\n        'HTTPie’s request items syntax for specifying HTTP headers, JSON/Form'\n        'data, files, and URL parameters.'\n    ),\n    nested_options=[\n        ('HTTP Headers', 'Name:Value', 'Arbitrary HTTP header, e.g X-API-Token:123'),\n        ('URL Parameters', 'name==value', 'Querystring parameter to the URL, e.g limit==50'),\n        ('Data Fields', 'field=value', 'Data fields to be serialized as JSON (default) or Form Data (with --form)'),\n        ('Raw JSON Fields', 'field:=json', 'Data field for real JSON types.'),\n        ('File upload Fields', 'field@/dir/file', 'Path field for uploading a file.'),\n    ],\n    help=r\"\"\"\n    Optional key-value pairs to be included in the request. The separator used\n    determines the type:\n\n    ':' HTTP headers:\n\n        Referer:https://httpie.io  Cookie:foo=bar  User-Agent:bacon/1.0\n\n    '==' URL parameters to be appended to the request URI:\n\n        search==httpie\n\n    '=' Data fields to be serialized into a JSON object (with --json, -j)\n        or form data (with --form, -f):\n\n        name=HTTPie  language=Python  description='CLI HTTP client'\n\n    ':=' Non-string JSON data fields (only with --json, -j):\n\n        awesome:=true  amount:=42  colors:='[\"red\", \"green\", \"blue\"]'\n\n    '@' Form file fields (only with --form or --multipart):\n\n        cv@~/Documents/CV.pdf\n        cv@'~/Documents/CV.pdf;type=application/pdf'\n\n    '=@' A data field like '=', but takes a file path and embeds its content:\n\n        essay=@Documents/essay.txt\n\n    ':=@' A raw JSON field like ':=', but takes a file path and embeds its content:\n\n        package:=@./package.json\n\n    You can use a backslash to escape a colliding separator in the field name:\n\n        field-name-with\\:colon=value\n\n    \"\"\",\n)\n\n#######################################################################\n# Content type.\n#######################################################################\n\ncontent_types = options.add_group('Predefined content types')\n\ncontent_types.add_argument(\n    '--json',\n    '-j',\n    action='store_const',\n    const=RequestType.JSON,\n    dest='request_type',\n    short_help='(default) Serialize data items from the command line as a JSON object.',\n    help=\"\"\"\n    (default) Data items from the command line are serialized as a JSON object.\n    The Content-Type and Accept headers are set to application/json\n    (if not specified).\n\n    \"\"\",\n)\ncontent_types.add_argument(\n    '--form',\n    '-f',\n    action='store_const',\n    const=RequestType.FORM,\n    dest='request_type',\n    short_help='Serialize data items from the command line as form field data.',\n    help=\"\"\"\n    Data items from the command line are serialized as form fields.\n\n    The Content-Type is set to application/x-www-form-urlencoded (if not\n    specified). The presence of any file fields results in a\n    multipart/form-data request.\n\n    \"\"\",\n)\ncontent_types.add_argument(\n    '--multipart',\n    action='store_const',\n    const=RequestType.MULTIPART,\n    dest='request_type',\n    short_help=(\n        'Similar to --form, but always sends a multipart/form-data '\n        'request (i.e., even without files).'\n    )\n)\ncontent_types.add_argument(\n    '--boundary',\n    short_help=(\n        'Specify a custom boundary string for multipart/form-data requests. '\n        'Only has effect only together with --form.'\n    )\n)\ncontent_types.add_argument(\n    '--raw',\n    short_help='Pass raw request data without extra processing.',\n    help=\"\"\"\n    This option allows you to pass raw request data without extra processing\n    (as opposed to the structured request items syntax):\n\n        $ http --raw='data' pie.dev/post\n\n    You can achieve the same by piping the data via stdin:\n\n        $ echo data | http pie.dev/post\n\n    Or have HTTPie load the raw data from a file:\n\n        $ http pie.dev/post @data.txt\n\n\n    \"\"\",\n)\n\n#######################################################################\n# Content processing.\n#######################################################################\n\nprocessing_options = options.add_group('Content processing options')\n\nprocessing_options.add_argument(\n    '--compress',\n    '-x',\n    action='count',\n    default=0,\n    short_help='Compress the content with Deflate algorithm.',\n    help=\"\"\"\n    Content compressed (encoded) with Deflate algorithm.\n    The Content-Encoding header is set to deflate.\n\n    Compression is skipped if it appears that compression ratio is\n    negative. Compression can be forced by repeating the argument.\n\n    \"\"\",\n)\n\n\n#######################################################################\n# Output processing\n#######################################################################\n\n\ndef format_style_help(available_styles, *, isolation_mode: bool = False):\n    text = \"\"\"\n    Output coloring style (default is \"{default}\"). It can be one of:\n\n        {available_styles}\n    \"\"\"\n    if isolation_mode:\n        text += '\\n\\n'\n        text += 'For finding out all available styles in your system, try:\\n\\n'\n        text += '    $ http --style\\n'\n    text += textwrap.dedent(\"\"\"\n        The \"{auto_style}\" style follows your terminal's ANSI color styles.\n        For non-{auto_style} styles to work properly, please make sure that the\n        $TERM environment variable is set to \"xterm-256color\" or similar\n        (e.g., via `export TERM=xterm-256color' in your ~/.bashrc).\n    \"\"\")\n\n    if isolation_mode:\n        available_styles = sorted(BUNDLED_STYLES)\n\n    available_styles_text = '\\n'.join(\n        f'    {line.strip()}'\n        for line in textwrap.wrap(', '.join(available_styles), 60)\n    ).strip()\n    return text.format(\n        default=DEFAULT_STYLE,\n        available_styles=available_styles_text,\n        auto_style=AUTO_STYLE,\n    )\n\n\n_sorted_kwargs = {\n    'action': 'append_const',\n    'const': SORTED_FORMAT_OPTIONS_STRING,\n    'dest': 'format_options',\n}\n_unsorted_kwargs = {\n    'action': 'append_const',\n    'const': UNSORTED_FORMAT_OPTIONS_STRING,\n    'dest': 'format_options',\n}\n\noutput_processing = options.add_group('Output processing')\n\noutput_processing.add_argument(\n    '--pretty',\n    dest='prettify',\n    default=PRETTY_STDOUT_TTY_ONLY,\n    choices=sorted(PRETTY_MAP.keys()),\n    short_help='Control the processing of console outputs.',\n    help=\"\"\"\n    Controls output processing. The value can be \"none\" to not prettify\n    the output (default for redirected output), \"all\" to apply both colors\n    and formatting (default for terminal output), \"colors\", or \"format\".\n\n    \"\"\",\n)\noutput_processing.add_argument(\n    '--style',\n    '-s',\n    dest='style',\n    metavar='STYLE',\n    default=DEFAULT_STYLE,\n    action='lazy_choices',\n    getter=get_available_styles,\n    short_help=f'Output coloring style (default is \"{DEFAULT_STYLE}\").',\n    help_formatter=format_style_help,\n)\n\n# The closest approx. of the documented resetting to default via --no-<option>.\n# We hide them from the doc because they act only as low-level aliases here.\noutput_processing.add_argument(\n    '--no-unsorted', **_sorted_kwargs, help=Qualifiers.SUPPRESS\n)\noutput_processing.add_argument(\n    '--no-sorted', **_unsorted_kwargs, help=Qualifiers.SUPPRESS\n)\n\noutput_processing.add_argument(\n    '--unsorted',\n    **_unsorted_kwargs,\n    short_help='Disables all sorting while formatting output.',\n    help=f\"\"\"\n    Disables all sorting while formatting output. It is a shortcut for:\n\n        --format-options={UNSORTED_FORMAT_OPTIONS_STRING}\n\n    \"\"\",\n)\noutput_processing.add_argument(\n    '--sorted',\n    **_sorted_kwargs,\n    short_help='Re-enables all sorting options while formatting output.',\n    help=f\"\"\"\n    Re-enables all sorting options while formatting output. It is a shortcut for:\n\n        --format-options={SORTED_FORMAT_OPTIONS_STRING}\n\n    \"\"\",\n)\noutput_processing.add_argument(\n    '--response-charset',\n    metavar='ENCODING',\n    type=response_charset_type,\n    short_help='Override the response encoding for terminal display purposes.',\n    help=\"\"\"\n    Override the response encoding for terminal display purposes, e.g.:\n\n        --response-charset=utf8\n        --response-charset=big5\n\n    \"\"\",\n)\noutput_processing.add_argument(\n    '--response-mime',\n    metavar='MIME_TYPE',\n    type=response_mime_type,\n    short_help='Override the response mime type for coloring and formatting for the terminal.',\n    help=\"\"\"\n    Override the response mime type for coloring and formatting for the terminal, e.g.:\n\n        --response-mime=application/json\n        --response-mime=text/xml\n\n    \"\"\",\n)\noutput_processing.add_argument(\n    '--format-options',\n    action='append',\n    short_help='Controls output formatting.',\n    help=\"\"\"\n    Controls output formatting. Only relevant when formatting is enabled\n    through (explicit or implied) --pretty=all or --pretty=format.\n    The following are the default options:\n\n        {option_list}\n\n    You may use this option multiple times, as well as specify multiple\n    comma-separated options at the same time. For example, this modifies the\n    settings to disable the sorting of JSON keys, and sets the indent size to 2:\n\n        --format-options json.sort_keys:false,json.indent:2\n\n    This is something you will typically put into your config file.\n\n    \"\"\".format(\n        option_list='\\n'.join(\n            f'        {option}' for option in DEFAULT_FORMAT_OPTIONS\n        ).strip()\n    ),\n)\n\n#######################################################################\n# Output options\n#######################################################################\n\noutput_options = options.add_group('Output options')\n\noutput_options.add_argument(\n    '--print',\n    '-p',\n    dest='output_options',\n    metavar='WHAT',\n    short_help='Options to specify what the console output should contain.',\n    help=f\"\"\"\n    String specifying what the output should contain:\n\n        '{OUT_REQ_HEAD}' request headers\n        '{OUT_REQ_BODY}' request body\n        '{OUT_RESP_HEAD}' response headers\n        '{OUT_RESP_BODY}' response body\n        '{OUT_RESP_META}' response metadata\n\n    The default behaviour is '{OUTPUT_OPTIONS_DEFAULT}' (i.e., the response\n    headers and body is printed), if standard output is not redirected.\n    If the output is piped to another program or to a file, then only the\n    response body is printed by default.\n\n    \"\"\",\n)\noutput_options.add_argument(\n    '--headers',\n    '-h',\n    dest='output_options',\n    action='store_const',\n    const=OUT_RESP_HEAD,\n    short_help='Print only the response headers.',\n    help=f\"\"\"\n    Print only the response headers. Shortcut for --print={OUT_RESP_HEAD}.\n\n    \"\"\",\n)\noutput_options.add_argument(\n    '--meta',\n    '-m',\n    dest='output_options',\n    action='store_const',\n    const=OUT_RESP_META,\n    short_help='Print only the response metadata.',\n    help=f\"\"\"\n    Print only the response metadata. Shortcut for --print={OUT_RESP_META}.\n\n    \"\"\",\n)\noutput_options.add_argument(\n    '--body',\n    '-b',\n    dest='output_options',\n    action='store_const',\n    const=OUT_RESP_BODY,\n    short_help='Print only the response body.',\n    help=f\"\"\"\n    Print only the response body. Shortcut for --print={OUT_RESP_BODY}.\n\n    \"\"\",\n)\n\noutput_options.add_argument(\n    '--verbose',\n    '-v',\n    dest='verbose',\n    action='count',\n    default=0,\n    short_help='Make output more verbose.',\n    help=f\"\"\"\n    Verbose output. For the level one (with single `-v`/`--verbose`), print\n    the whole request as well as the response. Also print any intermediary\n    requests/responses (such as redirects). For the second level and higher,\n    print these as well as the response metadata.\n\n    Level one is a shortcut for: --all --print={''.join(sorted(BASE_OUTPUT_OPTIONS))}\n    Level two is a shortcut for: --all --print={''.join(sorted(OUTPUT_OPTIONS))}\n    \"\"\",\n)\noutput_options.add_argument(\n    '--all',\n    default=False,\n    action='store_true',\n    short_help='Show any intermediary requests/responses.',\n    help=\"\"\"\n    By default, only the final request/response is shown. Use this flag to show\n    any intermediary requests/responses as well. Intermediary requests include\n    followed redirects (with --follow), the first unauthorized request when\n    Digest auth is used (--auth=digest), etc.\n\n    \"\"\",\n)\noutput_options.add_argument(\n    '--history-print',\n    '-P',\n    dest='output_options_history',\n    metavar='WHAT',\n    help=Qualifiers.SUPPRESS,\n)\noutput_options.add_argument(\n    '--stream',\n    '-S',\n    action='store_true',\n    default=False,\n    short_help='Always stream the response body by line, i.e., behave like `tail -f`.',\n    help=\"\"\"\n    Always stream the response body by line, i.e., behave like `tail -f'.\n\n    Without --stream and with --pretty (either set or implied),\n    HTTPie fetches the whole response before it outputs the processed data.\n\n    Set this option when you want to continuously display a prettified\n    long-lived response, such as one from the Twitter streaming API.\n\n    It is useful also without --pretty: It ensures that the output is flushed\n    more often and in smaller chunks.\n\n    \"\"\",\n)\noutput_options.add_argument(\n    '--output',\n    '-o',\n    type=FileType('a+b'),\n    dest='output_file',\n    metavar='FILE',\n    short_help='Save output to FILE instead of stdout.',\n    help=\"\"\"\n    Save output to FILE instead of stdout. If --download is also set, then only\n    the response body is saved to FILE. Other parts of the HTTP exchange are\n    printed to stderr.\n\n    \"\"\",\n)\n\noutput_options.add_argument(\n    '--download',\n    '-d',\n    action='store_true',\n    default=False,\n    short_help='Download the body to a file instead of printing it to stdout.',\n    help=\"\"\"\n    Do not print the response body to stdout. Rather, download it and store it\n    in a file. The filename is guessed unless specified with --output\n    [filename]. This action is similar to the default behaviour of wget.\n\n    \"\"\",\n)\noutput_options.add_argument(\n    '--continue',\n    '-c',\n    dest='download_resume',\n    action='store_true',\n    default=False,\n    short_help='Resume an interrupted download (--output needs to be specified).',\n    help=\"\"\"\n    Resume an interrupted download. Note that the --output option needs to be\n    specified as well.\n\n    \"\"\",\n)\noutput_options.add_argument(\n    '--quiet',\n    '-q',\n    action='count',\n    default=0,\n    short_help='Do not print to stdout or stderr, except for errors and warnings when provided once.',\n    help=\"\"\"\n    Do not print to stdout or stderr, except for errors and warnings when provided once.\n    Provide twice to suppress warnings as well.\n    stdout is still redirected if --output is specified.\n    Flag doesn't affect behaviour of download beyond not printing to terminal.\n\n    \"\"\",\n)\n\n#######################################################################\n# Sessions\n#######################################################################\n\nsession_name_validator = SessionNameValidator(\n    'Session name contains invalid characters.'\n)\n\nsessions = options.add_group('Sessions', is_mutually_exclusive=True)\n\nsessions.add_argument(\n    '--session',\n    metavar='SESSION_NAME_OR_PATH',\n    type=session_name_validator,\n    short_help='Create, or reuse and update a session.',\n    help=\"\"\"\n    Create, or reuse and update a session. Within a session, custom headers,\n    auth credential, as well as any cookies sent by the server persist between\n    requests.\n\n    Session files are stored in:\n\n        [HTTPIE_CONFIG_DIR]/<HOST>/<SESSION_NAME>.json.\n\n    See the following page to find out your default HTTPIE_CONFIG_DIR:\n\n        https://httpie.io/docs/cli/config-file-directory\n    \"\"\",\n)\nsessions.add_argument(\n    '--session-read-only',\n    metavar='SESSION_NAME_OR_PATH',\n    type=session_name_validator,\n    short_help='Create or read a session without updating it',\n    help=\"\"\"\n    Create or read a session without updating it form the request/response\n    exchange.\n\n    \"\"\",\n)\n\n\n#######################################################################\n# Authentication\n#######################################################################\n\n\ndef format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):\n    text = \"\"\"\n    The authentication mechanism to be used. Defaults to \"{default}\".\n\n    {auth_types}\n    \"\"\"\n\n    auth_plugins = list(auth_plugins_mapping.values())\n    if isolation_mode:\n        auth_plugins = [\n            auth_plugin\n            for auth_plugin in auth_plugins\n            if issubclass(auth_plugin, BuiltinAuthPlugin)\n        ]\n        text += '\\n'\n        text += 'To see all available auth types on your system, including ones installed via plugins, run:\\n\\n'\n        text += '    $ http --auth-type'\n\n    auth_types = '\\n\\n    '.join(\n        '\"{type}\": {name}{package}{description}'.format(\n            type=plugin.auth_type,\n            name=plugin.name,\n            package=(\n                ''\n                if issubclass(plugin, BuiltinAuthPlugin)\n                else f' (provided by {plugin.package_name})'\n            ),\n            description=(\n                ''\n                if not plugin.description\n                else '\\n      '\n                     + ('\\n      '.join(textwrap.wrap(plugin.description)))\n            ),\n        )\n        for plugin in auth_plugins\n    )\n\n    return text.format(\n        default=auth_plugins[0].auth_type,\n        auth_types=auth_types,\n    )\n\n\nauthentication = options.add_group('Authentication')\n\nauthentication.add_argument(\n    '--auth',\n    '-a',\n    default=None,\n    metavar='USER[:PASS] | TOKEN',\n    short_help='Credentials for the selected (-A) authentication method.',\n    help=\"\"\"\n    For username/password based authentication mechanisms (e.g\n    basic auth or digest auth) if only the username is provided\n    (-a username), HTTPie will prompt for the password.\n\n    \"\"\",\n)\nauthentication.add_argument(\n    '--auth-type',\n    '-A',\n    action='lazy_choices',\n    default=None,\n    getter=plugin_manager.get_auth_plugin_mapping,\n    sort=True,\n    cache=False,\n    short_help='The authentication mechanism to be used.',\n    help_formatter=format_auth_help,\n)\nauthentication.add_argument(\n    '--ignore-netrc',\n    default=False,\n    action='store_true',\n    short_help='Ignore credentials from .netrc.'\n)\n\n#######################################################################\n# Network\n#######################################################################\n\nnetwork = options.add_group('Network')\n\nnetwork.add_argument(\n    '--offline',\n    default=False,\n    action='store_true',\n    short_help='Build the request and print it but don’t actually send it.'\n)\nnetwork.add_argument(\n    '--proxy',\n    default=[],\n    action='append',\n    metavar='PROTOCOL:PROXY_URL',\n    type=KeyValueArgType(SEPARATOR_PROXY),\n    short_help='String mapping of protocol to the URL of the proxy.',\n    help=\"\"\"\n    String mapping protocol to the URL of the proxy\n    (e.g. http:http://foo.bar:3128). You can specify multiple proxies with\n    different protocols. The environment variables $ALL_PROXY, $HTTP_PROXY,\n    and $HTTPS_proxy are supported as well.\n\n    \"\"\",\n)\nnetwork.add_argument(\n    '--follow',\n    '-F',\n    default=False,\n    action='store_true',\n    short_help='Follow 30x Location redirects.'\n)\n\nnetwork.add_argument(\n    '--max-redirects',\n    type=int,\n    default=30,\n    short_help='The maximum number of redirects that should be followed (with --follow).',\n    help=\"\"\"\n    By default, requests have a limit of 30 redirects (works with --follow).\n\n    \"\"\",\n)\nnetwork.add_argument(\n    '--max-headers',\n    type=int,\n    default=0,\n    short_help=(\n        'The maximum number of response headers to be read before '\n        'giving up (default 0, i.e., no limit).'\n    )\n)\n\nnetwork.add_argument(\n    '--timeout',\n    type=float,\n    default=0,\n    metavar='SECONDS',\n    short_help='The connection timeout of the request in seconds.',\n    help=\"\"\"\n    The connection timeout of the request in seconds.\n    The default value is 0, i.e., there is no timeout limit.\n    This is not a time limit on the entire response download;\n    rather, an error is reported if the server has not issued a response for\n    timeout seconds (more precisely, if no bytes have been received on\n    the underlying socket for timeout seconds).\n\n    \"\"\",\n)\nnetwork.add_argument(\n    '--check-status',\n    default=False,\n    action='store_true',\n    short_help='Exit with an error status code if the server replies with an error.',\n    help=\"\"\"\n    By default, HTTPie exits with 0 when no network or other fatal errors\n    occur. This flag instructs HTTPie to also check the HTTP status code and\n    exit with an error if the status indicates one.\n\n    When the server replies with a 4xx (Client Error) or 5xx (Server Error)\n    status code, HTTPie exits with 4 or 5 respectively. If the response is a\n    3xx (Redirect) and --follow hasn't been set, then the exit status is 3.\n    Also an error message is written to stderr if stdout is redirected.\n\n    \"\"\",\n)\nnetwork.add_argument(\n    '--path-as-is',\n    default=False,\n    action='store_true',\n    short_help='Bypass dot segment (/../ or /./) URL squashing.'\n)\nnetwork.add_argument(\n    '--chunked',\n    default=False,\n    action='store_true',\n    short_help=(\n        'Enable streaming via chunked transfer encoding. '\n        'The Transfer-Encoding header is set to chunked.'\n    )\n)\n\n#######################################################################\n# SSL\n#######################################################################\n\nssl = options.add_group('SSL')\n\nssl.add_argument(\n    '--verify',\n    default='yes',\n    short_help='If \"no\", skip SSL verification. If a file path, use it as a CA bundle.',\n    help=\"\"\"\n    Set to \"no\" (or \"false\") to skip checking the host's SSL certificate.\n    Defaults to \"yes\" (\"true\"). You can also pass the path to a CA_BUNDLE file\n    for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment\n    variable instead.)\n    \"\"\",\n)\nssl.add_argument(\n    '--ssl',\n    dest='ssl_version',\n    choices=sorted(AVAILABLE_SSL_VERSION_ARG_MAPPING.keys()),\n    short_help='The desired protocol version to used.',\n    help=\"\"\"\n    The desired protocol version to use. This will default to\n    SSL v2.3 which will negotiate the highest protocol that both\n    the server and your installation of OpenSSL support. Available protocols\n    may vary depending on OpenSSL installation (only the supported ones\n    are shown here).\n\n    \"\"\",\n)\n\nCIPHERS_CURRENT_DEFAULTS = (\n    \"\"\"\n    See `http --help` for the default ciphers list on you system.\n\n    \"\"\"\n    if IS_MAN_PAGE else\n    f\"\"\"\n    By default, the following ciphers are used on your system:\n\n    {DEFAULT_SSL_CIPHERS_STRING}\n\n    \"\"\"\n)\nssl.add_argument(\n    '--ciphers',\n    short_help='A string in the OpenSSL cipher list format.',\n    help=f\"\"\"\n\n    A string in the OpenSSL cipher list format.\n\n    {CIPHERS_CURRENT_DEFAULTS}\n\n    \"\"\"\n)\nssl.add_argument(\n    '--cert',\n    default=None,\n    type=readable_file_arg,\n    short_help='Specifies a local cert to use as the client-side SSL certificate.',\n    help=\"\"\"\n    You can specify a local cert to use as client side SSL certificate.\n    This file may either contain both private key and certificate or you may\n    specify --cert-key separately.\n\n    \"\"\",\n)\nssl.add_argument(\n    '--cert-key',\n    default=None,\n    type=readable_file_arg,\n    short_help='The private key to use with SSL. Only needed if --cert is given.',\n    help=\"\"\"\n    The private key to use with SSL. Only needed if --cert is given and the\n    certificate file does not contain the private key.\n\n    \"\"\",\n)\n\nssl.add_argument(\n    '--cert-key-pass',\n    default=None,\n    type=SSLCredentials,\n    short_help='The passphrase to be used to with the given private key.',\n    help=\"\"\"\n    The passphrase to be used to with the given private key. Only needed if --cert-key\n    is given and the key file requires a passphrase.\n    If not provided, you’ll be prompted interactively.\n    \"\"\"\n)\n\n#######################################################################\n# Troubleshooting\n#######################################################################\n\ntroubleshooting = options.add_group('Troubleshooting')\ntroubleshooting.add_argument(\n    '--ignore-stdin',\n    '-I',\n    action='store_true',\n    default=False,\n    short_help='Do not attempt to read stdin'\n)\ntroubleshooting.add_argument(\n    '--help',\n    action='help',\n    default=Qualifiers.SUPPRESS,\n    short_help='Show this help message and exit.',\n)\ntroubleshooting.add_argument(\n    '--manual',\n    action='manual',\n    default=Qualifiers.SUPPRESS,\n    short_help='Show the full manual.',\n)\ntroubleshooting.add_argument(\n    '--version',\n    action='version',\n    version=__version__,\n    short_help='Show version and exit.',\n)\ntroubleshooting.add_argument(\n    '--traceback',\n    action='store_true',\n    default=False,\n    short_help='Prints the exception traceback should one occur.',\n)\ntroubleshooting.add_argument(\n    '--default-scheme',\n    default='http',\n    short_help='The default scheme to use if not specified in the URL.'\n)\ntroubleshooting.add_argument(\n    '--debug',\n    action='store_true',\n    default=False,\n    short_help='Print useful diagnostic information for bug reports.',\n    help=\"\"\"\n    Prints the exception traceback should one occur, as well as other\n    information useful for debugging HTTPie itself and for reporting bugs.\n\n    \"\"\",\n)\n\n#######################################################################\n# Finalization\n#######################################################################\n\noptions.finalize()\nparser = to_argparse(options)\n"
  },
  {
    "path": "httpie/cli/dicts.py",
    "content": "from collections import OrderedDict\n\nfrom multidict import MultiDict, CIMultiDict\n\n\nclass BaseMultiDict(MultiDict):\n    \"\"\"\n    Base class for all MultiDicts.\n    \"\"\"\n\n\nclass HTTPHeadersDict(CIMultiDict, BaseMultiDict):\n    \"\"\"\n    Headers are case-insensitive and multiple values are supported\n    through the `add()` API.\n    \"\"\"\n\n    def add(self, key, value):\n        \"\"\"\n        Add or update a new header.\n\n        If the given `value` is `None`, then all the previous\n        values will be overwritten and the value will be set\n        to `None`.\n        \"\"\"\n        if value is None:\n            self[key] = value\n            return None\n\n        # If the previous value for the given header is `None`\n        # then discard it since we are explicitly giving a new\n        # value for it.\n        if key in self and self.getone(key) is None:\n            self.popone(key)\n\n        super().add(key, value)\n\n    def remove_item(self, key, value):\n        \"\"\"\n        Remove a (key, value) pair from the dict.\n        \"\"\"\n        existing_values = self.popall(key)\n        existing_values.remove(value)\n\n        for value in existing_values:\n            self.add(key, value)\n\n\nclass RequestJSONDataDict(OrderedDict):\n    pass\n\n\nclass MultiValueOrderedDict(OrderedDict):\n    \"\"\"Multi-value dict for URL parameters and form data.\"\"\"\n\n    def __setitem__(self, key, value):\n        \"\"\"\n        If `key` is assigned more than once, `self[key]` holds a\n        `list` of all the values.\n\n        This allows having multiple fields with the same name in form\n        data and URL params.\n\n        \"\"\"\n        assert not isinstance(value, list)\n        if key not in self:\n            super().__setitem__(key, value)\n        else:\n            if not isinstance(self[key], list):\n                super().__setitem__(key, [self[key]])\n            self[key].append(value)\n\n    def items(self):\n        for key, values in super().items():\n            if not isinstance(values, list):\n                values = [values]\n            for value in values:\n                yield key, value\n\n\nclass RequestQueryParamsDict(MultiValueOrderedDict):\n    pass\n\n\nclass RequestDataDict(MultiValueOrderedDict):\n    pass\n\n\nclass MultipartRequestDataDict(MultiValueOrderedDict):\n    pass\n\n\nclass RequestFilesDict(RequestDataDict):\n    pass\n"
  },
  {
    "path": "httpie/cli/exceptions.py",
    "content": "class ParseError(Exception):\n    pass\n"
  },
  {
    "path": "httpie/cli/nested_json/__init__.py",
    "content": "\"\"\"\nA library for parsing the HTTPie nested JSON key syntax and constructing the resulting objects.\n\n<https://httpie.io/docs/cli/nested-json>\n\nIt has no dependencies.\n\n\"\"\"\nfrom .interpret import interpret_nested_json, unwrap_top_level_list_if_needed\nfrom .errors import NestedJSONSyntaxError\nfrom .tokens import EMPTY_STRING, NestedJSONArray\n\n\n__all__ = [\n    'interpret_nested_json',\n    'unwrap_top_level_list_if_needed',\n    'EMPTY_STRING',\n    'NestedJSONArray',\n    'NestedJSONSyntaxError'\n]\n"
  },
  {
    "path": "httpie/cli/nested_json/errors.py",
    "content": "from typing import Optional\n\nfrom .tokens import Token, HIGHLIGHTER\n\n\nclass NestedJSONSyntaxError(ValueError):\n    def __init__(\n        self,\n        source: str,\n        token: Optional[Token],\n        message: str,\n        message_kind: str = 'Syntax',\n    ) -> None:\n        self.source = source\n        self.token = token\n        self.message = message\n        self.message_kind = message_kind\n\n    def __str__(self):\n        lines = [f'HTTPie {self.message_kind} Error: {self.message}']\n        if self.token is not None:\n            lines.append(self.source)\n            lines.append(\n                ' ' * self.token.start\n                + HIGHLIGHTER * (self.token.end - self.token.start)\n            )\n        return '\\n'.join(lines)\n"
  },
  {
    "path": "httpie/cli/nested_json/interpret.py",
    "content": "from typing import Type, Union, Any, Iterable, Tuple\n\nfrom .parse import parse, assert_cant_happen\nfrom .errors import NestedJSONSyntaxError\nfrom .tokens import EMPTY_STRING, TokenKind, Token, PathAction, Path, NestedJSONArray\n\n\n__all__ = [\n    'interpret_nested_json',\n    'unwrap_top_level_list_if_needed',\n]\n\nJSONType = Type[Union[dict, list, int, float, str]]\nJSON_TYPE_MAPPING = {\n    dict: 'object',\n    list: 'array',\n    int: 'number',\n    float: 'number',\n    str: 'string',\n}\n\n\ndef interpret_nested_json(pairs: Iterable[Tuple[str, str]]) -> dict:\n    context = None\n    for key, value in pairs:\n        context = interpret(context, key, value)\n    return wrap_with_dict(context)\n\n\ndef interpret(context: Any, key: str, value: Any) -> Any:\n    cursor = context\n    paths = list(parse(key))\n    paths.append(Path(PathAction.SET, value))\n\n    # noinspection PyShadowingNames\n    def type_check(index: int, path: Path, expected_type: JSONType):\n        if not isinstance(cursor, expected_type):\n            if path.tokens:\n                pseudo_token = Token(\n                    kind=TokenKind.PSEUDO,\n                    value='',\n                    start=path.tokens[0].start,\n                    end=path.tokens[-1].end,\n                )\n            else:\n                pseudo_token = None\n            cursor_type = JSON_TYPE_MAPPING.get(type(cursor), type(cursor).__name__)\n            required_type = JSON_TYPE_MAPPING[expected_type]\n            message = f'Cannot perform {path.kind.to_string()!r} based access on '\n            message += repr(''.join(path.reconstruct() for path in paths[:index]))\n            message += f' which has a type of {cursor_type!r} but this operation'\n            message += f' requires a type of {required_type!r}.'\n            raise NestedJSONSyntaxError(\n                source=key,\n                token=pseudo_token,\n                message=message,\n                message_kind='Type',\n            )\n\n    def object_for(kind: PathAction) -> Any:\n        if kind is PathAction.KEY:\n            return {}\n        elif kind in {PathAction.INDEX, PathAction.APPEND}:\n            return []\n        else:\n            assert_cant_happen()\n\n    for index, (path, next_path) in enumerate(zip(paths, paths[1:])):\n        # If there is no context yet, set it.\n        if cursor is None:\n            context = cursor = object_for(path.kind)\n        if path.kind is PathAction.KEY:\n            type_check(index, path, dict)\n            if next_path.kind is PathAction.SET:\n                cursor[path.accessor] = next_path.accessor\n                break\n            cursor = cursor.setdefault(path.accessor, object_for(next_path.kind))\n        elif path.kind is PathAction.INDEX:\n            type_check(index, path, list)\n            if path.accessor < 0:\n                raise NestedJSONSyntaxError(\n                    source=key,\n                    token=path.tokens[1],\n                    message='Negative indexes are not supported.',\n                    message_kind='Value',\n                )\n            cursor.extend([None] * (path.accessor - len(cursor) + 1))\n            if next_path.kind is PathAction.SET:\n                cursor[path.accessor] = next_path.accessor\n                break\n            if cursor[path.accessor] is None:\n                cursor[path.accessor] = object_for(next_path.kind)\n            cursor = cursor[path.accessor]\n        elif path.kind is PathAction.APPEND:\n            type_check(index, path, list)\n            if next_path.kind is PathAction.SET:\n                cursor.append(next_path.accessor)\n                break\n            cursor.append(object_for(next_path.kind))\n            cursor = cursor[-1]\n        else:\n            assert_cant_happen()\n\n    return context\n\n\ndef wrap_with_dict(context):\n    if context is None:\n        return {}\n    elif isinstance(context, list):\n        return {\n            EMPTY_STRING: NestedJSONArray(context),\n        }\n    else:\n        assert isinstance(context, dict)\n        return context\n\n\ndef unwrap_top_level_list_if_needed(data: dict):\n    \"\"\"\n    Propagate the top-level list, if that’s what we got.\n\n    \"\"\"\n    if len(data) == 1:\n        key, value = list(data.items())[0]\n        if isinstance(value, NestedJSONArray):\n            assert key == EMPTY_STRING\n            return value\n    return data\n"
  },
  {
    "path": "httpie/cli/nested_json/parse.py",
    "content": "from typing import Iterator\n\nfrom .errors import NestedJSONSyntaxError\nfrom .tokens import (\n    EMPTY_STRING,\n    BACKSLASH,\n    TokenKind,\n    OPERATORS,\n    SPECIAL_CHARS,\n    LITERAL_TOKENS,\n    Token,\n    PathAction,\n    Path,\n)\n\n\n__all__ = [\n    'parse',\n    'assert_cant_happen',\n]\n\n\ndef parse(source: str) -> Iterator[Path]:\n    \"\"\"\n    start: root_path path*\n    root_path: (literal | index_path | append_path)\n    literal: TEXT | NUMBER\n\n    path:\n        key_path\n        | index_path\n        | append_path\n    key_path: LEFT_BRACKET TEXT RIGHT_BRACKET\n    index_path: LEFT_BRACKET NUMBER RIGHT_BRACKET\n    append_path: LEFT_BRACKET RIGHT_BRACKET\n\n    \"\"\"\n\n    tokens = list(tokenize(source))\n    cursor = 0\n\n    def can_advance():\n        return cursor < len(tokens)\n\n    # noinspection PyShadowingNames\n    def expect(*kinds):\n        nonlocal cursor\n        assert kinds\n        if can_advance():\n            token = tokens[cursor]\n            cursor += 1\n            if token.kind in kinds:\n                return token\n        elif tokens:\n            token = tokens[-1]._replace(\n                start=tokens[-1].end + 0,\n                end=tokens[-1].end + 1,\n            )\n        else:\n            token = None\n        if len(kinds) == 1:\n            suffix = kinds[0].to_name()\n        else:\n            suffix = ', '.join(kind.to_name() for kind in kinds[:-1])\n            suffix += ' or ' + kinds[-1].to_name()\n        message = f'Expecting {suffix}'\n        raise NestedJSONSyntaxError(source, token, message)\n\n    # noinspection PyShadowingNames\n    def parse_root():\n        tokens = []\n        if not can_advance():\n            return Path(\n                kind=PathAction.KEY,\n                accessor=EMPTY_STRING,\n                is_root=True\n            )\n        # (literal | index_path | append_path)?\n        token = expect(*LITERAL_TOKENS, TokenKind.LEFT_BRACKET)\n        tokens.append(token)\n        if token.kind in LITERAL_TOKENS:\n            action = PathAction.KEY\n            value = str(token.value)\n        elif token.kind is TokenKind.LEFT_BRACKET:\n            token = expect(TokenKind.NUMBER, TokenKind.RIGHT_BRACKET)\n            tokens.append(token)\n            if token.kind is TokenKind.NUMBER:\n                action = PathAction.INDEX\n                value = token.value\n                tokens.append(expect(TokenKind.RIGHT_BRACKET))\n            elif token.kind is TokenKind.RIGHT_BRACKET:\n                action = PathAction.APPEND\n                value = None\n            else:\n                assert_cant_happen()\n        else:\n            assert_cant_happen()\n        # noinspection PyUnboundLocalVariable\n        return Path(\n            kind=action,\n            accessor=value,\n            tokens=tokens,\n            is_root=True\n        )\n\n    yield parse_root()\n\n    # path*\n    while can_advance():\n        path_tokens = [expect(TokenKind.LEFT_BRACKET)]\n        token = expect(TokenKind.TEXT, TokenKind.NUMBER, TokenKind.RIGHT_BRACKET)\n        path_tokens.append(token)\n        if token.kind is TokenKind.RIGHT_BRACKET:\n            path = Path(PathAction.APPEND, tokens=path_tokens)\n        elif token.kind is TokenKind.TEXT:\n            path = Path(PathAction.KEY, token.value, tokens=path_tokens)\n            path_tokens.append(expect(TokenKind.RIGHT_BRACKET))\n        elif token.kind is TokenKind.NUMBER:\n            path = Path(PathAction.INDEX, token.value, tokens=path_tokens)\n            path_tokens.append(expect(TokenKind.RIGHT_BRACKET))\n        else:\n            assert_cant_happen()\n        # noinspection PyUnboundLocalVariable\n        yield path\n\n\ndef tokenize(source: str) -> Iterator[Token]:\n    cursor = 0\n    backslashes = 0\n    buffer = []\n\n    def send_buffer() -> Iterator[Token]:\n        nonlocal backslashes\n        if not buffer:\n            return None\n\n        value = ''.join(buffer)\n        kind = TokenKind.TEXT\n        if not backslashes:\n            for variation, kind in [\n                (int, TokenKind.NUMBER),\n                (check_escaped_int, TokenKind.TEXT),\n            ]:\n                try:\n                    value = variation(value)\n                except ValueError:\n                    continue\n                else:\n                    break\n        yield Token(\n            kind=kind,\n            value=value,\n            start=cursor - (len(buffer) + backslashes),\n            end=cursor,\n        )\n        buffer.clear()\n        backslashes = 0\n\n    def can_advance() -> bool:\n        return cursor < len(source)\n\n    while can_advance():\n        index = source[cursor]\n        if index in OPERATORS:\n            yield from send_buffer()\n            yield Token(OPERATORS[index], index, cursor, cursor + 1)\n        elif index == BACKSLASH and can_advance():\n            if source[cursor + 1] in SPECIAL_CHARS:\n                backslashes += 1\n            else:\n                buffer.append(index)\n            buffer.append(source[cursor + 1])\n            cursor += 1\n        else:\n            buffer.append(index)\n        cursor += 1\n\n    yield from send_buffer()\n\n\ndef check_escaped_int(value: str) -> str:\n    if not value.startswith(BACKSLASH):\n        raise ValueError('Not an escaped int')\n    try:\n        int(value[1:])\n    except ValueError as exc:\n        raise ValueError('Not an escaped int') from exc\n    else:\n        return value[1:]\n\n\ndef assert_cant_happen():\n    raise ValueError('Unexpected value')\n"
  },
  {
    "path": "httpie/cli/nested_json/tokens.py",
    "content": "from enum import Enum, auto\nfrom typing import NamedTuple, Union, Optional, List\n\nEMPTY_STRING = ''\nHIGHLIGHTER = '^'\nOPEN_BRACKET = '['\nCLOSE_BRACKET = ']'\nBACKSLASH = '\\\\'\n\n\nclass TokenKind(Enum):\n    TEXT = auto()\n    NUMBER = auto()\n    LEFT_BRACKET = auto()\n    RIGHT_BRACKET = auto()\n    PSEUDO = auto()  # Not a real token, use when representing location only.\n\n    def to_name(self) -> str:\n        for key, value in OPERATORS.items():\n            if value is self:\n                return repr(key)\n        else:\n            return 'a ' + self.name.lower()\n\n\nOPERATORS = {\n    OPEN_BRACKET: TokenKind.LEFT_BRACKET,\n    CLOSE_BRACKET: TokenKind.RIGHT_BRACKET,\n}\nSPECIAL_CHARS = OPERATORS.keys() | {BACKSLASH}\nLITERAL_TOKENS = [\n    TokenKind.TEXT,\n    TokenKind.NUMBER,\n]\n\n\nclass Token(NamedTuple):\n    kind: TokenKind\n    value: Union[str, int]\n    start: int\n    end: int\n\n\nclass PathAction(Enum):\n    KEY = auto()\n    INDEX = auto()\n    APPEND = auto()\n    # Pseudo action, used by the interpreter\n    SET = auto()\n\n    def to_string(self) -> str:\n        return self.name.lower()\n\n\nclass Path:\n    def __init__(\n        self,\n        kind: PathAction,\n        accessor: Optional[Union[str, int]] = None,\n        tokens: Optional[List[Token]] = None,\n        is_root: bool = False,\n    ):\n        self.kind = kind\n        self.accessor = accessor\n        self.tokens = tokens or []\n        self.is_root = is_root\n\n    def reconstruct(self) -> str:\n        if self.kind is PathAction.KEY:\n            if self.is_root:\n                return str(self.accessor)\n            return OPEN_BRACKET + self.accessor + CLOSE_BRACKET\n        elif self.kind is PathAction.INDEX:\n            return OPEN_BRACKET + str(self.accessor) + CLOSE_BRACKET\n        elif self.kind is PathAction.APPEND:\n            return OPEN_BRACKET + CLOSE_BRACKET\n\n\nclass NestedJSONArray(list):\n    \"\"\"Denotes a top-level JSON array.\"\"\"\n"
  },
  {
    "path": "httpie/cli/options.py",
    "content": "import argparse\nimport textwrap\nimport typing\nfrom dataclasses import dataclass, field\nfrom enum import Enum, auto\nfrom typing import Any, Optional, Dict, List, Tuple, Type, TypeVar\n\nfrom httpie.cli.argparser import HTTPieArgumentParser\nfrom httpie.cli.utils import Manual, LazyChoices\n\n\nclass Qualifiers(Enum):\n    OPTIONAL = auto()\n    ZERO_OR_MORE = auto()\n    ONE_OR_MORE = auto()\n    SUPPRESS = auto()\n\n\ndef map_qualifiers(\n    configuration: Dict[str, Any], qualifier_map: Dict[Qualifiers, Any]\n) -> Dict[str, Any]:\n    return {\n        key: qualifier_map[value] if isinstance(value, Qualifiers) else value\n        for key, value in configuration.items()\n    }\n\n\ndef drop_keys(\n    configuration: Dict[str, Any], key_blacklist: Tuple[str, ...]\n):\n    return {\n        key: value\n        for key, value in configuration.items()\n        if key not in key_blacklist\n    }\n\n\nPARSER_SPEC_VERSION = '0.0.1a0'\n\n\n@dataclass\nclass ParserSpec:\n    program: str\n    description: Optional[str] = None\n    epilog: Optional[str] = None\n    groups: List['Group'] = field(default_factory=list)\n    man_page_hint: Optional[str] = None\n    source_file: Optional[str] = None\n\n    def finalize(self) -> 'ParserSpec':\n        if self.description:\n            self.description = textwrap.dedent(self.description)\n        if self.epilog:\n            self.epilog = textwrap.dedent(self.epilog)\n        for group in self.groups:\n            group.finalize()\n        return self\n\n    def add_group(self, name: str, **kwargs) -> 'Group':\n        group = Group(name, **kwargs)\n        self.groups.append(group)\n        return group\n\n    def serialize(self) -> Dict[str, Any]:\n        return {\n            'name': self.program,\n            'description': self.description,\n            'groups': [group.serialize() for group in self.groups],\n        }\n\n\n@dataclass\nclass Group:\n    name: str\n    description: str = ''\n    is_mutually_exclusive: bool = False\n    arguments: List['Argument'] = field(default_factory=list)\n\n    def finalize(self) -> None:\n        if self.description:\n            self.description = textwrap.dedent(self.description)\n\n    def add_argument(self, *args, **kwargs):\n        argument = Argument(list(args), kwargs.copy())\n        argument.post_init()\n        self.arguments.append(argument)\n        return argument\n\n    def serialize(self) -> Dict[str, Any]:\n        return {\n            'name': self.name,\n            'description': self.description or None,\n            'is_mutually_exclusive': self.is_mutually_exclusive,\n            'args': [argument.serialize() for argument in self.arguments],\n        }\n\n\nclass Argument(typing.NamedTuple):\n    aliases: List[str]\n    configuration: Dict[str, Any]\n\n    def post_init(self):\n        \"\"\"Run a bunch of post-init hooks.\"\"\"\n        # If there is a short help, then create the longer version from it.\n        short_help = self.configuration.get('short_help')\n        if (\n            short_help\n            and 'help' not in self.configuration\n            and self.configuration.get('action') != 'lazy_choices'\n        ):\n            self.configuration['help'] = f'\\n{short_help}\\n\\n'\n\n    def serialize(self, *, isolation_mode: bool = False) -> Dict[str, Any]:\n        configuration = self.configuration.copy()\n\n        # Unpack the dynamically computed choices, since we\n        # will need to store the actual values somewhere.\n        action = configuration.pop('action', None)\n        short_help = configuration.pop('short_help', None)\n        nested_options = configuration.pop('nested_options', None)\n\n        if action == 'lazy_choices':\n            choices = LazyChoices(\n                self.aliases,\n                **{'dest': None, **configuration},\n                isolation_mode=isolation_mode\n            )\n            configuration['choices'] = list(choices.load())\n            configuration['help'] = choices.help\n\n        result = {}\n        if self.aliases:\n            result['options'] = self.aliases.copy()\n        else:\n            result['options'] = [configuration['metavar']]\n            result['is_positional'] = True\n\n        qualifiers = JSON_QUALIFIER_TO_OPTIONS[configuration.get('nargs', Qualifiers.SUPPRESS)]\n        result.update(qualifiers)\n\n        description = configuration.get('help')\n        if description and description is not Qualifiers.SUPPRESS:\n            result['short_description'] = short_help\n            result['description'] = description\n\n        if nested_options:\n            result['nested_options'] = nested_options\n\n        python_type = configuration.get('type')\n        if python_type is not None:\n            if hasattr(python_type, '__name__'):\n                type_name = python_type.__name__\n            else:\n                type_name = type(python_type).__name__\n\n            result['python_type_name'] = type_name\n\n        result.update({\n            key: value\n            for key, value in configuration.items()\n            if key in JSON_DIRECT_MIRROR_OPTIONS\n            if value is not Qualifiers.SUPPRESS\n        })\n\n        return result\n\n    @property\n    def is_positional(self):\n        return len(self.aliases) == 0\n\n    @property\n    def is_hidden(self):\n        return self.configuration.get('help') is Qualifiers.SUPPRESS\n\n    def __getattr__(self, attribute_name):\n        if attribute_name in self.configuration:\n            return self.configuration[attribute_name]\n        else:\n            raise AttributeError(attribute_name)\n\n\nParserType = TypeVar('ParserType', bound=Type[argparse.ArgumentParser])\n\nARGPARSE_QUALIFIER_MAP = {\n    Qualifiers.OPTIONAL: argparse.OPTIONAL,\n    Qualifiers.SUPPRESS: argparse.SUPPRESS,\n    Qualifiers.ZERO_OR_MORE: argparse.ZERO_OR_MORE,\n    Qualifiers.ONE_OR_MORE: argparse.ONE_OR_MORE\n}\nARGPARSE_IGNORE_KEYS = ('short_help', 'nested_options')\n\n\ndef to_argparse(\n    abstract_options: ParserSpec,\n    parser_type: ParserType = HTTPieArgumentParser,\n) -> ParserType:\n    concrete_parser = parser_type(\n        prog=abstract_options.program,\n        description=abstract_options.description,\n        epilog=abstract_options.epilog,\n    )\n    concrete_parser.spec = abstract_options\n    concrete_parser.register('action', 'lazy_choices', LazyChoices)\n    concrete_parser.register('action', 'manual', Manual)\n\n    for abstract_group in abstract_options.groups:\n        concrete_group = concrete_parser.add_argument_group(\n            title=abstract_group.name, description=abstract_group.description\n        )\n        if abstract_group.is_mutually_exclusive:\n            concrete_group = concrete_group.add_mutually_exclusive_group(required=False)\n\n        for abstract_argument in abstract_group.arguments:\n            concrete_group.add_argument(\n                *abstract_argument.aliases,\n                **drop_keys(map_qualifiers(\n                    abstract_argument.configuration, ARGPARSE_QUALIFIER_MAP\n                ), ARGPARSE_IGNORE_KEYS)\n            )\n\n    return concrete_parser\n\n\nJSON_DIRECT_MIRROR_OPTIONS = (\n    'choices',\n    'metavar'\n)\n\n\nJSON_QUALIFIER_TO_OPTIONS = {\n    Qualifiers.OPTIONAL: {'is_optional': True},\n    Qualifiers.ZERO_OR_MORE: {'is_optional': True, 'is_variadic': True},\n    Qualifiers.ONE_OR_MORE: {'is_optional': False, 'is_variadic': True},\n    Qualifiers.SUPPRESS: {}\n}\n\n\ndef to_data(abstract_options: ParserSpec) -> Dict[str, Any]:\n    return {'version': PARSER_SPEC_VERSION, 'spec': abstract_options.serialize()}\n\n\ndef parser_to_parser_spec(parser: argparse.ArgumentParser, **kwargs) -> ParserSpec:\n    \"\"\"Take an existing argparse parser, and create a spec from it.\"\"\"\n    return ParserSpec(\n        program=parser.prog,\n        description=parser.description,\n        epilog=parser.epilog,\n        **kwargs\n    )\n"
  },
  {
    "path": "httpie/cli/requestitems.py",
    "content": "import os\nimport functools\nfrom typing import Callable, Dict, IO, List, Optional, Tuple, Union\n\nfrom .argtypes import KeyValueArg\nfrom .constants import (\n    SEPARATORS_GROUP_MULTIPART, SEPARATOR_DATA_EMBED_FILE_CONTENTS,\n    SEPARATOR_DATA_EMBED_RAW_JSON_FILE, SEPARATOR_GROUP_NESTED_JSON_ITEMS,\n    SEPARATOR_DATA_RAW_JSON, SEPARATOR_DATA_STRING, SEPARATOR_FILE_UPLOAD,\n    SEPARATOR_FILE_UPLOAD_TYPE, SEPARATOR_HEADER, SEPARATOR_HEADER_EMPTY,\n    SEPARATOR_HEADER_EMBED, SEPARATOR_QUERY_PARAM,\n    SEPARATOR_QUERY_EMBED_FILE, RequestType\n)\nfrom .dicts import (\n    BaseMultiDict, MultipartRequestDataDict, RequestDataDict,\n    RequestFilesDict, HTTPHeadersDict, RequestJSONDataDict,\n    RequestQueryParamsDict,\n)\nfrom .exceptions import ParseError\nfrom .nested_json import interpret_nested_json\nfrom ..utils import get_content_type, load_json_preserve_order_and_dupe_keys, split_iterable\n\n\nclass RequestItems:\n\n    def __init__(self, request_type: Optional[RequestType] = None):\n        self.headers = HTTPHeadersDict()\n        self.request_type = request_type\n        self.is_json = request_type is None or request_type is RequestType.JSON\n        self.data = RequestJSONDataDict() if self.is_json else RequestDataDict()\n        self.files = RequestFilesDict()\n        self.params = RequestQueryParamsDict()\n        # To preserve the order of fields in file upload multipart requests.\n        self.multipart_data = MultipartRequestDataDict()\n\n    @classmethod\n    def from_args(\n        cls,\n        request_item_args: List[KeyValueArg],\n        request_type: Optional[RequestType] = None,\n    ) -> 'RequestItems':\n        instance = cls(request_type=request_type)\n        rules: Dict[str, Tuple[Callable, dict]] = {\n            SEPARATOR_HEADER: (\n                process_header_arg,\n                instance.headers,\n            ),\n            SEPARATOR_HEADER_EMPTY: (\n                process_empty_header_arg,\n                instance.headers,\n            ),\n            SEPARATOR_HEADER_EMBED: (\n                process_embed_header_arg,\n                instance.headers,\n            ),\n            SEPARATOR_QUERY_PARAM: (\n                process_query_param_arg,\n                instance.params,\n            ),\n            SEPARATOR_QUERY_EMBED_FILE: (\n                process_embed_query_param_arg,\n                instance.params,\n            ),\n            SEPARATOR_FILE_UPLOAD: (\n                process_file_upload_arg,\n                instance.files,\n            ),\n            SEPARATOR_DATA_STRING: (\n                process_data_item_arg,\n                instance.data,\n            ),\n            SEPARATOR_DATA_EMBED_FILE_CONTENTS: (\n                process_data_embed_file_contents_arg,\n                instance.data,\n            ),\n            SEPARATOR_GROUP_NESTED_JSON_ITEMS: (\n                process_data_nested_json_embed_args,\n                instance.data,\n            ),\n            SEPARATOR_DATA_RAW_JSON: (\n                convert_json_value_to_form_if_needed(\n                    in_json_mode=instance.is_json,\n                    processor=process_data_raw_json_embed_arg\n                ),\n                instance.data,\n            ),\n            SEPARATOR_DATA_EMBED_RAW_JSON_FILE: (\n                convert_json_value_to_form_if_needed(\n                    in_json_mode=instance.is_json,\n                    processor=process_data_embed_raw_json_file_arg,\n                ),\n                instance.data,\n            ),\n        }\n\n        if instance.is_json:\n            json_item_args, request_item_args = split_iterable(\n                iterable=request_item_args,\n                key=lambda arg: arg.sep in SEPARATOR_GROUP_NESTED_JSON_ITEMS\n            )\n            if json_item_args:\n                pairs = [(arg.key, rules[arg.sep][0](arg)) for arg in json_item_args]\n                processor_func, target_dict = rules[SEPARATOR_GROUP_NESTED_JSON_ITEMS]\n                value = processor_func(pairs)\n                target_dict.update(value)\n\n        # Then handle all other items.\n        for arg in request_item_args:\n            processor_func, target_dict = rules[arg.sep]\n            value = processor_func(arg)\n\n            if arg.sep in SEPARATORS_GROUP_MULTIPART:\n                instance.multipart_data[arg.key] = value\n\n            if isinstance(target_dict, BaseMultiDict):\n                target_dict.add(arg.key, value)\n            else:\n                target_dict[arg.key] = value\n\n        return instance\n\n\nJSONType = Union[str, bool, int, list, dict]\n\n\ndef process_header_arg(arg: KeyValueArg) -> Optional[str]:\n    return arg.value or None\n\n\ndef process_embed_header_arg(arg: KeyValueArg) -> str:\n    return load_text_file(arg).rstrip('\\n')\n\n\ndef process_empty_header_arg(arg: KeyValueArg) -> str:\n    if not arg.value:\n        return arg.value\n    raise ParseError(\n        f'Invalid item {arg.orig!r} (to specify an empty header use `Header;`)'\n    )\n\n\ndef process_query_param_arg(arg: KeyValueArg) -> str:\n    return arg.value\n\n\ndef process_embed_query_param_arg(arg: KeyValueArg) -> str:\n    return load_text_file(arg).rstrip('\\n')\n\n\ndef process_file_upload_arg(arg: KeyValueArg) -> Tuple[str, IO, str]:\n    parts = arg.value.split(SEPARATOR_FILE_UPLOAD_TYPE)\n    filename = parts[0]\n    mime_type = parts[1] if len(parts) > 1 else None\n    try:\n        f = open(os.path.expanduser(filename), 'rb')\n    except OSError as e:\n        raise ParseError(f'{arg.orig!r}: {e}')\n    return (\n        os.path.basename(filename),\n        f,\n        mime_type or get_content_type(filename),\n    )\n\n\ndef convert_json_value_to_form_if_needed(in_json_mode: bool, processor: Callable[[KeyValueArg], JSONType]) -> Callable[[], str]:\n    \"\"\"\n    We allow primitive values to be passed to forms via JSON key/value syntax.\n\n    But complex values lead to an error because there’s no clear way to serialize them.\n\n    \"\"\"\n    if in_json_mode:\n        return processor\n\n    @functools.wraps(processor)\n    def wrapper(*args, **kwargs) -> str:\n        try:\n            output = processor(*args, **kwargs)\n        except ParseError:\n            output = None\n        if isinstance(output, (str, int, float)):\n            return str(output)\n        else:\n            raise ParseError('Cannot use complex JSON value types with --form/--multipart.')\n\n    return wrapper\n\n\ndef process_data_item_arg(arg: KeyValueArg) -> str:\n    return arg.value\n\n\ndef process_data_embed_file_contents_arg(arg: KeyValueArg) -> str:\n    return load_text_file(arg)\n\n\ndef process_data_embed_raw_json_file_arg(arg: KeyValueArg) -> JSONType:\n    contents = load_text_file(arg)\n    value = load_json(arg, contents)\n    return value\n\n\ndef process_data_raw_json_embed_arg(arg: KeyValueArg) -> JSONType:\n    value = load_json(arg, arg.value)\n    return value\n\n\ndef process_data_nested_json_embed_args(pairs) -> Dict[str, JSONType]:\n    return interpret_nested_json(pairs)\n\n\ndef load_text_file(item: KeyValueArg) -> str:\n    path = item.value\n    try:\n        with open(os.path.expanduser(path), 'rb') as f:\n            return f.read().decode()\n    except OSError as e:\n        raise ParseError(f'{item.orig!r}: {e}')\n    except UnicodeDecodeError:\n        raise ParseError(\n            f'{item.orig!r}: cannot embed the content of {item.value!r},'\n            ' not a UTF-8 or ASCII-encoded text file'\n        )\n\n\ndef load_json(arg: KeyValueArg, contents: str) -> JSONType:\n    try:\n        return load_json_preserve_order_and_dupe_keys(contents)\n    except ValueError as e:\n        raise ParseError(f'{arg.orig!r}: {e}')\n"
  },
  {
    "path": "httpie/cli/utils.py",
    "content": "import argparse\nfrom typing import Any, Callable, Generic, Iterator, Iterable, Optional, TypeVar\n\nT = TypeVar('T')\n\n\nclass Manual(argparse.Action):\n    def __init__(\n        self,\n        option_strings,\n        dest=argparse.SUPPRESS,\n        default=argparse.SUPPRESS,\n        help=None\n    ):\n        super().__init__(\n            option_strings=option_strings,\n            dest=dest,\n            default=default,\n            nargs=0,\n            help=help\n        )\n\n    def __call__(self, parser, namespace, values, option_string=None):\n        parser.print_manual()\n        parser.exit()\n\n\nclass LazyChoices(argparse.Action, Generic[T]):\n    def __init__(\n        self,\n        *args,\n        getter: Callable[[], Iterable[T]],\n        help_formatter: Optional[Callable[[T, bool], str]] = None,\n        sort: bool = False,\n        cache: bool = True,\n        isolation_mode: bool = False,\n        **kwargs\n    ) -> None:\n        self.getter = getter\n        self.help_formatter = help_formatter\n        self.sort = sort\n        self.cache = cache\n        self.isolation_mode = isolation_mode\n        self._help: Optional[str] = None\n        self._obj: Optional[Iterable[T]] = None\n        super().__init__(*args, **kwargs)\n        self.choices = self\n\n    def load(self) -> T:\n        if self._obj is None or not self.cache:\n            self._obj = self.getter()\n\n        assert self._obj is not None\n        return self._obj\n\n    @property\n    def help(self) -> str:\n        if self._help is None and self.help_formatter is not None:\n            self._help = self.help_formatter(\n                self.load(),\n                isolation_mode=self.isolation_mode\n            )\n        return self._help\n\n    @help.setter\n    def help(self, value: Any) -> None:\n        self._help = value\n\n    def __contains__(self, item: Any) -> bool:\n        return item in self.load()\n\n    def __iter__(self) -> Iterator[T]:\n        if self.sort:\n            return iter(sorted(self.load()))\n        else:\n            return iter(self.load())\n\n    def __call__(self, parser, namespace, values, option_string=None):\n        setattr(namespace, self.dest, values)\n"
  },
  {
    "path": "httpie/client.py",
    "content": "import argparse\nimport http.client\nimport json\nimport sys\nfrom contextlib import contextmanager\nfrom time import monotonic\nfrom typing import Any, Dict, Callable, Iterable\nfrom urllib.parse import urlparse, urlunparse\n\nimport requests\n# noinspection PyPackageRequirements\nimport urllib3\nfrom urllib3.util import SKIP_HEADER, SKIPPABLE_HEADERS\n\nfrom . import __version__\nfrom .adapters import HTTPieHTTPAdapter\nfrom .cli.constants import HTTP_OPTIONS\nfrom .cli.dicts import HTTPHeadersDict\nfrom .cli.nested_json import unwrap_top_level_list_if_needed\nfrom .context import Environment\nfrom .encoding import UTF8\nfrom .models import RequestsMessage\nfrom .plugins.registry import plugin_manager\nfrom .sessions import get_httpie_session\nfrom .ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, HTTPieCertificate, HTTPieHTTPSAdapter\nfrom .uploads import (\n    compress_request, prepare_request_body,\n    get_multipart_data_and_content_type,\n)\nfrom .utils import get_expired_cookies, repr_dict\n\n\nurllib3.disable_warnings()\n\nFORM_CONTENT_TYPE = f'application/x-www-form-urlencoded; charset={UTF8}'\nJSON_CONTENT_TYPE = 'application/json'\nJSON_ACCEPT = f'{JSON_CONTENT_TYPE}, */*;q=0.5'\nDEFAULT_UA = f'HTTPie/{__version__}'\n\nIGNORE_CONTENT_LENGTH_METHODS = frozenset([HTTP_OPTIONS])\n\n\ndef collect_messages(\n    env: Environment,\n    args: argparse.Namespace,\n    request_body_read_callback: Callable[[bytes], None] = None,\n) -> Iterable[RequestsMessage]:\n    httpie_session = None\n    httpie_session_headers = None\n    if args.session or args.session_read_only:\n        httpie_session = get_httpie_session(\n            env=env,\n            config_dir=env.config.directory,\n            session_name=args.session or args.session_read_only,\n            host=args.headers.get('Host'),\n            url=args.url,\n        )\n        httpie_session_headers = httpie_session.headers\n\n    request_kwargs = make_request_kwargs(\n        env,\n        args=args,\n        base_headers=httpie_session_headers,\n        request_body_read_callback=request_body_read_callback\n    )\n    send_kwargs = make_send_kwargs(args)\n    send_kwargs_mergeable_from_env = make_send_kwargs_mergeable_from_env(args)\n    requests_session = build_requests_session(\n        ssl_version=args.ssl_version,\n        ciphers=args.ciphers,\n        verify=bool(send_kwargs_mergeable_from_env['verify'])\n    )\n\n    if httpie_session:\n        httpie_session.update_headers(request_kwargs['headers'])\n        requests_session.cookies = httpie_session.cookies\n        if args.auth_plugin:\n            # Save auth from CLI to HTTPie session.\n            httpie_session.auth = {\n                'type': args.auth_plugin.auth_type,\n                'raw_auth': args.auth_plugin.raw_auth,\n            }\n        elif httpie_session.auth:\n            # Apply auth from HTTPie session\n            request_kwargs['auth'] = httpie_session.auth\n\n    if args.debug:\n        # TODO: reflect the split between request and send kwargs.\n        dump_request(request_kwargs)\n\n    request = requests.Request(**request_kwargs)\n    prepared_request = requests_session.prepare_request(request)\n    transform_headers(request, prepared_request)\n    if args.path_as_is:\n        prepared_request.url = ensure_path_as_is(\n            orig_url=args.url,\n            prepped_url=prepared_request.url,\n        )\n    if args.compress and prepared_request.body:\n        compress_request(\n            request=prepared_request,\n            always=args.compress > 1,\n        )\n    response_count = 0\n    expired_cookies = []\n    while prepared_request:\n        yield prepared_request\n        if not args.offline:\n            send_kwargs_merged = requests_session.merge_environment_settings(\n                url=prepared_request.url,\n                **send_kwargs_mergeable_from_env,\n            )\n            with max_headers(args.max_headers):\n                response = requests_session.send(\n                    request=prepared_request,\n                    **send_kwargs_merged,\n                    **send_kwargs,\n                )\n            response._httpie_headers_parsed_at = monotonic()\n            expired_cookies += get_expired_cookies(\n                response.headers.get('Set-Cookie', '')\n            )\n\n            response_count += 1\n            if response.next:\n                if args.max_redirects and response_count == args.max_redirects:\n                    raise requests.TooManyRedirects\n                if args.follow:\n                    prepared_request = response.next\n                    if args.all:\n                        yield response\n                    continue\n            yield response\n        break\n\n    if httpie_session:\n        if httpie_session.is_new() or not args.session_read_only:\n            httpie_session.cookies = requests_session.cookies\n            httpie_session.remove_cookies(expired_cookies)\n            httpie_session.save()\n\n\n# noinspection PyProtectedMember\n@contextmanager\ndef max_headers(limit):\n    # <https://github.com/httpie/cli/issues/802>\n    # noinspection PyUnresolvedReferences\n    orig = http.client._MAXHEADERS\n    http.client._MAXHEADERS = limit or float('Inf')\n    try:\n        yield\n    finally:\n        http.client._MAXHEADERS = orig\n\n\ndef build_requests_session(\n    verify: bool,\n    ssl_version: str = None,\n    ciphers: str = None,\n) -> requests.Session:\n    requests_session = requests.Session()\n\n    # Install our adapter.\n    http_adapter = HTTPieHTTPAdapter()\n    https_adapter = HTTPieHTTPSAdapter(\n        ciphers=ciphers,\n        verify=verify,\n        ssl_version=(\n            AVAILABLE_SSL_VERSION_ARG_MAPPING[ssl_version]\n            if ssl_version else None\n        ),\n    )\n    requests_session.mount('http://', http_adapter)\n    requests_session.mount('https://', https_adapter)\n\n    # Install adapters from plugins.\n    for plugin_cls in plugin_manager.get_transport_plugins():\n        transport_plugin = plugin_cls()\n        requests_session.mount(\n            prefix=transport_plugin.prefix,\n            adapter=transport_plugin.get_adapter(),\n        )\n\n    return requests_session\n\n\ndef dump_request(kwargs: dict):\n    sys.stderr.write(\n        f'\\n>>> requests.request(**{repr_dict(kwargs)})\\n\\n')\n\n\ndef finalize_headers(headers: HTTPHeadersDict) -> HTTPHeadersDict:\n    final_headers = HTTPHeadersDict()\n    for name, value in headers.items():\n        if value is not None:\n            # “leading or trailing LWS MAY be removed without\n            # changing the semantics of the field value”\n            # <https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html>\n            # Also, requests raises `InvalidHeader` for leading spaces.\n            value = value.strip()\n            if isinstance(value, str):\n                # See <https://github.com/httpie/cli/issues/212>\n                value = value.encode()\n        elif name.lower() in SKIPPABLE_HEADERS:\n            # Some headers get overwritten by urllib3 when set to `None`\n            # and should be replaced with the `SKIP_HEADER` constant.\n            value = SKIP_HEADER\n        final_headers.add(name, value)\n    return final_headers\n\n\ndef transform_headers(\n    request: requests.Request,\n    prepared_request: requests.PreparedRequest\n) -> None:\n    \"\"\"Apply various transformations on top of the `prepared_requests`'s\n    headers to change the request prepreation behavior.\"\"\"\n\n    # Remove 'Content-Length' when it is misplaced by requests.\n    if (\n        prepared_request.method in IGNORE_CONTENT_LENGTH_METHODS\n        and prepared_request.headers.get('Content-Length') == '0'\n        and request.headers.get('Content-Length') != '0'\n    ):\n        prepared_request.headers.pop('Content-Length')\n\n    apply_missing_repeated_headers(\n        request.headers,\n        prepared_request\n    )\n\n\ndef apply_missing_repeated_headers(\n    original_headers: HTTPHeadersDict,\n    prepared_request: requests.PreparedRequest\n) -> None:\n    \"\"\"Update the given `prepared_request`'s headers with the original\n    ones. This allows the requests to be prepared as usual, and then later\n    merged with headers that are specified multiple times.\"\"\"\n\n    new_headers = HTTPHeadersDict(prepared_request.headers)\n    for prepared_name, prepared_value in prepared_request.headers.items():\n        if prepared_name not in original_headers:\n            continue\n\n        original_keys, original_values = zip(*filter(\n            lambda item: item[0].casefold() == prepared_name.casefold(),\n            original_headers.items()\n        ))\n\n        if prepared_value not in original_values:\n            # If the current value is not among the initial values\n            # set for this field, then it means that this field got\n            # overridden on the way, and we should preserve it.\n            continue\n\n        new_headers.popone(prepared_name)\n        new_headers.update(zip(original_keys, original_values))\n\n    prepared_request.headers = new_headers\n\n\ndef make_default_headers(args: argparse.Namespace) -> HTTPHeadersDict:\n    default_headers = HTTPHeadersDict({\n        'User-Agent': DEFAULT_UA\n    })\n\n    auto_json = args.data and not args.form\n    if args.json or auto_json:\n        default_headers['Accept'] = JSON_ACCEPT\n        if args.json or (auto_json and args.data):\n            default_headers['Content-Type'] = JSON_CONTENT_TYPE\n\n    elif args.form and not args.files:\n        # If sending files, `requests` will set\n        # the `Content-Type` for us.\n        default_headers['Content-Type'] = FORM_CONTENT_TYPE\n    return default_headers\n\n\ndef make_send_kwargs(args: argparse.Namespace) -> dict:\n    return {\n        'timeout': args.timeout or None,\n        'allow_redirects': False,\n    }\n\n\ndef make_send_kwargs_mergeable_from_env(args: argparse.Namespace) -> dict:\n    cert = None\n    if args.cert:\n        cert = args.cert\n        if args.cert_key:\n            # Having a client certificate key passphrase is not supported\n            # by requests. So we are using our own transportation structure\n            # which is compatible with their format (a tuple of minimum two\n            # items).\n            #\n            # See: https://github.com/psf/requests/issues/2519\n            cert = HTTPieCertificate(cert, args.cert_key, args.cert_key_pass.value)\n\n    return {\n        'proxies': {p.key: p.value for p in args.proxy},\n        'stream': True,\n        'verify': {\n            'yes': True,\n            'true': True,\n            'no': False,\n            'false': False,\n        }.get(args.verify.lower(), args.verify),\n        'cert': cert,\n    }\n\n\ndef json_dict_to_request_body(data: Dict[str, Any]) -> str:\n    data = unwrap_top_level_list_if_needed(data)\n    if data:\n        data = json.dumps(data)\n    else:\n        # We need to set data to an empty string to prevent requests\n        # from assigning an empty list to `response.request.data`.\n        data = ''\n    return data\n\n\ndef make_request_kwargs(\n    env: Environment,\n    args: argparse.Namespace,\n    base_headers: HTTPHeadersDict = None,\n    request_body_read_callback=lambda chunk: chunk\n) -> dict:\n    \"\"\"\n    Translate our `args` into `requests.Request` keyword arguments.\n\n    \"\"\"\n    files = args.files\n    # Serialize JSON data, if needed.\n    data = args.data\n    auto_json = data and not args.form\n    if (args.json or auto_json) and isinstance(data, dict):\n        data = json_dict_to_request_body(data)\n\n    # Finalize headers.\n    headers = make_default_headers(args)\n    if base_headers:\n        headers.update(base_headers)\n    headers.update(args.headers)\n    if args.offline and args.chunked and 'Transfer-Encoding' not in headers:\n        # When online, we let requests set the header instead to be able more\n        # easily verify chunking is taking place.\n        headers['Transfer-Encoding'] = 'chunked'\n    headers = finalize_headers(headers)\n\n    if (args.form and files) or args.multipart:\n        data, headers['Content-Type'] = get_multipart_data_and_content_type(\n            data=args.multipart_data,\n            boundary=args.boundary,\n            content_type=args.headers.get('Content-Type'),\n        )\n\n    return {\n        'method': args.method.lower(),\n        'url': args.url,\n        'headers': headers,\n        'data': prepare_request_body(\n            env,\n            data,\n            body_read_callback=request_body_read_callback,\n            chunked=args.chunked,\n            offline=args.offline,\n            content_length_header_value=headers.get('Content-Length'),\n        ),\n        'auth': args.auth,\n        'params': args.params.items(),\n    }\n\n\ndef ensure_path_as_is(orig_url: str, prepped_url: str) -> str:\n    \"\"\"\n    Handle `--path-as-is` by replacing the path component of the prepared\n    URL with the path component from the original URL. Other parts stay\n    untouched because other (welcome) processing on the URL might have\n    taken place.\n\n    <https://github.com/httpie/cli/issues/895>\n\n\n    <https://ec.haxx.se/http/http-basics#path-as-is>\n    <https://curl.haxx.se/libcurl/c/CURLOPT_PATH_AS_IS.html>\n\n    >>> ensure_path_as_is('http://foo/../', 'http://foo/?foo=bar')\n    'http://foo/../?foo=bar'\n\n    \"\"\"\n    parsed_orig, parsed_prepped = urlparse(orig_url), urlparse(prepped_url)\n    final_dict = {\n        # noinspection PyProtectedMember\n        **parsed_prepped._asdict(),\n        'path': parsed_orig.path,\n    }\n    return urlunparse(tuple(final_dict.values()))\n"
  },
  {
    "path": "httpie/compat.py",
    "content": "import sys\nfrom ssl import SSLContext\nfrom typing import Any, Optional, Iterable\n\nfrom httpie.cookies import HTTPieCookiePolicy\nfrom http import cookiejar  # noqa\n\n\n# Request does not carry the original policy attached to the\n# cookie jar, so until it is resolved we change the global cookie\n# policy. <https://github.com/psf/requests/issues/5449>\ncookiejar.DefaultCookiePolicy = HTTPieCookiePolicy\n\nis_windows = 'win32' in str(sys.platform).lower()\nis_frozen = getattr(sys, 'frozen', False)\n\nMIN_SUPPORTED_PY_VERSION = (3, 7)\nMAX_SUPPORTED_PY_VERSION = (3, 11)\n\ntry:\n    from functools import cached_property\nexcept ImportError:\n    # Can be removed once we drop Python <3.8 support.\n    # Taken from `django.utils.functional.cached_property`.\n    class cached_property:\n        \"\"\"\n        Decorator that converts a method with a single self argument into a\n        property cached on the instance.\n\n        A cached property can be made out of an existing method:\n        (e.g. ``url = cached_property(get_absolute_url)``).\n        The optional ``name`` argument is obsolete as of Python 3.6 and will be\n        deprecated in Django 4.0 (#30127).\n        \"\"\"\n        name = None\n\n        @staticmethod\n        def func(instance):\n            raise TypeError(\n                'Cannot use cached_property instance without calling '\n                '__set_name__() on it.'\n            )\n\n        def __init__(self, func, name=None):\n            self.real_func = func\n            self.__doc__ = getattr(func, '__doc__')\n\n        def __set_name__(self, owner, name):\n            if self.name is None:\n                self.name = name\n                self.func = self.real_func\n            elif name != self.name:\n                raise TypeError(\n                    \"Cannot assign the same cached_property to two different names \"\n                    \"(%r and %r).\" % (self.name, name)\n                )\n\n        def __get__(self, instance, cls=None):\n            \"\"\"\n            Call the function and put the return value in instance.__dict__ so that\n            subsequent attribute access on the instance returns the cached value\n            instead of calling cached_property.__get__().\n            \"\"\"\n            if instance is None:\n                return self\n            res = instance.__dict__[self.name] = self.func(instance)\n            return res\n\n# importlib_metadata was a provisional module, so the APIs changed quite a few times\n# between 3.8-3.10. It was also not included in the standard library until 3.8, so\n# we install the backport for <3.8.\n\nif sys.version_info >= (3, 8):\n    import importlib.metadata as importlib_metadata\nelse:\n    import importlib_metadata\n\n\ndef find_entry_points(entry_points: Any, group: str) -> Iterable[importlib_metadata.EntryPoint]:\n    if hasattr(entry_points, \"select\"):  # Python 3.10+ / importlib_metadata >= 3.9.0\n        return entry_points.select(group=group)\n    else:\n        return set(entry_points.get(group, ()))\n\n\ndef get_dist_name(entry_point: importlib_metadata.EntryPoint) -> Optional[str]:\n    dist = getattr(entry_point, \"dist\", None)\n    if dist is not None:  # Python 3.10+\n        return dist.name\n\n    match = entry_point.pattern.match(entry_point.value)\n    if not (match and match.group('module')):\n        return None\n\n    package = match.group('module').split('.')[0]\n    try:\n        metadata = importlib_metadata.metadata(package)\n    except importlib_metadata.PackageNotFoundError:\n        return None\n    else:\n        return metadata.get('name')\n\n\ndef ensure_default_certs_loaded(ssl_context: SSLContext) -> None:\n    \"\"\"\n    Workaround for a bug in Requests 2.32.3\n\n    See <https://github.com/httpie/cli/issues/1583>\n\n    \"\"\"\n    if hasattr(ssl_context, 'load_default_certs'):\n        if not ssl_context.get_ca_certs():\n            ssl_context.load_default_certs()\n"
  },
  {
    "path": "httpie/config.py",
    "content": "import json\nimport os\nfrom pathlib import Path\nfrom typing import Any, Dict, Union\n\nfrom . import __version__\nfrom .compat import is_windows\nfrom .encoding import UTF8\n\n\nENV_XDG_CONFIG_HOME = 'XDG_CONFIG_HOME'\nENV_HTTPIE_CONFIG_DIR = 'HTTPIE_CONFIG_DIR'\nDEFAULT_CONFIG_DIRNAME = 'httpie'\nDEFAULT_RELATIVE_XDG_CONFIG_HOME = Path('.config')\nDEFAULT_RELATIVE_LEGACY_CONFIG_DIR = Path('.httpie')\nDEFAULT_WINDOWS_CONFIG_DIR = Path(\n    os.path.expandvars('%APPDATA%')) / DEFAULT_CONFIG_DIRNAME\n\n\ndef get_default_config_dir() -> Path:\n    \"\"\"\n    Return the path to the httpie configuration directory.\n\n    This directory isn't guaranteed to exist, and nor are any of its\n    ancestors (only the legacy ~/.httpie, if returned, is guaranteed to exist).\n\n    XDG Base Directory Specification support:\n\n        <https://wiki.archlinux.org/index.php/XDG_Base_Directory>\n\n        $XDG_CONFIG_HOME is supported; $XDG_CONFIG_DIRS is not\n\n    \"\"\"\n    # 1. explicitly set through env\n    env_config_dir = os.environ.get(ENV_HTTPIE_CONFIG_DIR)\n    if env_config_dir:\n        return Path(env_config_dir)\n\n    # 2. Windows\n    if is_windows:\n        return DEFAULT_WINDOWS_CONFIG_DIR\n\n    home_dir = Path.home()\n\n    # 3. legacy ~/.httpie\n    legacy_config_dir = home_dir / DEFAULT_RELATIVE_LEGACY_CONFIG_DIR\n    if legacy_config_dir.exists():\n        return legacy_config_dir\n\n    # 4. XDG\n    xdg_config_home_dir = os.environ.get(\n        ENV_XDG_CONFIG_HOME,  # 4.1. explicit\n        home_dir / DEFAULT_RELATIVE_XDG_CONFIG_HOME  # 4.2. default\n    )\n    return Path(xdg_config_home_dir) / DEFAULT_CONFIG_DIRNAME\n\n\nDEFAULT_CONFIG_DIR = get_default_config_dir()\n\n\nclass ConfigFileError(Exception):\n    pass\n\n\ndef read_raw_config(config_type: str, path: Path) -> Dict[str, Any]:\n    try:\n        with path.open(encoding=UTF8) as f:\n            try:\n                return json.load(f)\n            except ValueError as e:\n                raise ConfigFileError(\n                    f'invalid {config_type} file: {e} [{path}]'\n                )\n    except FileNotFoundError:\n        pass\n    except OSError as e:\n        raise ConfigFileError(f'cannot read {config_type} file: {e}')\n\n\nclass BaseConfigDict(dict):\n    name = None\n    helpurl = None\n    about = None\n\n    def __init__(self, path: Path):\n        super().__init__()\n        self.path = path\n\n    def ensure_directory(self):\n        self.path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)\n\n    def is_new(self) -> bool:\n        return not self.path.exists()\n\n    def pre_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Hook for processing the incoming config data.\"\"\"\n        return data\n\n    def post_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Hook for processing the outgoing config data.\"\"\"\n        return data\n\n    def load(self):\n        config_type = type(self).__name__.lower()\n        data = read_raw_config(config_type, self.path)\n        if data is not None:\n            data = self.pre_process_data(data)\n            self.update(data)\n\n    def save(self, *, bump_version: bool = False):\n        self.setdefault('__meta__', {})\n        if bump_version or 'httpie' not in self['__meta__']:\n            self['__meta__']['httpie'] = __version__\n        if self.helpurl:\n            self['__meta__']['help'] = self.helpurl\n\n        if self.about:\n            self['__meta__']['about'] = self.about\n\n        self.ensure_directory()\n\n        json_string = json.dumps(\n            obj=self.post_process_data(self),\n            indent=4,\n            sort_keys=True,\n            ensure_ascii=True,\n        )\n        self.path.write_text(json_string + '\\n', encoding=UTF8)\n\n    @property\n    def version(self):\n        return self.get(\n            '__meta__', {}\n        ).get('httpie', __version__)\n\n\nclass Config(BaseConfigDict):\n    FILENAME = 'config.json'\n    DEFAULTS = {\n        'default_options': []\n    }\n\n    def __init__(self, directory: Union[str, Path] = DEFAULT_CONFIG_DIR):\n        self.directory = Path(directory)\n        super().__init__(path=self.directory / self.FILENAME)\n        self.update(self.DEFAULTS)\n\n    @property\n    def default_options(self) -> list:\n        return self['default_options']\n\n    def _configured_path(self, config_option: str, default: str) -> None:\n        return Path(\n            self.get(config_option, self.directory / default)\n        ).expanduser().resolve()\n\n    @property\n    def plugins_dir(self) -> Path:\n        return self._configured_path('plugins_dir', 'plugins')\n\n    @property\n    def version_info_file(self) -> Path:\n        return self._configured_path('version_info_file', 'version_info.json')\n\n    @property\n    def developer_mode(self) -> bool:\n        \"\"\"This is a special setting for the development environment. It is\n        different from the --debug mode in the terms that it might change\n        the behavior for certain parameters (e.g updater system) that\n        we usually ignore.\"\"\"\n\n        return self.get('developer_mode')\n"
  },
  {
    "path": "httpie/context.py",
    "content": "import argparse\nimport sys\nimport os\nimport warnings\nfrom contextlib import contextmanager\nfrom pathlib import Path\nfrom typing import Iterator, IO, Optional, TYPE_CHECKING\nfrom enum import Enum\n\n\ntry:\n    import curses\nexcept ImportError:\n    curses = None  # Compiled w/o curses\n\nfrom .compat import is_windows, cached_property\nfrom .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError\nfrom .encoding import UTF8\n\nfrom .utils import repr_dict\nfrom .output.ui.palette import GenericColor\n\nif TYPE_CHECKING:\n    from rich.console import Console\n\n\nclass LogLevel(str, Enum):\n    INFO = 'info'\n    WARNING = 'warning'\n    ERROR = 'error'\n\n\nLOG_LEVEL_COLORS = {\n    LogLevel.INFO: GenericColor.PINK,\n    LogLevel.WARNING: GenericColor.ORANGE,\n    LogLevel.ERROR: GenericColor.RED,\n}\n\nLOG_LEVEL_DISPLAY_THRESHOLDS = {\n    LogLevel.INFO: 1,\n    LogLevel.WARNING: 2,\n    LogLevel.ERROR: float('inf'),  # Never hide errors.\n}\n\n\nclass Environment:\n    \"\"\"\n    Information about the execution context\n    (standard streams, config directory, etc).\n\n    By default, it represents the actual environment.\n    All of the attributes can be overwritten though, which\n    is used by the test suite to simulate various scenarios.\n\n    \"\"\"\n    args = argparse.Namespace()\n    is_windows: bool = is_windows\n    config_dir: Path = DEFAULT_CONFIG_DIR\n    stdin: Optional[IO] = sys.stdin  # `None` when closed fd (#791)\n    stdin_isatty: bool = stdin.isatty() if stdin else False\n    stdin_encoding: str = None\n    stdout: IO = sys.stdout\n    stdout_isatty: bool = stdout.isatty()\n    stdout_encoding: str = None\n    stderr: IO = sys.stderr\n    stderr_isatty: bool = stderr.isatty()\n    colors = 256\n    program_name: str = 'http'\n\n    # Whether to show progress bars / status spinners etc.\n    show_displays: bool = True\n\n    if not is_windows:\n        if curses:\n            try:\n                curses.setupterm()\n                colors = curses.tigetnum('colors')\n            except curses.error:\n                pass\n    else:\n        # noinspection PyUnresolvedReferences\n        import colorama.initialise\n        stdout = colorama.initialise.wrap_stream(\n            stdout, convert=None, strip=None,\n            autoreset=True, wrap=True\n        )\n        stderr = colorama.initialise.wrap_stream(\n            stderr, convert=None, strip=None,\n            autoreset=True, wrap=True\n        )\n        del colorama\n\n    def __init__(self, devnull=None, **kwargs):\n        \"\"\"\n        Use keyword arguments to overwrite\n        any of the class attributes for this instance.\n\n        \"\"\"\n        assert all(hasattr(type(self), attr) for attr in kwargs.keys())\n        self.__dict__.update(**kwargs)\n\n        # The original STDERR unaffected by --quiet’ing.\n        self._orig_stderr = self.stderr\n        self._devnull = devnull\n\n        # Keyword arguments > stream.encoding > default UTF-8\n        if self.stdin and self.stdin_encoding is None:\n            self.stdin_encoding = getattr(\n                self.stdin, 'encoding', None) or UTF8\n        if self.stdout_encoding is None:\n            actual_stdout = self.stdout\n            if is_windows:\n                # noinspection PyUnresolvedReferences\n                from colorama import AnsiToWin32\n                if isinstance(self.stdout, AnsiToWin32):\n                    # noinspection PyUnresolvedReferences\n                    actual_stdout = self.stdout.wrapped\n            self.stdout_encoding = getattr(\n                actual_stdout, 'encoding', None) or UTF8\n\n        self.quiet = kwargs.pop('quiet', 0)\n\n    def __str__(self):\n        defaults = dict(type(self).__dict__)\n        actual = dict(defaults)\n        actual.update(self.__dict__)\n        actual['config'] = self.config\n        return repr_dict({\n            key: value\n            for key, value in actual.items()\n            if not key.startswith('_')\n        })\n\n    def __repr__(self):\n        return f'<{type(self).__name__} {self}>'\n\n    _config: Config = None\n\n    @property\n    def config(self) -> Config:\n        config = self._config\n        if not config:\n            self._config = config = Config(directory=self.config_dir)\n            if not config.is_new():\n                try:\n                    config.load()\n                except ConfigFileError as e:\n                    self.log_error(e, level=LogLevel.WARNING)\n        return config\n\n    @property\n    def devnull(self) -> IO:\n        if self._devnull is None:\n            self._devnull = open(os.devnull, 'w+')\n        return self._devnull\n\n    @contextmanager\n    def as_silent(self) -> Iterator[None]:\n        original_stdout = self.stdout\n        original_stderr = self.stderr\n\n        try:\n            self.stdout = self.devnull\n            self.stderr = self.devnull\n            yield\n        finally:\n            self.stdout = original_stdout\n            self.stderr = original_stderr\n\n    def log_error(self, msg: str, level: LogLevel = LogLevel.ERROR) -> None:\n        if self.stdout_isatty and self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[level]:\n            stderr = self.stderr  # Not directly /dev/null, since stderr might be mocked\n        else:\n            stderr = self._orig_stderr\n        rich_console = self._make_rich_console(file=stderr, force_terminal=stderr.isatty())\n        rich_console.print(\n            f'\\n{self.program_name}: {level.value}: {msg}\\n\\n',\n            style=LOG_LEVEL_COLORS[level],\n            markup=False,\n            highlight=False,\n            soft_wrap=True\n        )\n\n    def apply_warnings_filter(self) -> None:\n        if self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[LogLevel.WARNING]:\n            warnings.simplefilter(\"ignore\")\n\n    def _make_rich_console(\n        self,\n        file: IO[str],\n        force_terminal: bool\n    ) -> 'Console':\n        from rich.console import Console\n        from httpie.output.ui.rich_palette import _make_rich_color_theme\n\n        style = getattr(self.args, 'style', None)\n        theme = _make_rich_color_theme(style)\n        # Rich infers the rest of the knowledge (e.g encoding)\n        # dynamically by looking at the file/stderr.\n        return Console(\n            file=file,\n            force_terminal=force_terminal,\n            no_color=(self.colors == 0),\n            theme=theme\n        )\n\n    # Rich recommends separating the actual console (stdout) from\n    # the error (stderr) console for better isolation between parts.\n    # https://rich.readthedocs.io/en/stable/console.html#error-console\n\n    @cached_property\n    def rich_console(self):\n        return self._make_rich_console(self.stdout, self.stdout_isatty)\n\n    @cached_property\n    def rich_error_console(self):\n        return self._make_rich_console(self.stderr, self.stderr_isatty)\n"
  },
  {
    "path": "httpie/cookies.py",
    "content": "from http import cookiejar\n\n\n_LOCALHOST = 'localhost'\n_LOCALHOST_SUFFIX = '.localhost'\n\n\nclass HTTPieCookiePolicy(cookiejar.DefaultCookiePolicy):\n    def return_ok_secure(self, cookie, request):\n        \"\"\"Check whether the given cookie is sent to a secure host.\"\"\"\n\n        is_secure_protocol = super().return_ok_secure(cookie, request)\n        if is_secure_protocol:\n            return True\n\n        # The original implementation of this method only takes secure protocols\n        # (e.g., https) into account, but the latest developments in modern browsers\n        # (chrome, firefox) assume 'localhost' is also a secure location. So we\n        # override it with our own strategy.\n        return self._is_local_host(cookiejar.request_host(request))\n\n    def _is_local_host(self, hostname):\n        # Implements the static localhost detection algorithm in firefox.\n        # <https://searchfox.org/mozilla-central/rev/d4d7611ee4dd0003b492b865bc5988a4e6afc985/netwerk/dns/DNS.cpp#205-218>\n        return hostname == _LOCALHOST or hostname.endswith(_LOCALHOST_SUFFIX)\n"
  },
  {
    "path": "httpie/core.py",
    "content": "import argparse\nimport os\nimport platform\nimport sys\nimport socket\nfrom typing import List, Optional, Union, Callable\n\nimport requests\nfrom pygments import __version__ as pygments_version\nfrom requests import __version__ as requests_version\n\nfrom . import __version__ as httpie_version\nfrom .cli.constants import OUT_REQ_BODY\nfrom .cli.nested_json import NestedJSONSyntaxError\nfrom .client import collect_messages\nfrom .context import Environment, LogLevel\nfrom .downloads import Downloader\nfrom .models import (\n    RequestsMessageKind,\n    OutputOptions\n)\nfrom .output.models import ProcessingOptions\nfrom .output.writer import write_message, write_stream, write_raw_data, MESSAGE_SEPARATOR_BYTES\nfrom .plugins.registry import plugin_manager\nfrom .status import ExitStatus, http_status_to_exit_status\nfrom .utils import unwrap_context\nfrom .internal.update_warnings import check_updates\nfrom .internal.daemon_runner import is_daemon_mode, run_daemon_task\n\n\n# noinspection PyDefaultArgument\ndef raw_main(\n    parser: argparse.ArgumentParser,\n    main_program: Callable[[argparse.Namespace, Environment], ExitStatus],\n    args: List[Union[str, bytes]] = sys.argv,\n    env: Environment = Environment(),\n    use_default_options: bool = True,\n) -> ExitStatus:\n    program_name, *args = args\n    env.program_name = os.path.basename(program_name)\n    args = decode_raw_args(args, env.stdin_encoding)\n\n    if is_daemon_mode(args):\n        return run_daemon_task(env, args)\n\n    plugin_manager.load_installed_plugins(env.config.plugins_dir)\n\n    if use_default_options and env.config.default_options:\n        args = env.config.default_options + args\n\n    include_debug_info = '--debug' in args\n    include_traceback = include_debug_info or '--traceback' in args\n\n    def handle_generic_error(e, annotation=None):\n        msg = str(e)\n        if hasattr(e, 'request'):\n            request = e.request\n            if hasattr(request, 'url'):\n                msg = (\n                    f'{msg} while doing a {request.method}'\n                    f' request to URL: {request.url}'\n                )\n        if annotation:\n            msg += annotation\n        env.log_error(f'{type(e).__name__}: {msg}')\n        if include_traceback:\n            raise\n\n    if include_debug_info:\n        print_debug_info(env)\n        if args == ['--debug']:\n            return ExitStatus.SUCCESS\n\n    exit_status = ExitStatus.SUCCESS\n\n    try:\n        parsed_args = parser.parse_args(\n            args=args,\n            env=env,\n        )\n    except NestedJSONSyntaxError as exc:\n        env.stderr.write(str(exc) + \"\\n\")\n        if include_traceback:\n            raise\n        exit_status = ExitStatus.ERROR\n    except KeyboardInterrupt:\n        env.stderr.write('\\n')\n        if include_traceback:\n            raise\n        exit_status = ExitStatus.ERROR_CTRL_C\n    except SystemExit as e:\n        if e.code != ExitStatus.SUCCESS:\n            env.stderr.write('\\n')\n            if include_traceback:\n                raise\n            exit_status = ExitStatus.ERROR\n    else:\n        check_updates(env)\n        try:\n            exit_status = main_program(\n                args=parsed_args,\n                env=env,\n            )\n        except KeyboardInterrupt:\n            env.stderr.write('\\n')\n            if include_traceback:\n                raise\n            exit_status = ExitStatus.ERROR_CTRL_C\n        except SystemExit as e:\n            if e.code != ExitStatus.SUCCESS:\n                env.stderr.write('\\n')\n                if include_traceback:\n                    raise\n                exit_status = ExitStatus.ERROR\n        except requests.Timeout:\n            exit_status = ExitStatus.ERROR_TIMEOUT\n            env.log_error(f'Request timed out ({parsed_args.timeout}s).')\n        except requests.TooManyRedirects:\n            exit_status = ExitStatus.ERROR_TOO_MANY_REDIRECTS\n            env.log_error(\n                f'Too many redirects'\n                f' (--max-redirects={parsed_args.max_redirects}).'\n            )\n        except requests.exceptions.ConnectionError as exc:\n            annotation = None\n            original_exc = unwrap_context(exc)\n            if isinstance(original_exc, socket.gaierror):\n                if original_exc.errno == socket.EAI_AGAIN:\n                    annotation = '\\nCouldn’t connect to a DNS server. Please check your connection and try again.'\n                elif original_exc.errno == socket.EAI_NONAME:\n                    annotation = '\\nCouldn’t resolve the given hostname. Please check the URL and try again.'\n                propagated_exc = original_exc\n            else:\n                propagated_exc = exc\n\n            handle_generic_error(propagated_exc, annotation=annotation)\n            exit_status = ExitStatus.ERROR\n        except Exception as e:\n            # TODO: Further distinction between expected and unexpected errors.\n            handle_generic_error(e)\n            exit_status = ExitStatus.ERROR\n\n    return exit_status\n\n\ndef main(\n    args: List[Union[str, bytes]] = sys.argv,\n    env: Environment = Environment()\n) -> ExitStatus:\n    \"\"\"\n    The main function.\n\n    Pre-process args, handle some special types of invocations,\n    and run the main program with error handling.\n\n    Return exit status code.\n\n    \"\"\"\n\n    from .cli.definition import parser\n\n    return raw_main(\n        parser=parser,\n        main_program=program,\n        args=args,\n        env=env\n    )\n\n\ndef program(args: argparse.Namespace, env: Environment) -> ExitStatus:\n    \"\"\"\n    The main program without error handling.\n\n    \"\"\"\n    # TODO: Refactor and drastically simplify, especially so that the separator logic is elsewhere.\n    exit_status = ExitStatus.SUCCESS\n    downloader = None\n    initial_request: Optional[requests.PreparedRequest] = None\n    final_response: Optional[requests.Response] = None\n    processing_options = ProcessingOptions.from_raw_args(args)\n\n    def separate():\n        getattr(env.stdout, 'buffer', env.stdout).write(MESSAGE_SEPARATOR_BYTES)\n\n    def request_body_read_callback(chunk: bytes):\n        should_pipe_to_stdout = bool(\n            # Request body output desired\n            OUT_REQ_BODY in args.output_options\n            # & not `.read()` already pre-request (e.g., for  compression)\n            and initial_request\n            # & non-EOF chunk\n            and chunk\n        )\n        if should_pipe_to_stdout:\n            return write_raw_data(\n                env,\n                chunk,\n                processing_options=processing_options,\n                headers=initial_request.headers\n            )\n\n    try:\n        if args.download:\n            args.follow = True  # --download implies --follow.\n            downloader = Downloader(env, output_file=args.output_file, resume=args.download_resume)\n            downloader.pre_request(args.headers)\n        messages = collect_messages(env, args=args,\n                                    request_body_read_callback=request_body_read_callback)\n        force_separator = False\n        prev_with_body = False\n\n        # Process messages as they’re generated\n        for message in messages:\n            output_options = OutputOptions.from_message(message, args.output_options)\n\n            do_write_body = output_options.body\n            if prev_with_body and output_options.any() and (force_separator or not env.stdout_isatty):\n                # Separate after a previous message with body, if needed. See test_tokens.py.\n                separate()\n            force_separator = False\n            if output_options.kind is RequestsMessageKind.REQUEST:\n                if not initial_request:\n                    initial_request = message\n                if output_options.body:\n                    is_streamed_upload = not isinstance(message.body, (str, bytes))\n                    do_write_body = not is_streamed_upload\n                    force_separator = is_streamed_upload and env.stdout_isatty\n            else:\n                final_response = message\n                if args.check_status or downloader:\n                    exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow)\n                    if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet == 1):\n                        env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level=LogLevel.WARNING)\n            write_message(\n                requests_message=message,\n                env=env,\n                output_options=output_options._replace(\n                    body=do_write_body\n                ),\n                processing_options=processing_options\n            )\n            prev_with_body = output_options.body\n\n        # Cleanup\n        if force_separator:\n            separate()\n        if downloader and exit_status == ExitStatus.SUCCESS:\n            # Last response body download.\n            download_stream, download_to = downloader.start(\n                initial_url=initial_request.url,\n                final_response=final_response,\n            )\n            write_stream(stream=download_stream, outfile=download_to, flush=False)\n            downloader.finish()\n            if downloader.interrupted:\n                exit_status = ExitStatus.ERROR\n                env.log_error(\n                    f'Incomplete download: size={downloader.status.total_size};'\n                    f' downloaded={downloader.status.downloaded}'\n                )\n        return exit_status\n\n    finally:\n        if downloader and not downloader.finished:\n            downloader.failed()\n        if args.output_file and args.output_file_specified:\n            args.output_file.close()\n\n\ndef print_debug_info(env: Environment):\n    env.stderr.writelines([\n        f'HTTPie {httpie_version}\\n',\n        f'Requests {requests_version}\\n',\n        f'Pygments {pygments_version}\\n',\n        f'Python {sys.version}\\n{sys.executable}\\n',\n        f'{platform.system()} {platform.release()}',\n    ])\n    env.stderr.write('\\n\\n')\n    env.stderr.write(repr(env))\n    env.stderr.write('\\n\\n')\n    env.stderr.write(repr(plugin_manager))\n    env.stderr.write('\\n')\n\n\ndef decode_raw_args(\n    args: List[Union[str, bytes]],\n    stdin_encoding: str\n) -> List[str]:\n    \"\"\"\n    Convert all bytes args to str\n    by decoding them using stdin encoding.\n\n    \"\"\"\n    return [\n        arg.decode(stdin_encoding)\n        if type(arg) is bytes else arg\n        for arg in args\n    ]\n"
  },
  {
    "path": "httpie/downloads.py",
    "content": "\"\"\"\nDownload mode implementation.\n\n\"\"\"\nimport mimetypes\nimport os\nimport re\nfrom mailbox import Message\nfrom time import monotonic\nfrom typing import IO, Optional, Tuple\nfrom urllib.parse import urlsplit\n\nimport requests\n\nfrom .models import HTTPResponse, OutputOptions\nfrom .output.streams import RawStream\nfrom .context import Environment\n\n\nPARTIAL_CONTENT = 206\n\n\nclass ContentRangeError(ValueError):\n    pass\n\n\ndef parse_content_range(content_range: str, resumed_from: int) -> int:\n    \"\"\"\n    Parse and validate Content-Range header.\n\n    <https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html>\n\n    :param content_range: the value of a Content-Range response header\n                          eg. \"bytes 21010-47021/47022\"\n    :param resumed_from: first byte pos. from the Range request header\n    :return: total size of the response body when fully downloaded.\n\n    \"\"\"\n    if content_range is None:\n        raise ContentRangeError('Missing Content-Range')\n\n    pattern = (\n        r'^bytes (?P<first_byte_pos>\\d+)-(?P<last_byte_pos>\\d+)'\n        r'/(\\*|(?P<instance_length>\\d+))$'\n    )\n    match = re.match(pattern, content_range)\n\n    if not match:\n        raise ContentRangeError(\n            f'Invalid Content-Range format {content_range!r}')\n\n    content_range_dict = match.groupdict()\n    first_byte_pos = int(content_range_dict['first_byte_pos'])\n    last_byte_pos = int(content_range_dict['last_byte_pos'])\n    instance_length = (\n        int(content_range_dict['instance_length'])\n        if content_range_dict['instance_length']\n        else None\n    )\n\n    # \"A byte-content-range-spec with a byte-range-resp-spec whose\n    # last- byte-pos value is less than its first-byte-pos value,\n    # or whose instance-length value is less than or equal to its\n    # last-byte-pos value, is invalid. The recipient of an invalid\n    # byte-content-range- spec MUST ignore it and any content\n    # transferred along with it.\"\n    if (first_byte_pos > last_byte_pos\n        or (instance_length is not None\n            and instance_length <= last_byte_pos)):\n        raise ContentRangeError(\n            f'Invalid Content-Range returned: {content_range!r}')\n\n    if (first_byte_pos != resumed_from\n        or (instance_length is not None\n            and last_byte_pos + 1 != instance_length)):\n        # Not what we asked for.\n        raise ContentRangeError(\n            f'Unexpected Content-Range returned ({content_range!r})'\n            f' for the requested Range (\"bytes={resumed_from}-\")'\n        )\n\n    return last_byte_pos + 1\n\n\ndef filename_from_content_disposition(\n    content_disposition: str\n) -> Optional[str]:\n    \"\"\"\n    Extract and validate filename from a Content-Disposition header.\n\n    :param content_disposition: Content-Disposition value\n    :return: the filename if present and valid, otherwise `None`\n\n    \"\"\"\n    # attachment; filename=jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz\n\n    msg = Message(f'Content-Disposition: {content_disposition}')\n    filename = msg.get_filename()\n    if filename:\n        # Basic sanitation.\n        filename = os.path.basename(filename).lstrip('.').strip()\n        if filename:\n            return filename\n\n\ndef filename_from_url(url: str, content_type: Optional[str]) -> str:\n    fn = urlsplit(url).path.rstrip('/')\n    fn = os.path.basename(fn) if fn else 'index'\n    if '.' not in fn and content_type:\n        content_type = content_type.split(';')[0]\n        if content_type == 'text/plain':\n            # mimetypes returns '.ksh'\n            ext = '.txt'\n        else:\n            ext = mimetypes.guess_extension(content_type)\n\n        if ext == '.htm':\n            ext = '.html'\n\n        if ext:\n            fn += ext\n\n    return fn\n\n\ndef trim_filename(filename: str, max_len: int) -> str:\n    if len(filename) > max_len:\n        trim_by = len(filename) - max_len\n        name, ext = os.path.splitext(filename)\n        if trim_by >= len(name):\n            filename = filename[:-trim_by]\n        else:\n            filename = name[:-trim_by] + ext\n    return filename\n\n\ndef get_filename_max_length(directory: str) -> int:\n    max_len = 255\n    if hasattr(os, 'pathconf') and 'PC_NAME_MAX' in os.pathconf_names:\n        max_len = os.pathconf(directory, 'PC_NAME_MAX')\n    return max_len\n\n\ndef trim_filename_if_needed(filename: str, directory='.', extra=0) -> str:\n    max_len = get_filename_max_length(directory) - extra\n    if len(filename) > max_len:\n        filename = trim_filename(filename, max_len)\n    return filename\n\n\ndef get_unique_filename(filename: str, exists=os.path.exists) -> str:\n    attempt = 0\n    while True:\n        suffix = f'-{attempt}' if attempt > 0 else ''\n        try_filename = trim_filename_if_needed(filename, extra=len(suffix))\n        try_filename += suffix\n        if not exists(try_filename):\n            return try_filename\n        attempt += 1\n\n\nclass Downloader:\n\n    def __init__(\n        self,\n        env: Environment,\n        output_file: IO = None,\n        resume: bool = False\n    ):\n        \"\"\"\n        :param resume: Should the download resume if partial download\n                       already exists.\n\n        :param output_file: The file to store response body in. If not\n                            provided, it will be guessed from the response.\n\n        :param progress_file: Where to report download progress.\n\n        \"\"\"\n        self.finished = False\n        self.status = DownloadStatus(env=env)\n        self._output_file = output_file\n        self._resume = resume\n        self._resumed_from = 0\n\n    def pre_request(self, request_headers: dict):\n        \"\"\"Called just before the HTTP request is sent.\n\n        Might alter `request_headers`.\n\n        \"\"\"\n        # Ask the server not to encode the content so that we can resume, etc.\n        request_headers['Accept-Encoding'] = 'identity'\n        if self._resume:\n            bytes_have = os.path.getsize(self._output_file.name)\n            if bytes_have:\n                # Set ``Range`` header to resume the download\n                # TODO: Use \"If-Range: mtime\" to make sure it's fresh?\n                request_headers['Range'] = f'bytes={bytes_have}-'\n                self._resumed_from = bytes_have\n\n    def start(\n        self,\n        initial_url: str,\n        final_response: requests.Response\n    ) -> Tuple[RawStream, IO]:\n        \"\"\"\n        Initiate and return a stream for `response` body  with progress\n        callback attached. Can be called only once.\n\n        :param initial_url: The original requested URL\n        :param final_response: Initiated response object with headers already fetched\n\n        :return: RawStream, output_file\n\n        \"\"\"\n        assert not self.status.time_started\n\n        # FIXME: some servers still might sent Content-Encoding: gzip\n        # <https://github.com/httpie/cli/issues/423>\n        try:\n            total_size = int(final_response.headers['Content-Length'])\n        except (KeyError, ValueError, TypeError):\n            total_size = None\n\n        if not self._output_file:\n            self._output_file = self._get_output_file_from_response(\n                initial_url=initial_url,\n                final_response=final_response,\n            )\n        else:\n            # `--output, -o` provided\n            if self._resume and final_response.status_code == PARTIAL_CONTENT:\n                total_size = parse_content_range(\n                    final_response.headers.get('Content-Range'),\n                    self._resumed_from\n                )\n\n            else:\n                self._resumed_from = 0\n                try:\n                    self._output_file.seek(0)\n                    self._output_file.truncate()\n                except OSError:\n                    pass  # stdout\n\n        output_options = OutputOptions.from_message(final_response, headers=False, body=True)\n        stream = RawStream(\n            msg=HTTPResponse(final_response),\n            output_options=output_options,\n            on_body_chunk_downloaded=self.chunk_downloaded,\n        )\n\n        self.status.started(\n            output_file=self._output_file,\n            resumed_from=self._resumed_from,\n            total_size=total_size\n        )\n\n        return stream, self._output_file\n\n    def finish(self):\n        assert not self.finished\n        self.finished = True\n        self.status.finished()\n\n    def failed(self):\n        self.status.terminate()\n\n    @property\n    def interrupted(self) -> bool:\n        return (\n            self.finished\n            and self.status.total_size\n            and self.status.total_size != self.status.downloaded\n        )\n\n    def chunk_downloaded(self, chunk: bytes):\n        \"\"\"\n        A download progress callback.\n\n        :param chunk: A chunk of response body data that has just\n                      been downloaded and written to the output.\n\n        \"\"\"\n        self.status.chunk_downloaded(len(chunk))\n\n    @staticmethod\n    def _get_output_file_from_response(\n        initial_url: str,\n        final_response: requests.Response,\n    ) -> IO:\n        # Output file not specified. Pick a name that doesn't exist yet.\n        filename = None\n        if 'Content-Disposition' in final_response.headers:\n            filename = filename_from_content_disposition(\n                final_response.headers['Content-Disposition'])\n        if not filename:\n            filename = filename_from_url(\n                url=initial_url,\n                content_type=final_response.headers.get('Content-Type'),\n            )\n        unique_filename = get_unique_filename(filename)\n        return open(unique_filename, buffering=0, mode='a+b')\n\n\nclass DownloadStatus:\n    \"\"\"Holds details about the download status.\"\"\"\n\n    def __init__(self, env):\n        self.env = env\n        self.downloaded = 0\n        self.total_size = None\n        self.resumed_from = 0\n        self.time_started = None\n        self.time_finished = None\n\n    def started(self, output_file, resumed_from=0, total_size=None):\n        assert self.time_started is None\n        self.total_size = total_size\n        self.downloaded = self.resumed_from = resumed_from\n        self.time_started = monotonic()\n        self.start_display(output_file=output_file)\n\n    def start_display(self, output_file):\n        from httpie.output.ui.rich_progress import (\n            DummyDisplay,\n            StatusDisplay,\n            ProgressDisplay\n        )\n\n        message = f'Downloading to {output_file.name}'\n        if self.env.show_displays:\n            if self.total_size is None:\n                # Rich does not support progress bars without a total\n                # size given. Instead we use status objects.\n                self.display = StatusDisplay(self.env)\n            else:\n                self.display = ProgressDisplay(self.env)\n        else:\n            self.display = DummyDisplay(self.env)\n\n        self.display.start(\n            total=self.total_size,\n            at=self.downloaded,\n            description=message\n        )\n\n    def chunk_downloaded(self, size):\n        assert self.time_finished is None\n        self.downloaded += size\n        self.display.update(size)\n\n    @property\n    def has_finished(self):\n        return self.time_finished is not None\n\n    @property\n    def time_spent(self):\n        if (\n            self.time_started is not None\n            and self.time_finished is not None\n        ):\n            return self.time_finished - self.time_started\n        else:\n            return None\n\n    def finished(self):\n        assert self.time_started is not None\n        assert self.time_finished is None\n        self.time_finished = monotonic()\n        if hasattr(self, 'display'):\n            self.display.stop(self.time_spent)\n\n    def terminate(self):\n        if hasattr(self, 'display'):\n            self.display.stop(self.time_spent)\n"
  },
  {
    "path": "httpie/encoding.py",
    "content": "from typing import Union, Tuple\n\nfrom charset_normalizer import from_bytes\nfrom charset_normalizer.constant import TOO_SMALL_SEQUENCE\n\nUTF8 = 'utf-8'\n\nContentBytes = Union[bytearray, bytes]\n\n\ndef detect_encoding(content: ContentBytes) -> str:\n    \"\"\"\n    We default to UTF-8 if text too short, because the detection\n    can return a random encoding leading to confusing results\n    given the `charset_normalizer` version (< 2.0.5).\n\n    >>> too_short = ']\"foo\"'\n    >>> detected = from_bytes(too_short.encode()).best().encoding\n    >>> detected\n    'ascii'\n    >>> too_short.encode().decode(detected)\n    ']\"foo\"'\n    \"\"\"\n    encoding = UTF8\n    if len(content) > TOO_SMALL_SEQUENCE:\n        match = from_bytes(bytes(content)).best()\n        if match:\n            encoding = match.encoding\n    return encoding\n\n\ndef smart_decode(content: ContentBytes, encoding: str) -> Tuple[str, str]:\n    \"\"\"Decode `content` using the given `encoding`.\n    If no `encoding` is provided, the best effort is to guess it from `content`.\n\n    Unicode errors are replaced.\n\n    \"\"\"\n    if not encoding:\n        encoding = detect_encoding(content)\n    return content.decode(encoding, 'replace'), encoding\n\n\ndef smart_encode(content: str, encoding: str) -> bytes:\n    \"\"\"Encode `content` using the given `encoding`.\n\n    Unicode errors are replaced.\n\n    \"\"\"\n    return content.encode(encoding, 'replace')\n"
  },
  {
    "path": "httpie/internal/__build_channel__.py",
    "content": "# Represents the packaging method. This file should\n# be overridden by every build system we support on\n# the packaging step.\n\nBUILD_CHANNEL = 'unknown'\n"
  },
  {
    "path": "httpie/internal/__init__.py",
    "content": ""
  },
  {
    "path": "httpie/internal/daemon_runner.py",
    "content": "import argparse\nfrom contextlib import redirect_stderr, redirect_stdout\nfrom typing import List\n\nfrom httpie.context import Environment\nfrom httpie.internal.update_warnings import _fetch_updates, _get_suppress_context\nfrom httpie.status import ExitStatus\n\nSTATUS_FILE = '.httpie-test-daemon-status'\n\n\ndef _check_status(env):\n    # This function is used only for the testing (test_update_warnings).\n    # Since we don't want to trigger the fetch_updates (which would interact\n    # with real world resources), we'll only trigger this pseudo task\n    # and check whether the STATUS_FILE is created or not.\n    import tempfile\n    from pathlib import Path\n\n    status_file = Path(tempfile.gettempdir()) / STATUS_FILE\n    status_file.touch()\n\n\nDAEMONIZED_TASKS = {\n    'check_status': _check_status,\n    'fetch_updates': _fetch_updates,\n}\n\n\ndef _parse_options(args: List[str]) -> argparse.Namespace:\n    parser = argparse.ArgumentParser()\n    parser.add_argument('task_id')\n    parser.add_argument('--daemon', action='store_true')\n    return parser.parse_known_args(args)[0]\n\n\ndef is_daemon_mode(args: List[str]) -> bool:\n    return '--daemon' in args\n\n\ndef run_daemon_task(env: Environment, args: List[str]) -> ExitStatus:\n    options = _parse_options(args)\n\n    assert options.daemon\n    assert options.task_id in DAEMONIZED_TASKS\n    with redirect_stdout(env.devnull), redirect_stderr(env.devnull):\n        with _get_suppress_context(env):\n            DAEMONIZED_TASKS[options.task_id](env)\n\n    return ExitStatus.SUCCESS\n"
  },
  {
    "path": "httpie/internal/daemons.py",
    "content": "\"\"\"\nThis module provides an interface to spawn a detached task to be\nrun with httpie.internal.daemon_runner on a separate process. It is\nbased on DVC's daemon system.\nhttps://github.com/iterative/dvc/blob/main/dvc/daemon.py\n\"\"\"\n\nimport inspect\nimport os\nimport platform\nimport sys\nimport httpie.__main__\nfrom contextlib import suppress\nfrom subprocess import Popen, DEVNULL\nfrom typing import Dict, List\nfrom httpie.compat import is_frozen, is_windows\n\n\nProcessContext = Dict[str, str]\n\n\ndef _start_process(cmd: List[str], **kwargs) -> Popen:\n    prefix = [sys.executable]\n    # If it is frozen, sys.executable points to the binary (http).\n    # Otherwise it points to the python interpreter.\n    if not is_frozen:\n        main_entrypoint = httpie.__main__.__file__\n        prefix += [main_entrypoint]\n    return Popen(prefix + cmd, close_fds=True, shell=False, stdout=DEVNULL, stderr=DEVNULL, **kwargs)\n\n\ndef _spawn_windows(cmd: List[str], process_context: ProcessContext) -> None:\n    from subprocess import (\n        CREATE_NEW_PROCESS_GROUP,\n        CREATE_NO_WINDOW,\n        STARTF_USESHOWWINDOW,\n        STARTUPINFO,\n    )\n\n    # https://stackoverflow.com/a/7006424\n    # https://bugs.python.org/issue41619\n    creationflags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW\n\n    startupinfo = STARTUPINFO()\n    startupinfo.dwFlags |= STARTF_USESHOWWINDOW\n\n    _start_process(\n        cmd,\n        env=process_context,\n        creationflags=creationflags,\n        startupinfo=startupinfo,\n    )\n\n\ndef _spawn_posix(args: List[str], process_context: ProcessContext) -> None:\n    \"\"\"\n    Perform a double fork procedure* to detach from the parent\n    process so that we don't block the user even if their original\n    command's execution is done but the release fetcher is not.\n\n    [1]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html#tag_11_01_03\n    \"\"\"\n\n    from httpie.core import main\n\n    try:\n        pid = os.fork()\n        if pid > 0:\n            return\n    except OSError:\n        os._exit(1)\n\n    os.setsid()\n\n    try:\n        pid = os.fork()\n        if pid > 0:\n            os._exit(0)\n    except OSError:\n        os._exit(1)\n\n    # Close all standard inputs/outputs\n    sys.stdin.close()\n    sys.stdout.close()\n    sys.stderr.close()\n\n    if platform.system() == 'Darwin':\n        # Double-fork is not reliable on MacOS, so we'll use a subprocess\n        # to ensure the task is isolated properly.\n        process = _start_process(args, env=process_context)\n        # Unlike windows, since we already completed the fork procedure\n        # we can simply join the process and wait for it.\n        process.communicate()\n    else:\n        os.environ.update(process_context)\n        with suppress(BaseException):\n            main(['http'] + args)\n\n    os._exit(0)\n\n\ndef _spawn(args: List[str], process_context: ProcessContext) -> None:\n    \"\"\"\n    Spawn a new process to run the given command.\n    \"\"\"\n    if is_windows:\n        _spawn_windows(args, process_context)\n    else:\n        _spawn_posix(args, process_context)\n\n\ndef spawn_daemon(task: str) -> None:\n    args = [task, '--daemon']\n    process_context = os.environ.copy()\n    if not is_frozen:\n        file_path = os.path.abspath(inspect.stack()[0][1])\n        process_context['PYTHONPATH'] = os.path.dirname(\n            os.path.dirname(os.path.dirname(file_path))\n        )\n\n    _spawn(args, process_context)\n"
  },
  {
    "path": "httpie/internal/update_warnings.py",
    "content": "import json\nfrom contextlib import nullcontext, suppress\nfrom datetime import datetime, timedelta\nfrom pathlib import Path\nfrom typing import Any, Optional, Callable\n\nimport requests\n\nimport httpie\nfrom httpie.context import Environment, LogLevel\nfrom httpie.internal.__build_channel__ import BUILD_CHANNEL\nfrom httpie.internal.daemons import spawn_daemon\nfrom httpie.utils import is_version_greater, open_with_lockfile\n\n# Automatically updated package version index.\nPACKAGE_INDEX_LINK = 'https://packages.httpie.io/latest.json'\n\nFETCH_INTERVAL = timedelta(weeks=2)\nWARN_INTERVAL = timedelta(weeks=1)\n\nUPDATE_MESSAGE_FORMAT = \"\"\"\\\nA new HTTPie release ({last_released_version}) is available.\nTo see how you can update, please visit https://httpie.io/docs/cli/{installation_method}\n\"\"\"\n\nALREADY_UP_TO_DATE_MESSAGE = \"\"\"\\\nYou are already up-to-date.\n\"\"\"\n\n\ndef _read_data_error_free(file: Path) -> Any:\n    # If the file is broken / non-existent, ignore it.\n    try:\n        with open(file) as stream:\n            return json.load(stream)\n    except (ValueError, OSError):\n        return {}\n\n\ndef _fetch_updates(env: Environment) -> str:\n    file = env.config.version_info_file\n    data = _read_data_error_free(file)\n\n    response = requests.get(PACKAGE_INDEX_LINK, verify=False)\n    response.raise_for_status()\n\n    data.setdefault('last_warned_date', None)\n    data['last_fetched_date'] = datetime.now().isoformat()\n    data['last_released_versions'] = response.json()\n\n    with open_with_lockfile(file, 'w') as stream:\n        json.dump(data, stream)\n\n\ndef fetch_updates(env: Environment, lazy: bool = True):\n    if lazy:\n        spawn_daemon('fetch_updates')\n    else:\n        _fetch_updates(env)\n\n\ndef maybe_fetch_updates(env: Environment) -> None:\n    if env.config.get('disable_update_warnings'):\n        return None\n\n    data = _read_data_error_free(env.config.version_info_file)\n\n    if data:\n        current_date = datetime.now()\n        last_fetched_date = datetime.fromisoformat(data['last_fetched_date'])\n        earliest_fetch_date = last_fetched_date + FETCH_INTERVAL\n        if current_date < earliest_fetch_date:\n            return None\n\n    fetch_updates(env)\n\n\ndef _get_suppress_context(env: Environment) -> Any:\n    \"\"\"Return a context manager that suppress\n    all possible errors.\n\n    Note: if you have set the developer_mode=True in\n    your config, then it will show all errors for easier\n    debugging.\"\"\"\n    if env.config.developer_mode:\n        return nullcontext()\n    else:\n        return suppress(BaseException)\n\n\ndef _update_checker(\n    func: Callable[[Environment], None]\n) -> Callable[[Environment], None]:\n    \"\"\"Control the execution of the update checker (suppress errors, trigger\n    auto updates etc.)\"\"\"\n\n    def wrapper(env: Environment) -> None:\n        with _get_suppress_context(env):\n            func(env)\n\n        with _get_suppress_context(env):\n            maybe_fetch_updates(env)\n\n    return wrapper\n\n\ndef _get_update_status(env: Environment) -> Optional[str]:\n    \"\"\"If there is a new update available, return the warning text.\n    Otherwise just return None.\"\"\"\n    file = env.config.version_info_file\n    if not file.exists():\n        return None\n\n    with _get_suppress_context(env):\n        # If the user quickly spawns multiple httpie processes\n        # we don't want to end in a race.\n        with open_with_lockfile(file) as stream:\n            version_info = json.load(stream)\n\n        available_channels = version_info['last_released_versions']\n        if BUILD_CHANNEL not in available_channels:\n            return None\n\n        current_version = httpie.__version__\n        last_released_version = available_channels[BUILD_CHANNEL]\n        if not is_version_greater(last_released_version, current_version):\n            return None\n\n        text = UPDATE_MESSAGE_FORMAT.format(\n            last_released_version=last_released_version,\n            installation_method=BUILD_CHANNEL,\n        )\n        return text\n\n\ndef get_update_status(env: Environment) -> str:\n    return _get_update_status(env) or ALREADY_UP_TO_DATE_MESSAGE\n\n\n@_update_checker\ndef check_updates(env: Environment) -> None:\n    if env.config.get('disable_update_warnings'):\n        return None\n\n    file = env.config.version_info_file\n    update_status = _get_update_status(env)\n\n    if not update_status:\n        return None\n\n    # If the user quickly spawns multiple httpie processes\n    # we don't want to end in a race.\n    with open_with_lockfile(file) as stream:\n        version_info = json.load(stream)\n\n    # We don't want to spam the user with too many warnings,\n    # so we'll only warn every once a while (WARN_INTERNAL).\n    current_date = datetime.now()\n    last_warned_date = version_info['last_warned_date']\n    if last_warned_date is not None:\n        earliest_warn_date = (\n            datetime.fromisoformat(last_warned_date) + WARN_INTERVAL\n        )\n        if current_date < earliest_warn_date:\n            return None\n\n    env.log_error(update_status, level=LogLevel.INFO)\n    version_info['last_warned_date'] = current_date.isoformat()\n\n    with open_with_lockfile(file, 'w') as stream:\n        json.dump(version_info, stream)\n"
  },
  {
    "path": "httpie/legacy/__init__.py",
    "content": ""
  },
  {
    "path": "httpie/legacy/v3_1_0_session_cookie_format.py",
    "content": "import argparse\nfrom typing import Any, Type, List, Dict, TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from httpie.sessions import Session\n\n\nINSECURE_COOKIE_JAR_WARNING = '''\\\nOutdated layout detected for the current session. Please consider updating it,\nin order to not get affected by potential security problems.\n\nFor fixing the current session:\n\n    With binding all cookies to the current host (secure):\n        $ httpie cli sessions upgrade --bind-cookies {hostname} {session_id}\n\n    Without binding cookies (leaving them as is) (insecure):\n        $ httpie cli sessions upgrade {hostname} {session_id}\n'''\n\n\nINSECURE_COOKIE_JAR_WARNING_FOR_NAMED_SESSIONS = '''\\\n\nFor fixing all named sessions:\n\n    With binding all cookies to the current host (secure):\n        $ httpie cli sessions upgrade-all --bind-cookies\n\n    Without binding cookies (leaving them as is) (insecure):\n        $ httpie cli sessions upgrade-all\n'''\n\nINSECURE_COOKIE_SECURITY_LINK = '\\nSee https://pie.co/docs/security for more information.'\n\n\ndef pre_process(session: 'Session', cookies: Any) -> List[Dict[str, Any]]:\n    \"\"\"Load the given cookies to the cookie jar while maintaining\n    support for the old cookie layout.\"\"\"\n\n    is_old_style = isinstance(cookies, dict)\n    if is_old_style:\n        normalized_cookies = [\n            {\n                'name': key,\n                **value\n            }\n            for key, value in cookies.items()\n        ]\n    else:\n        normalized_cookies = cookies\n\n    should_issue_warning = is_old_style and any(\n        cookie.get('domain', '') == ''\n        for cookie in normalized_cookies\n    )\n\n    if should_issue_warning:\n        warning = INSECURE_COOKIE_JAR_WARNING.format(hostname=session.bound_host, session_id=session.session_id)\n        if not session.is_anonymous:\n            warning += INSECURE_COOKIE_JAR_WARNING_FOR_NAMED_SESSIONS\n        warning += INSECURE_COOKIE_SECURITY_LINK\n        session.warn_legacy_usage(warning)\n\n    return normalized_cookies\n\n\ndef post_process(\n    normalized_cookies: List[Dict[str, Any]],\n    *,\n    original_type: Type[Any]\n) -> Any:\n    \"\"\"Convert the cookies to their original format for\n    maximum compatibility.\"\"\"\n\n    if issubclass(original_type, dict):\n        return {\n            cookie.pop('name'): cookie\n            for cookie in normalized_cookies\n        }\n    else:\n        return normalized_cookies\n\n\ndef fix_layout(session: 'Session', hostname: str, args: argparse.Namespace) -> None:\n    if not isinstance(session['cookies'], dict):\n        return None\n\n    session['cookies'] = [\n        {\n            'name': key,\n            **value\n        }\n        for key, value in session['cookies'].items()\n    ]\n    for cookie in session.cookies:\n        if cookie.domain == '':\n            if args.bind_cookies:\n                cookie.domain = hostname\n            else:\n                cookie._rest['is_explicit_none'] = True\n"
  },
  {
    "path": "httpie/legacy/v3_2_0_session_header_format.py",
    "content": "from typing import Any, Type, List, Dict, TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from httpie.sessions import Session\n\n\nOLD_HEADER_STORE_WARNING = '''\\\nOutdated layout detected for the current session. Please consider updating it,\nin order to use the latest features regarding the header layout.\n\nFor fixing the current session:\n\n    $ httpie cli sessions upgrade {hostname} {session_id}\n'''\n\nOLD_HEADER_STORE_WARNING_FOR_NAMED_SESSIONS = '''\\\n\nFor fixing all named sessions:\n\n    $ httpie cli sessions upgrade-all\n'''\n\nOLD_HEADER_STORE_LINK = '\\nSee $INSERT_LINK for more information.'\n\n\ndef pre_process(session: 'Session', headers: Any) -> List[Dict[str, Any]]:\n    \"\"\"Serialize the headers into a unified form and issue a warning if\n    the session file is using the old layout.\"\"\"\n\n    is_old_style = isinstance(headers, dict)\n    if is_old_style:\n        normalized_headers = list(headers.items())\n    else:\n        normalized_headers = [\n            (item['name'], item['value'])\n            for item in headers\n        ]\n\n    if is_old_style:\n        warning = OLD_HEADER_STORE_WARNING.format(hostname=session.bound_host, session_id=session.session_id)\n        if not session.is_anonymous:\n            warning += OLD_HEADER_STORE_WARNING_FOR_NAMED_SESSIONS\n        warning += OLD_HEADER_STORE_LINK\n        session.warn_legacy_usage(warning)\n\n    return normalized_headers\n\n\ndef post_process(\n    normalized_headers: List[Dict[str, Any]],\n    *,\n    original_type: Type[Any]\n) -> Any:\n    \"\"\"Deserialize given header store into the original form it was\n    used in.\"\"\"\n\n    if issubclass(original_type, dict):\n        # For the legacy behavior, preserve the last value.\n        return {\n            item['name']: item['value']\n            for item in normalized_headers\n        }\n    else:\n        return normalized_headers\n\n\ndef fix_layout(session: 'Session', *args, **kwargs) -> None:\n    from httpie.sessions import materialize_headers\n\n    if not isinstance(session['headers'], dict):\n        return None\n\n    session['headers'] = materialize_headers(session['headers'])\n"
  },
  {
    "path": "httpie/manager/__init__.py",
    "content": ""
  },
  {
    "path": "httpie/manager/__main__.py",
    "content": "import argparse\nimport sys\n\nfrom typing import List, Union\n\nfrom httpie.context import Environment\nfrom httpie.status import ExitStatus\nfrom httpie.manager.cli import parser\nfrom httpie.manager.core import MSG_COMMAND_CONFUSION, program as main_program\n\n\ndef is_http_command(args: List[Union[str, bytes]], env: Environment) -> bool:\n    \"\"\"Check whether http/https parser can parse the arguments.\"\"\"\n\n    from httpie.cli.definition import parser as http_parser\n    from httpie.manager.cli import COMMANDS\n\n    # If the user already selected a top-level sub-command, never\n    # show the http/https version. E.g httpie plugins pie.dev/post\n    if len(args) >= 1 and args[0] in COMMANDS:\n        return False\n\n    with env.as_silent():\n        try:\n            http_parser.parse_args(env=env, args=args)\n        except (Exception, SystemExit):\n            return False\n        else:\n            return True\n\n\ndef main(args: List[Union[str, bytes]] = sys.argv, env: Environment = Environment()) -> ExitStatus:\n    from httpie.core import raw_main\n\n    try:\n        return raw_main(\n            parser=parser,\n            main_program=main_program,\n            args=args,\n            env=env,\n            use_default_options=False,\n        )\n    except argparse.ArgumentError:\n        program_args = args[1:]\n        if is_http_command(program_args, env):\n            env.stderr.write(MSG_COMMAND_CONFUSION.format(args=' '.join(program_args)) + \"\\n\")\n\n        return ExitStatus.ERROR\n\n\ndef program():\n    try:\n        exit_status = main()\n    except KeyboardInterrupt:\n        exit_status = ExitStatus.ERROR_CTRL_C\n\n    return exit_status\n\n\nif __name__ == '__main__':  # pragma: nocover\n    sys.exit(program())\n"
  },
  {
    "path": "httpie/manager/cli.py",
    "content": "from textwrap import dedent\nfrom httpie.cli.argparser import HTTPieManagerArgumentParser\nfrom httpie.cli.options import Qualifiers, ARGPARSE_QUALIFIER_MAP, map_qualifiers, parser_to_parser_spec\nfrom httpie import __version__\n\nCLI_SESSION_UPGRADE_FLAGS = [\n    {\n        'flags': ['--bind-cookies'],\n        'action': 'store_true',\n        'default': False,\n        'help': 'Bind domainless cookies to the host that session belongs.'\n    }\n]\n\nCOMMANDS = {\n    'cli': {\n        'help': 'Manage HTTPie for Terminal',\n        'export-args': [\n            'Export available options for the CLI',\n            {\n                'flags': ['-f', '--format'],\n                'choices': ['json'],\n                'help': 'Format to export in.',\n                'default': 'json'\n            }\n        ],\n        'check-updates': [\n            'Check for updates'\n        ],\n        'sessions': {\n            'help': 'Manage HTTPie sessions',\n            'upgrade': [\n                'Upgrade the given HTTPie session with the latest '\n                'layout. A list of changes between different session versions '\n                'can be found in the official documentation.',\n                {\n                    'dest': 'hostname',\n                    'metavar': 'HOSTNAME',\n                    'help': 'The host this session belongs.'\n                },\n                {\n                    'dest': 'session',\n                    'metavar': 'SESSION_NAME_OR_PATH',\n                    'help': 'The name or the path for the session that will be upgraded.'\n                },\n                *CLI_SESSION_UPGRADE_FLAGS\n            ],\n            'upgrade-all': [\n                'Upgrade all named sessions with the latest layout. A list of '\n                'changes between different session versions can be found in the official '\n                'documentation.',\n                *CLI_SESSION_UPGRADE_FLAGS\n            ],\n        }\n    }\n}\n\n\nCOMMANDS['plugins'] = COMMANDS['cli']['plugins'] = {\n    'help': 'Manage HTTPie plugins.',\n    'install': [\n        'Install the given targets from PyPI '\n        'or from a local paths.',\n        {\n            'dest': 'targets',\n            'metavar': 'TARGET',\n            'nargs': Qualifiers.ONE_OR_MORE,\n            'help': 'targets to install'\n        }\n    ],\n    'upgrade': [\n        'Upgrade the given plugins',\n        {\n            'dest': 'targets',\n            'metavar': 'TARGET',\n            'nargs': Qualifiers.ONE_OR_MORE,\n            'help': 'targets to upgrade'\n        }\n    ],\n    'uninstall': [\n        'Uninstall the given HTTPie plugins.',\n        {\n            'dest': 'targets',\n            'metavar': 'TARGET',\n            'nargs': Qualifiers.ONE_OR_MORE,\n            'help': 'targets to install'\n        }\n    ],\n    'list': [\n        'List all installed HTTPie plugins.'\n    ],\n}\n\n\ndef missing_subcommand(*args) -> str:\n    base = COMMANDS\n    for arg in args:\n        base = base[arg]\n\n    assert isinstance(base, dict)\n    subcommands = ', '.join(map(repr, base.keys()))\n    return f'Please specify one of these: {subcommands}'\n\n\ndef generate_subparsers(root, parent_parser, definitions, spec):\n    action_dest = '_'.join(parent_parser.prog.split()[1:] + ['action'])\n    actions = parent_parser.add_subparsers(\n        dest=action_dest\n    )\n    for command, properties in definitions.items():\n        is_subparser = isinstance(properties, dict)\n        properties = properties.copy()\n\n        descr = properties.pop('help', None) if is_subparser else properties.pop(0)\n        command_parser = actions.add_parser(command, description=descr)\n        command_parser.root = root\n        if is_subparser:\n            generate_subparsers(root, command_parser, properties, spec)\n            continue\n\n        group = spec.add_group(parent_parser.prog + ' ' + command, description=descr)\n        for argument in properties:\n            argument = argument.copy()\n            flags = argument.pop('flags', [])\n            command_parser.add_argument(*flags, **map_qualifiers(argument, ARGPARSE_QUALIFIER_MAP))\n            group.add_argument(*flags, **argument)\n\n\nparser = HTTPieManagerArgumentParser(\n    prog='httpie',\n    description=dedent(\n        '''\n        Managing interface for the HTTPie itself. <https://httpie.io/docs#manager>\n\n        Be aware that you might be looking for http/https commands for sending\n        HTTP requests. This command is only available for managing the HTTTPie\n        plugins and the configuration around it.\n        '''\n    ),\n)\n\nparser.add_argument(\n    '--debug',\n    action='store_true',\n    default=False,\n    help='''\n    Prints the exception traceback should one occur, as well as other\n    information useful for debugging HTTPie itself and for reporting bugs.\n\n    '''\n)\n\nparser.add_argument(\n    '--traceback',\n    action='store_true',\n    default=False,\n    help='''\n    Prints the exception traceback should one occur.\n\n    '''\n)\n\nparser.add_argument(\n    '--version',\n    action='version',\n    version=__version__,\n    help='''\n    Show version and exit.\n\n    '''\n)\n\nman_page_hint = '''\nIf you are looking for the man pages of http/https commands, try one of the following:\n    $ man http\n    $ man https\n\n'''\n\noptions = parser_to_parser_spec(parser, man_page_hint=man_page_hint, source_file=__file__)\ngenerate_subparsers(parser, parser, COMMANDS, options)\n"
  },
  {
    "path": "httpie/manager/compat.py",
    "content": "import sys\nimport shutil\nimport subprocess\n\nfrom contextlib import suppress\nfrom typing import List, Optional\nfrom httpie.compat import is_frozen\n\n\nclass PipError(Exception):\n    \"\"\"An exception that occurs when pip exits with an error status code.\"\"\"\n\n    def __init__(self, stdout, stderr):\n        self.stdout = stdout\n        self.stderr = stderr\n\n\ndef _discover_system_pip() -> List[str]:\n    # When we are running inside of a frozen binary, we need the system\n    # pip to install plugins since there is no way for us to execute any\n    # code outside of the HTTPie.\n    #\n    # We explicitly depend on system pip, so the SystemError should not\n    # be executed (except for broken installations).\n    def _check_pip_version(pip_location: Optional[str]) -> bool:\n        if not pip_location:\n            return False\n\n        with suppress(subprocess.CalledProcessError):\n            stdout = subprocess.check_output([pip_location, \"--version\"], text=True)\n            return \"python 3\" in stdout\n\n    targets = [\n        \"pip\",\n        \"pip3\"\n    ]\n    for target in targets:\n        pip_location = shutil.which(target)\n        if _check_pip_version(pip_location):\n            return pip_location\n\n    raise SystemError(\"Couldn't find 'pip' executable. Please ensure that pip in your system is available.\")\n\n\ndef _run_pip_subprocess(pip_executable: List[str], args: List[str]) -> bytes:\n\n    cmd = [*pip_executable, *args]\n    try:\n        process = subprocess.run(\n            cmd,\n            check=True,\n            shell=False,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE\n        )\n    except subprocess.CalledProcessError as error:\n        raise PipError(error.stdout, error.stderr) from error\n    else:\n        return process.stdout\n\n\ndef run_pip(args: List[str]) -> bytes:\n    if is_frozen:\n        pip_executable = [_discover_system_pip()]\n    else:\n        pip_executable = [sys.executable, '-m', 'pip']\n\n    return _run_pip_subprocess(pip_executable, args)\n"
  },
  {
    "path": "httpie/manager/core.py",
    "content": "import argparse\nfrom typing import Optional\n\nfrom httpie.context import Environment\nfrom httpie.status import ExitStatus\nfrom httpie.manager.cli import missing_subcommand, parser\nfrom httpie.manager.tasks import CLI_TASKS\n\nMSG_COMMAND_CONFUSION = '''\\\nThis command is only for managing HTTPie plugins.\nTo send a request, please use the http/https commands:\n\n  $ http {args}\n\n  $ https {args}\n'''\n\n# noinspection PyStringFormat\nMSG_NAKED_INVOCATION = f'''\\\n{missing_subcommand()}\n\n{MSG_COMMAND_CONFUSION}\n'''.rstrip(\"\\n\").format(args='POST pie.dev/post hello=world')\n\n\ndef dispatch_cli_task(env: Environment, action: Optional[str], args: argparse.Namespace) -> ExitStatus:\n    if action is None:\n        parser.error(missing_subcommand('cli'))\n\n    return CLI_TASKS[action](env, args)\n\n\ndef program(args: argparse.Namespace, env: Environment) -> ExitStatus:\n    if args.action is None:\n        parser.error(MSG_NAKED_INVOCATION)\n\n    if args.action == 'plugins':\n        return dispatch_cli_task(env, args.action, args)\n    elif args.action == 'cli':\n        return dispatch_cli_task(env, args.cli_action, args)\n\n    return ExitStatus.SUCCESS\n"
  },
  {
    "path": "httpie/manager/tasks/__init__.py",
    "content": "from httpie.manager.tasks.sessions import cli_sessions\nfrom httpie.manager.tasks.export_args import cli_export_args\nfrom httpie.manager.tasks.plugins import cli_plugins\nfrom httpie.manager.tasks.check_updates import cli_check_updates\n\nCLI_TASKS = {\n    'sessions': cli_sessions,\n    'export-args': cli_export_args,\n    'plugins': cli_plugins,\n    'check-updates': cli_check_updates\n}\n"
  },
  {
    "path": "httpie/manager/tasks/check_updates.py",
    "content": "import argparse\nfrom httpie.context import Environment\nfrom httpie.status import ExitStatus\nfrom httpie.internal.update_warnings import fetch_updates, get_update_status\n\n\ndef cli_check_updates(env: Environment, args: argparse.Namespace) -> ExitStatus:\n    fetch_updates(env, lazy=False)\n    env.stdout.write(get_update_status(env))\n    return ExitStatus.SUCCESS\n"
  },
  {
    "path": "httpie/manager/tasks/export_args.py",
    "content": "import argparse\nimport json\n\nfrom httpie.cli.definition import options\nfrom httpie.cli.options import to_data\nfrom httpie.output.writer import write_raw_data\nfrom httpie.status import ExitStatus\nfrom httpie.context import Environment\n\n\nFORMAT_TO_CONTENT_TYPE = {\n    'json': 'application/json'\n}\n\n\ndef cli_export_args(env: Environment, args: argparse.Namespace) -> ExitStatus:\n    if args.format == 'json':\n        data = json.dumps(to_data(options))\n    else:\n        raise NotImplementedError(f'Unexpected format value: {args.format}')\n\n    write_raw_data(\n        env,\n        data,\n        stream_kwargs={'mime_overwrite': FORMAT_TO_CONTENT_TYPE[args.format]},\n    )\n    return ExitStatus.SUCCESS\n"
  },
  {
    "path": "httpie/manager/tasks/plugins.py",
    "content": "import argparse\nimport os\nimport textwrap\nimport re\nimport shutil\nfrom collections import defaultdict\nfrom contextlib import suppress\nfrom pathlib import Path\nfrom typing import List, Optional, Tuple\n\nfrom httpie.manager.compat import PipError, run_pip\nfrom httpie.manager.cli import parser, missing_subcommand\nfrom httpie.compat import get_dist_name, importlib_metadata\nfrom httpie.context import Environment\nfrom httpie.status import ExitStatus\nfrom httpie.utils import get_site_paths\n\nPEP_503 = re.compile(r\"[-_.]+\")\n\n\nclass PluginInstaller:\n\n    def __init__(self, env: Environment, debug: bool = False) -> None:\n        self.env = env\n        self.dir = env.config.plugins_dir\n        self.debug = debug\n\n        self.setup_plugins_dir()\n\n    def setup_plugins_dir(self) -> None:\n        try:\n            self.dir.mkdir(\n                exist_ok=True,\n                parents=True\n            )\n        except OSError:\n            self.env.stderr.write(\n                f'Couldn\\'t create \"{self.dir!s}\"'\n                ' directory for plugin installation.'\n                ' Please re-check the permissions for that directory,'\n                ' and if needed, allow write-access.'\n            )\n            raise\n\n    def fail(\n        self,\n        command: str,\n        target: Optional[str] = None,\n        reason: Optional[str] = None\n    ) -> ExitStatus:\n        message = f'Can\\'t {command}'\n        if target:\n            message += f' {target!r}'\n        if reason:\n            message += f': {reason}'\n\n        self.env.stderr.write(message + '\\n')\n        return ExitStatus.ERROR\n\n    def _install(self, targets: List[str], mode='install') -> Tuple[\n        bytes, ExitStatus\n    ]:\n        pip_args = [\n            'install',\n            '--prefer-binary',\n            f'--prefix={self.dir}',\n            '--no-warn-script-location',\n        ]\n        if mode == 'upgrade':\n            pip_args.append('--upgrade')\n        pip_args.extend(targets)\n\n        try:\n            stdout = run_pip(pip_args)\n        except PipError as pip_error:\n            error = pip_error\n            stdout = pip_error.stdout\n        else:\n            error = None\n\n        self.env.stdout.write(stdout.decode())\n\n        if error:\n            reason = None\n            if error.stderr:\n                stderr = error.stderr.decode()\n\n                if self.debug:\n                    self.env.stderr.write('Command failed: ')\n                    self.env.stderr.write('pip ' + ' '.join(pip_args) + '\\n')\n                    self.env.stderr.write(textwrap.indent('  ', stderr))\n\n                last_line = stderr.strip().splitlines()[-1]\n                severity, _, message = last_line.partition(': ')\n                if severity == 'ERROR':\n                    reason = message\n\n            stdout = error.stdout\n            exit_status = self.fail(mode, ', '.join(targets), reason)\n        else:\n            exit_status = ExitStatus.SUCCESS\n\n        return stdout, exit_status\n\n    def install(self, targets: List[str]) -> ExitStatus:\n        self.env.stdout.write(f\"Installing {', '.join(targets)}...\\n\")\n        self.env.stdout.flush()\n        _, exit_status = self._install(targets)\n        return exit_status\n\n    def _clear_metadata(self, targets: List[str]) -> None:\n        # Due to an outstanding pip problem[0], we have to get rid of\n        # existing metadata for old versions manually.\n        # [0]: https://github.com/pypa/pip/issues/10727\n        result_deps = defaultdict(list)\n        for site_dir in get_site_paths(self.dir):\n            for child in site_dir.iterdir():\n                if child.suffix in {'.dist-info', '.egg-info'}:\n                    name, _, version = child.stem.rpartition('-')\n                    result_deps[name].append((version, child))\n\n        for target in targets:\n            name, _, version = target.rpartition('-')\n            name = PEP_503.sub(\"-\", name).lower().replace('-', '_')\n            if name not in result_deps:\n                continue\n\n            for result_version, meta_path in result_deps[name]:\n                if version != result_version:\n                    shutil.rmtree(meta_path)\n\n    def upgrade(self, targets: List[str]) -> ExitStatus:\n        self.env.stdout.write(f\"Upgrading {', '.join(targets)}...\\n\")\n        self.env.stdout.flush()\n\n        raw_stdout, exit_status = self._install(\n            targets,\n            mode='upgrade'\n        )\n        if not raw_stdout:\n            return exit_status\n\n        stdout = raw_stdout.decode()\n        installation_line = stdout.splitlines()[-1]\n        if installation_line.startswith('Successfully installed'):\n            self._clear_metadata(installation_line.split()[2:])\n\n    def _uninstall(self, target: str) -> Optional[ExitStatus]:\n        try:\n            distribution = importlib_metadata.distribution(target)\n        except importlib_metadata.PackageNotFoundError:\n            return self.fail('uninstall', target, 'package is not installed')\n\n        base_dir = Path(distribution.locate_file('.')).resolve()\n        if self.dir not in base_dir.parents:\n            # If the package is installed somewhere else (e.g on the site packages\n            # of the real python interpreter), than that means this package is not\n            # installed through us.\n            return self.fail('uninstall', target,\n                             'package is not installed through httpie plugins'\n                             ' interface')\n\n        files = distribution.files\n        if files is None:\n            return self.fail('uninstall', target, 'couldn\\'t locate the package')\n\n        # TODO: Consider handling failures here (e.g if it fails,\n        # just revert the operation and leave the site-packages\n        # in a proper shape).\n        for file in files:\n            with suppress(FileNotFoundError):\n                os.unlink(distribution.locate_file(file))\n\n        metadata_path = getattr(distribution, '_path', None)\n        if (\n            metadata_path\n            and metadata_path.exists()\n            and not any(metadata_path.iterdir())\n        ):\n            metadata_path.rmdir()\n\n        self.env.stdout.write(f'Successfully uninstalled {target}\\n')\n\n    def uninstall(self, targets: List[str]) -> ExitStatus:\n        # Unfortunately uninstall doesn't work with custom pip schemes. See:\n        # - https://github.com/pypa/pip/issues/5595\n        # - https://github.com/pypa/pip/issues/4575\n        # so we have to implement our own uninstalling logic. Which works\n        # on top of the importlib_metadata.\n\n        exit_code = ExitStatus.SUCCESS\n        for target in targets:\n            exit_code |= self._uninstall(target) or ExitStatus.SUCCESS\n        return ExitStatus(exit_code)\n\n    def list(self) -> None:\n        from httpie.plugins.registry import plugin_manager\n\n        known_plugins = defaultdict(list)\n\n        for entry_point in plugin_manager.iter_entry_points(self.dir):\n            ep_info = (entry_point.group, entry_point.name)\n            ep_name = get_dist_name(entry_point) or entry_point.module\n            known_plugins[ep_name].append(ep_info)\n\n        for plugin, entry_points in known_plugins.items():\n            self.env.stdout.write(plugin)\n\n            version = importlib_metadata.version(plugin)\n            if version is not None:\n                self.env.stdout.write(f' ({version})')\n            self.env.stdout.write('\\n')\n\n            for group, entry_point in sorted(entry_points):\n                self.env.stdout.write(f'  {entry_point} ({group})\\n')\n\n    def run(\n        self,\n        action: Optional[str],\n        args: argparse.Namespace,\n    ) -> ExitStatus:\n        from httpie.plugins.manager import enable_plugins\n\n        if action is None:\n            parser.error(missing_subcommand('plugins'))\n\n        with enable_plugins(self.dir):\n            if action == 'install':\n                status = self.install(args.targets)\n            elif action == 'upgrade':\n                status = self.upgrade(args.targets)\n            elif action == 'uninstall':\n                status = self.uninstall(args.targets)\n            elif action == 'list':\n                status = self.list()\n\n        return status or ExitStatus.SUCCESS\n\n\ndef cli_plugins(env: Environment, args: argparse.Namespace) -> ExitStatus:\n    plugins = PluginInstaller(env, debug=args.debug)\n\n    try:\n        action = args.cli_plugins_action\n    except AttributeError:\n        action = args.plugins_action\n\n    return plugins.run(action, args)\n"
  },
  {
    "path": "httpie/manager/tasks/sessions.py",
    "content": "import argparse\n\nfrom httpie.sessions import SESSIONS_DIR_NAME, get_httpie_session\nfrom httpie.status import ExitStatus\nfrom httpie.context import Environment\nfrom httpie.legacy import v3_1_0_session_cookie_format, v3_2_0_session_header_format\nfrom httpie.manager.cli import missing_subcommand, parser\nfrom httpie.utils import is_version_greater\n\n\nFIXERS_TO_VERSIONS = {\n    '3.1.0': v3_1_0_session_cookie_format.fix_layout,\n    '3.2.0': v3_2_0_session_header_format.fix_layout,\n}\n\n\ndef cli_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus:\n    action = args.cli_sessions_action\n    if action is None:\n        parser.error(missing_subcommand('cli', 'sessions'))\n\n    if action == 'upgrade':\n        return cli_upgrade_session(env, args)\n    elif action == 'upgrade-all':\n        return cli_upgrade_all_sessions(env, args)\n    else:\n        raise ValueError(f'Unexpected action: {action}')\n\n\ndef upgrade_session(env: Environment, args: argparse.Namespace, hostname: str, session_name: str):\n    session = get_httpie_session(\n        env=env,\n        config_dir=env.config.directory,\n        session_name=session_name,\n        host=hostname,\n        url=hostname,\n        suppress_legacy_warnings=True\n    )\n\n    session_name = session.path.stem\n    if session.is_new():\n        env.log_error(f'{session_name!r} @ {hostname!r} does not exist.')\n        return ExitStatus.ERROR\n\n    fixers = [\n        fixer\n        for version, fixer in FIXERS_TO_VERSIONS.items()\n        if is_version_greater(version, session.version)\n    ]\n\n    if len(fixers) == 0:\n        env.stdout.write(f'{session_name!r} @ {hostname!r} is already up to date.\\n')\n        return ExitStatus.SUCCESS\n\n    for fixer in fixers:\n        fixer(session, hostname, args)\n\n    session.save(bump_version=True)\n    env.stdout.write(f'Upgraded {session_name!r} @ {hostname!r} to v{session.version}\\n')\n    return ExitStatus.SUCCESS\n\n\ndef cli_upgrade_session(env: Environment, args: argparse.Namespace) -> ExitStatus:\n    return upgrade_session(\n        env,\n        args=args,\n        hostname=args.hostname,\n        session_name=args.session\n    )\n\n\ndef cli_upgrade_all_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus:\n    session_dir_path = env.config_dir / SESSIONS_DIR_NAME\n\n    status = ExitStatus.SUCCESS\n    for host_path in session_dir_path.iterdir():\n        hostname = host_path.name\n        for session_path in host_path.glob(\"*.json\"):\n            session_name = session_path.stem\n            status |= upgrade_session(\n                env,\n                args=args,\n                hostname=hostname,\n                session_name=session_name\n            )\n    return status\n"
  },
  {
    "path": "httpie/models.py",
    "content": "from time import monotonic\n\nimport requests\nfrom urllib3.util import SKIP_HEADER, SKIPPABLE_HEADERS\n\nfrom enum import Enum, auto\nfrom typing import Iterable, Union, NamedTuple\nfrom urllib.parse import urlsplit\n\nfrom .cli.constants import (\n    OUT_REQ_BODY,\n    OUT_REQ_HEAD,\n    OUT_RESP_BODY,\n    OUT_RESP_HEAD,\n    OUT_RESP_META\n)\nfrom .compat import cached_property\nfrom .utils import split_cookies, parse_content_type_header\n\nELAPSED_TIME_LABEL = 'Elapsed time'\n\n\nclass HTTPMessage:\n    \"\"\"Abstract class for HTTP messages.\"\"\"\n\n    def __init__(self, orig):\n        self._orig = orig\n\n    def iter_body(self, chunk_size: int) -> Iterable[bytes]:\n        \"\"\"Return an iterator over the body.\"\"\"\n        raise NotImplementedError\n\n    def iter_lines(self, chunk_size: int) -> Iterable[bytes]:\n        \"\"\"Return an iterator over the body yielding (`line`, `line_feed`).\"\"\"\n        raise NotImplementedError\n\n    @property\n    def headers(self) -> str:\n        \"\"\"Return a `str` with the message's headers.\"\"\"\n        raise NotImplementedError\n\n    @property\n    def metadata(self) -> str:\n        \"\"\"Return metadata about the current message.\"\"\"\n        raise NotImplementedError\n\n    @cached_property\n    def encoding(self) -> str:\n        ct, params = parse_content_type_header(self.content_type)\n        return params.get('charset', '')\n\n    @property\n    def content_type(self) -> str:\n        \"\"\"Return the message content type.\"\"\"\n        ct = self._orig.headers.get('Content-Type', '')\n        if not isinstance(ct, str):\n            ct = ct.decode()\n        return ct\n\n\nclass HTTPResponse(HTTPMessage):\n    \"\"\"A :class:`requests.models.Response` wrapper.\"\"\"\n\n    def iter_body(self, chunk_size=1):\n        return self._orig.iter_content(chunk_size=chunk_size)\n\n    def iter_lines(self, chunk_size):\n        return ((line, b'\\n') for line in self._orig.iter_lines(chunk_size))\n\n    @property\n    def headers(self):\n        original = self._orig\n        status_line = f'HTTP/{self.version} {original.status_code} {original.reason}'\n        headers = [status_line]\n        headers.extend(\n            ': '.join(header)\n            for header in original.headers.items()\n            if header[0] != 'Set-Cookie'\n        )\n        headers.extend(\n            f'Set-Cookie: {cookie}'\n            for header, value in original.headers.items()\n            for cookie in split_cookies(value)\n            if header == 'Set-Cookie'\n        )\n        return '\\r\\n'.join(headers)\n\n    @property\n    def metadata(self) -> str:\n        data = {}\n        time_to_parse_headers = self._orig.elapsed.total_seconds()\n        # noinspection PyProtectedMember\n        time_since_headers_parsed = monotonic() - self._orig._httpie_headers_parsed_at\n        time_elapsed = time_to_parse_headers + time_since_headers_parsed\n        # data['Headers time'] = str(round(time_to_parse_headers, 5)) + 's'\n        # data['Body time'] = str(round(time_since_headers_parsed, 5)) + 's'\n        data[ELAPSED_TIME_LABEL] = str(round(time_elapsed, 10)) + 's'\n        return '\\n'.join(\n            f'{key}: {value}'\n            for key, value in data.items()\n        )\n\n    @property\n    def version(self) -> str:\n        \"\"\"\n        Return the HTTP version used by the server, e.g. '1.1'.\n\n        Assume HTTP/1.1 if version is not available.\n\n        \"\"\"\n        mapping = {\n            9: '0.9',\n            10: '1.0',\n            11: '1.1',\n            20: '2.0',\n        }\n        fallback = 11\n        version = None\n        try:\n            raw = self._orig.raw\n            if getattr(raw, '_original_response', None):\n                version = raw._original_response.version\n            else:\n                version = raw.version\n        except AttributeError:\n            pass\n        return mapping[version or fallback]\n\n\nclass HTTPRequest(HTTPMessage):\n    \"\"\"A :class:`requests.models.Request` wrapper.\"\"\"\n\n    def iter_body(self, chunk_size):\n        yield self.body\n\n    def iter_lines(self, chunk_size):\n        yield self.body, b''\n\n    @property\n    def headers(self):\n        url = urlsplit(self._orig.url)\n\n        request_line = '{method} {path}{query} HTTP/1.1'.format(\n            method=self._orig.method,\n            path=url.path or '/',\n            query=f'?{url.query}' if url.query else ''\n        )\n\n        headers = self._orig.headers.copy()\n        if 'Host' not in self._orig.headers:\n            headers['Host'] = url.netloc.split('@')[-1]\n\n        headers = [\n            f'{name}: {value if isinstance(value, str) else value.decode()}'\n            for name, value in headers.items()\n            if not (name.lower() in SKIPPABLE_HEADERS and value == SKIP_HEADER)\n        ]\n\n        headers.insert(0, request_line)\n        headers = '\\r\\n'.join(headers).strip()\n        return headers\n\n    @property\n    def body(self):\n        body = self._orig.body\n        if isinstance(body, str):\n            # Happens with JSON/form request data parsed from the command line.\n            body = body.encode()\n        return body or b''\n\n\nRequestsMessage = Union[requests.PreparedRequest, requests.Response]\n\n\nclass RequestsMessageKind(Enum):\n    REQUEST = auto()\n    RESPONSE = auto()\n\n\ndef infer_requests_message_kind(message: RequestsMessage) -> RequestsMessageKind:\n    if isinstance(message, requests.PreparedRequest):\n        return RequestsMessageKind.REQUEST\n    elif isinstance(message, requests.Response):\n        return RequestsMessageKind.RESPONSE\n    else:\n        raise TypeError(f\"Unexpected message type: {type(message).__name__}\")\n\n\nOPTION_TO_PARAM = {\n    RequestsMessageKind.REQUEST: {\n        'headers': OUT_REQ_HEAD,\n        'body': OUT_REQ_BODY,\n    },\n    RequestsMessageKind.RESPONSE: {\n        'headers': OUT_RESP_HEAD,\n        'body': OUT_RESP_BODY,\n        'meta': OUT_RESP_META\n    }\n}\n\n\nclass OutputOptions(NamedTuple):\n    kind: RequestsMessageKind\n    headers: bool\n    body: bool\n    meta: bool = False\n\n    def any(self):\n        return (\n            self.headers\n            or self.body\n            or self.meta\n        )\n\n    @classmethod\n    def from_message(\n        cls,\n        message: RequestsMessage,\n        raw_args: str = '',\n        **kwargs\n    ):\n        kind = infer_requests_message_kind(message)\n\n        options = {\n            option: param in raw_args\n            for option, param in OPTION_TO_PARAM[kind].items()\n        }\n        options.update(kwargs)\n\n        return cls(\n            kind=kind,\n            **options\n        )\n"
  },
  {
    "path": "httpie/output/__init__.py",
    "content": ""
  },
  {
    "path": "httpie/output/formatters/__init__.py",
    "content": ""
  },
  {
    "path": "httpie/output/formatters/colors.py",
    "content": "import json\nfrom typing import Optional, Type, Tuple\n\nimport pygments.formatters\nimport pygments.lexer\nimport pygments.lexers\nimport pygments.style\nimport pygments.styles\nimport pygments.token\nfrom pygments.formatters.terminal import TerminalFormatter\nfrom pygments.formatters.terminal256 import Terminal256Formatter\nfrom pygments.lexer import Lexer\nfrom pygments.lexers.data import JsonLexer\nfrom pygments.lexers.special import TextLexer\nfrom pygments.lexers.text import HttpLexer as PygmentsHttpLexer\nfrom pygments.util import ClassNotFound\n\nfrom ..lexers.json import EnhancedJsonLexer\nfrom ..lexers.metadata import MetadataLexer\nfrom ..ui.palette import AUTO_STYLE, SHADE_TO_PIE_STYLE, PieColor, ColorString, get_color\nfrom ...context import Environment\nfrom ...plugins import FormatterPlugin\n\n\nDEFAULT_STYLE = AUTO_STYLE\nSOLARIZED_STYLE = 'solarized'  # Bundled here\nPYGMENTS_BOLD = ColorString('bold')\nPYGMENTS_ITALIC = ColorString('italic')\n\nBUNDLED_STYLES = {\n    SOLARIZED_STYLE,\n    AUTO_STYLE\n}\n\n\ndef get_available_styles():\n    return sorted(BUNDLED_STYLES | set(pygments.styles.get_all_styles()))\n\n\nclass ColorFormatter(FormatterPlugin):\n    \"\"\"\n    Colorize using Pygments\n\n    This processor that applies syntax highlighting to the headers,\n    and also to the body if its content type is recognized.\n\n    \"\"\"\n    group_name = 'colors'\n    metadata_lexer = MetadataLexer()\n\n    def __init__(\n        self,\n        env: Environment,\n        explicit_json=False,\n        color_scheme=DEFAULT_STYLE,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n\n        if not env.colors:\n            self.enabled = False\n            return\n\n        use_auto_style = color_scheme == AUTO_STYLE\n        has_256_colors = env.colors == 256\n        if use_auto_style or not has_256_colors:\n            http_lexer = PygmentsHttpLexer()\n            body_formatter = header_formatter = TerminalFormatter()\n            precise = False\n        else:\n            from ..lexers.http import SimplifiedHTTPLexer\n            header_formatter, body_formatter, precise = self.get_formatters(color_scheme)\n            http_lexer = SimplifiedHTTPLexer(precise=precise)\n\n        self.explicit_json = explicit_json  # --json\n        self.header_formatter = header_formatter\n        self.body_formatter = body_formatter\n        self.http_lexer = http_lexer\n        self.metadata_lexer = MetadataLexer(precise=precise)\n\n    def format_headers(self, headers: str) -> str:\n        return pygments.highlight(\n            code=headers,\n            lexer=self.http_lexer,\n            formatter=self.header_formatter,\n        ).strip()\n\n    def format_body(self, body: str, mime: str) -> str:\n        lexer = self.get_lexer_for_body(mime, body)\n        if lexer:\n            body = pygments.highlight(\n                code=body,\n                lexer=lexer,\n                formatter=self.body_formatter,\n            )\n        return body\n\n    def format_metadata(self, metadata: str) -> str:\n        return pygments.highlight(\n            code=metadata,\n            lexer=self.metadata_lexer,\n            formatter=self.header_formatter,\n        ).strip()\n\n    def get_lexer_for_body(\n        self, mime: str,\n        body: str\n    ) -> Optional[Type[Lexer]]:\n        return get_lexer(\n            mime=mime,\n            explicit_json=self.explicit_json,\n            body=body,\n        )\n\n    def get_formatters(self, color_scheme: str) -> Tuple[\n        pygments.formatter.Formatter,\n        pygments.formatter.Formatter,\n        bool\n    ]:\n        if color_scheme in PIE_STYLES:\n            header_style, body_style = PIE_STYLES[color_scheme]\n            precise = True\n        else:\n            header_style = self.get_style_class(color_scheme)\n            body_style = header_style\n            precise = False\n\n        return (\n            Terminal256Formatter(style=header_style),\n            Terminal256Formatter(style=body_style),\n            precise\n        )\n\n    @staticmethod\n    def get_style_class(color_scheme: str) -> Type[pygments.style.Style]:\n        try:\n            return pygments.styles.get_style_by_name(color_scheme)\n        except ClassNotFound:\n            return Solarized256Style\n\n\ndef get_lexer(\n    mime: str,\n    explicit_json=False,\n    body=''\n) -> Optional[Type[Lexer]]:\n    # Build candidate mime type and lexer names.\n    mime_types, lexer_names = [mime], []\n    type_, subtype = mime.split('/', 1)\n    if '+' not in subtype:\n        lexer_names.append(subtype)\n    else:\n        subtype_name, subtype_suffix = subtype.split('+', 1)\n        lexer_names.extend([subtype_name, subtype_suffix])\n        mime_types.extend([\n            f'{type_}/{subtype_name}',\n            f'{type_}/{subtype_suffix}',\n        ])\n\n    # As a last resort, if no lexer feels responsible, and\n    # the subtype contains 'json', take the JSON lexer\n    if 'json' in subtype:\n        lexer_names.append('json')\n\n    # Try to resolve the right lexer.\n    lexer = None\n    for mime_type in mime_types:\n        try:\n            lexer = pygments.lexers.get_lexer_for_mimetype(mime_type)\n            break\n        except ClassNotFound:\n            pass\n    else:\n        for name in lexer_names:\n            try:\n                lexer = pygments.lexers.get_lexer_by_name(name)\n            except ClassNotFound:\n                pass\n\n    if explicit_json and body and (not lexer or isinstance(lexer, TextLexer)):\n        # JSON response with an incorrect Content-Type?\n        try:\n            json.loads(body)  # FIXME: the body also gets parsed in json.py\n        except ValueError:\n            pass  # Nope\n        else:\n            lexer = pygments.lexers.get_lexer_by_name('json')\n\n    # Use our own JSON lexer: it supports JSON bodies preceded by non-JSON data\n    # as well as legit JSON bodies.\n    if isinstance(lexer, JsonLexer):\n        lexer = EnhancedJsonLexer()\n\n    return lexer\n\n\nclass Solarized256Style(pygments.style.Style):\n    \"\"\"\n    solarized256\n    ------------\n\n    A Pygments style inspired by Solarized's 256 color mode.\n\n    :copyright: (c) 2011 by Hank Gay, (c) 2012 by John Mastro.\n    :license: BSD, see LICENSE for more details.\n\n    \"\"\"\n    BASE03 = \"#1c1c1c\"\n    BASE02 = \"#262626\"\n    BASE01 = \"#4e4e4e\"\n    BASE00 = \"#585858\"\n    BASE0 = \"#808080\"\n    BASE1 = \"#8a8a8a\"\n    BASE2 = \"#d7d7af\"\n    BASE3 = \"#ffffd7\"\n    YELLOW = \"#af8700\"\n    ORANGE = \"#d75f00\"\n    RED = \"#af0000\"\n    MAGENTA = \"#af005f\"\n    VIOLET = \"#5f5faf\"\n    BLUE = \"#0087ff\"\n    CYAN = \"#00afaf\"\n    GREEN = \"#5f8700\"\n\n    background_color = BASE03\n    styles = {\n        pygments.token.Keyword: GREEN,\n        pygments.token.Keyword.Constant: ORANGE,\n        pygments.token.Keyword.Declaration: BLUE,\n        pygments.token.Keyword.Namespace: ORANGE,\n        pygments.token.Keyword.Reserved: BLUE,\n        pygments.token.Keyword.Type: RED,\n        pygments.token.Name.Attribute: BASE1,\n        pygments.token.Name.Builtin: BLUE,\n        pygments.token.Name.Builtin.Pseudo: BLUE,\n        pygments.token.Name.Class: BLUE,\n        pygments.token.Name.Constant: ORANGE,\n        pygments.token.Name.Decorator: BLUE,\n        pygments.token.Name.Entity: ORANGE,\n        pygments.token.Name.Exception: YELLOW,\n        pygments.token.Name.Function: BLUE,\n        pygments.token.Name.Tag: BLUE,\n        pygments.token.Name.Variable: BLUE,\n        pygments.token.String: CYAN,\n        pygments.token.String.Backtick: BASE01,\n        pygments.token.String.Char: CYAN,\n        pygments.token.String.Doc: CYAN,\n        pygments.token.String.Escape: RED,\n        pygments.token.String.Heredoc: CYAN,\n        pygments.token.String.Regex: RED,\n        pygments.token.Number: CYAN,\n        pygments.token.Operator: BASE1,\n        pygments.token.Operator.Word: GREEN,\n        pygments.token.Comment: BASE01,\n        pygments.token.Comment.Preproc: GREEN,\n        pygments.token.Comment.Special: GREEN,\n        pygments.token.Generic.Deleted: CYAN,\n        pygments.token.Generic.Emph: PYGMENTS_ITALIC,\n        pygments.token.Generic.Error: RED,\n        pygments.token.Generic.Heading: ORANGE,\n        pygments.token.Generic.Inserted: GREEN,\n        pygments.token.Generic.Strong: PYGMENTS_BOLD,\n        pygments.token.Generic.Subheading: ORANGE,\n        pygments.token.Token: BASE1,\n        pygments.token.Token.Other: ORANGE,\n    }\n\n\nPIE_HEADER_STYLE = {\n    # HTTP line / Headers / Etc.\n    pygments.token.Name.Namespace: PYGMENTS_BOLD | PieColor.PRIMARY,\n    pygments.token.Keyword.Reserved: PYGMENTS_BOLD | PieColor.GREY,\n    pygments.token.Operator: PYGMENTS_BOLD | PieColor.GREY,\n    pygments.token.Number: PYGMENTS_BOLD | PieColor.GREY,\n    pygments.token.Name.Function.Magic: PYGMENTS_BOLD | PieColor.GREEN,\n    pygments.token.Name.Exception: PYGMENTS_BOLD | PieColor.GREEN,\n    pygments.token.Name.Attribute: PieColor.BLUE,\n    pygments.token.String: PieColor.PRIMARY,\n\n    # HTTP Methods\n    pygments.token.Name.Function: PYGMENTS_BOLD | PieColor.GREY,\n    pygments.token.Name.Function.HTTP.GET: PYGMENTS_BOLD | PieColor.GREEN,\n    pygments.token.Name.Function.HTTP.HEAD: PYGMENTS_BOLD | PieColor.GREEN,\n    pygments.token.Name.Function.HTTP.POST: PYGMENTS_BOLD | PieColor.YELLOW,\n    pygments.token.Name.Function.HTTP.PUT: PYGMENTS_BOLD | PieColor.ORANGE,\n    pygments.token.Name.Function.HTTP.PATCH: PYGMENTS_BOLD | PieColor.ORANGE,\n    pygments.token.Name.Function.HTTP.DELETE: PYGMENTS_BOLD | PieColor.RED,\n\n    # HTTP status codes\n    pygments.token.Number.HTTP.INFO: PYGMENTS_BOLD | PieColor.AQUA,\n    pygments.token.Number.HTTP.OK: PYGMENTS_BOLD | PieColor.GREEN,\n    pygments.token.Number.HTTP.REDIRECT: PYGMENTS_BOLD | PieColor.YELLOW,\n    pygments.token.Number.HTTP.CLIENT_ERR: PYGMENTS_BOLD | PieColor.ORANGE,\n    pygments.token.Number.HTTP.SERVER_ERR: PYGMENTS_BOLD | PieColor.RED,\n\n    # Metadata\n    pygments.token.Name.Decorator: PieColor.GREY,\n    pygments.token.Number.SPEED.FAST: PYGMENTS_BOLD | PieColor.GREEN,\n    pygments.token.Number.SPEED.AVG: PYGMENTS_BOLD | PieColor.YELLOW,\n    pygments.token.Number.SPEED.SLOW: PYGMENTS_BOLD | PieColor.ORANGE,\n    pygments.token.Number.SPEED.VERY_SLOW: PYGMENTS_BOLD | PieColor.RED,\n}\n\nPIE_BODY_STYLE = {\n    # {}[]:\n    pygments.token.Punctuation: PieColor.GREY,\n\n    # Keys\n    pygments.token.Name.Tag: PieColor.PINK,\n\n    # Values\n    pygments.token.Literal.String: PieColor.GREEN,\n    pygments.token.Literal.String.Double: PieColor.GREEN,\n    pygments.token.Literal.Number: PieColor.AQUA,\n    pygments.token.Keyword: PieColor.ORANGE,\n\n    # Other stuff\n    pygments.token.Text: PieColor.PRIMARY,\n    pygments.token.Name.Attribute: PieColor.PRIMARY,\n    pygments.token.Name.Builtin: PieColor.BLUE,\n    pygments.token.Name.Builtin.Pseudo: PieColor.BLUE,\n    pygments.token.Name.Class: PieColor.BLUE,\n    pygments.token.Name.Constant: PieColor.ORANGE,\n    pygments.token.Name.Decorator: PieColor.BLUE,\n    pygments.token.Name.Entity: PieColor.ORANGE,\n    pygments.token.Name.Exception: PieColor.YELLOW,\n    pygments.token.Name.Function: PieColor.BLUE,\n    pygments.token.Name.Variable: PieColor.BLUE,\n    pygments.token.String: PieColor.AQUA,\n    pygments.token.String.Backtick: PieColor.SECONDARY,\n    pygments.token.String.Char: PieColor.AQUA,\n    pygments.token.String.Doc: PieColor.AQUA,\n    pygments.token.String.Escape: PieColor.RED,\n    pygments.token.String.Heredoc: PieColor.AQUA,\n    pygments.token.String.Regex: PieColor.RED,\n    pygments.token.Number: PieColor.AQUA,\n    pygments.token.Operator: PieColor.PRIMARY,\n    pygments.token.Operator.Word: PieColor.GREEN,\n    pygments.token.Comment: PieColor.SECONDARY,\n    pygments.token.Comment.Preproc: PieColor.GREEN,\n    pygments.token.Comment.Special: PieColor.GREEN,\n    pygments.token.Generic.Deleted: PieColor.AQUA,\n    pygments.token.Generic.Emph: PYGMENTS_ITALIC,\n    pygments.token.Generic.Error: PieColor.RED,\n    pygments.token.Generic.Heading: PieColor.ORANGE,\n    pygments.token.Generic.Inserted: PieColor.GREEN,\n    pygments.token.Generic.Strong: PYGMENTS_BOLD,\n    pygments.token.Generic.Subheading: PieColor.ORANGE,\n    pygments.token.Token: PieColor.PRIMARY,\n    pygments.token.Token.Other: PieColor.ORANGE,\n}\n\n\ndef make_style(name, raw_styles, shade):\n    def format_value(value):\n        return ' '.join(\n            get_color(part, shade) or part\n            for part in value.split()\n        )\n\n    bases = (pygments.style.Style,)\n    data = {\n        'styles': {\n            key: format_value(value)\n            for key, value in raw_styles.items()\n        }\n    }\n    return type(name, bases, data)\n\n\ndef make_styles():\n    styles = {}\n\n    for shade, name in SHADE_TO_PIE_STYLE.items():\n        styles[name] = [\n            make_style(name, style_map, shade)\n            for style_name, style_map in [\n                (f'Pie{name}HeaderStyle', PIE_HEADER_STYLE),\n                (f'Pie{name}BodyStyle', PIE_BODY_STYLE),\n            ]\n        ]\n\n    return styles\n\n\nPIE_STYLES = make_styles()\nPIE_STYLE_NAMES = list(PIE_STYLES.keys())\nBUNDLED_STYLES |= PIE_STYLES.keys()\n"
  },
  {
    "path": "httpie/output/formatters/headers.py",
    "content": "from ...plugins import FormatterPlugin\n\n\nclass HeadersFormatter(FormatterPlugin):\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.enabled = self.format_options['headers']['sort']\n\n    def format_headers(self, headers: str) -> str:\n        \"\"\"\n        Sorts headers by name while retaining relative\n        order of multiple headers with the same name.\n\n        \"\"\"\n        lines = headers.splitlines()\n        headers = sorted(lines[1:], key=lambda h: h.split(':')[0])\n        return '\\r\\n'.join(lines[:1] + headers)\n"
  },
  {
    "path": "httpie/output/formatters/json.py",
    "content": "import json\n\nfrom ...plugins import FormatterPlugin\n\n\nclass JSONFormatter(FormatterPlugin):\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.enabled = self.format_options['json']['format']\n\n    def format_body(self, body: str, mime: str) -> str:\n        maybe_json = [\n            'json',\n            'javascript',\n            'text',\n        ]\n        if (self.kwargs['explicit_json']\n                or any(token in mime for token in maybe_json)):\n            from ..utils import load_prefixed_json\n            try:\n                data_prefix, json_obj = load_prefixed_json(body)\n            except ValueError:\n                pass  # Invalid JSON, ignore.\n            else:\n                # Indent, sort keys by name, and avoid\n                # unicode escapes to improve readability.\n                body = data_prefix + json.dumps(\n                    obj=json_obj,\n                    sort_keys=self.format_options['json']['sort_keys'],\n                    ensure_ascii=False,\n                    indent=self.format_options['json']['indent']\n                )\n        return body\n"
  },
  {
    "path": "httpie/output/formatters/xml.py",
    "content": "from typing import TYPE_CHECKING, Optional\n\nfrom ...encoding import UTF8\nfrom ...plugins import FormatterPlugin\n\nif TYPE_CHECKING:\n    from xml.dom.minidom import Document\n\n\nXML_DECLARATION_OPEN = '<?xml'\nXML_DECLARATION_CLOSE = '?>'\n\n\ndef parse_xml(data: str) -> 'Document':\n    \"\"\"Parse given XML `data` string into an appropriate :class:`~xml.dom.minidom.Document` object.\"\"\"\n    from defusedxml.minidom import parseString\n    return parseString(data)\n\n\ndef parse_declaration(raw_body: str) -> Optional[str]:\n    body = raw_body.strip()\n    # XMLDecl ::= '<?xml' DECL_CONTENT '?>'\n    if body.startswith(XML_DECLARATION_OPEN):\n        end = body.find(XML_DECLARATION_CLOSE)\n        if end != -1:\n            return body[:end + len(XML_DECLARATION_CLOSE)]\n\n\ndef pretty_xml(document: 'Document',\n               declaration: Optional[str] = None,\n               encoding: Optional[str] = UTF8,\n               indent: int = 2) -> str:\n    \"\"\"Render the given :class:`~xml.dom.minidom.Document` `document` into a prettified string.\"\"\"\n    kwargs = {\n        'encoding': encoding or UTF8,\n        'indent': ' ' * indent,\n    }\n    body = document.toprettyxml(**kwargs).decode(kwargs['encoding'])\n\n    # Remove blank lines automatically added by `toprettyxml()`.\n    lines = [line for line in body.splitlines() if line.strip()]\n\n    # xml.dom automatically adds the declaration, even if\n    # it is not present in the actual body. Remove it.\n    if len(lines) >= 1 and parse_declaration(lines[0]):\n        lines.pop(0)\n        if declaration:\n            lines.insert(0, declaration)\n\n    return '\\n'.join(lines)\n\n\nclass XMLFormatter(FormatterPlugin):\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.enabled = self.format_options['xml']['format']\n\n    def format_body(self, body: str, mime: str):\n        if 'xml' not in mime:\n            return body\n\n        from xml.parsers.expat import ExpatError\n        from defusedxml.common import DefusedXmlException\n\n        declaration = parse_declaration(body)\n        try:\n            parsed_body = parse_xml(body)\n        except ExpatError:\n            pass  # Invalid XML, ignore.\n        except DefusedXmlException:\n            pass  # Unsafe XML, ignore.\n        else:\n            body = pretty_xml(parsed_body,\n                              encoding=parsed_body.encoding,\n                              indent=self.format_options['xml']['indent'],\n                              declaration=declaration)\n\n        return body\n"
  },
  {
    "path": "httpie/output/lexers/__init__.py",
    "content": ""
  },
  {
    "path": "httpie/output/lexers/common.py",
    "content": "def precise(lexer, precise_token, parent_token):\n    # Due to a pygments bug*, custom tokens will look bad\n    # on outside styles. Until it is fixed on upstream, we'll\n    # convey whether the client is using pie style or not\n    # through precise option and return more precise tokens\n    # depending on it's value.\n    #\n    # [0]: https://github.com/pygments/pygments/issues/1986\n    if precise_token is None or not lexer.options.get(\"precise\"):\n        return parent_token\n    else:\n        return precise_token\n"
  },
  {
    "path": "httpie/output/lexers/http.py",
    "content": "import re\nimport pygments\nfrom httpie.output.lexers.common import precise\n\nRE_STATUS_LINE = re.compile(r'(\\d{3})( +)?(.+)?')\n\nSTATUS_TYPES = {\n    '1': pygments.token.Number.HTTP.INFO,\n    '2': pygments.token.Number.HTTP.OK,\n    '3': pygments.token.Number.HTTP.REDIRECT,\n    '4': pygments.token.Number.HTTP.CLIENT_ERR,\n    '5': pygments.token.Number.HTTP.SERVER_ERR,\n}\n\nRESPONSE_TYPES = {\n    'GET': pygments.token.Name.Function.HTTP.GET,\n    'HEAD': pygments.token.Name.Function.HTTP.HEAD,\n    'POST': pygments.token.Name.Function.HTTP.POST,\n    'PUT': pygments.token.Name.Function.HTTP.PUT,\n    'PATCH': pygments.token.Name.Function.HTTP.PATCH,\n    'DELETE': pygments.token.Name.Function.HTTP.DELETE,\n}\n\n\ndef http_response_type(lexer, match, ctx):\n    status_match = RE_STATUS_LINE.match(match.group())\n    if status_match is None:\n        return None\n\n    status_code, text, reason = status_match.groups()\n    status_type = precise(\n        lexer,\n        STATUS_TYPES.get(status_code[0]),\n        pygments.token.Number\n    )\n\n    groups = pygments.lexer.bygroups(\n        status_type,\n        pygments.token.Text,\n        status_type\n    )\n    yield from groups(lexer, status_match, ctx)\n\n\ndef request_method(lexer, match, ctx):\n    response_type = precise(\n        lexer,\n        RESPONSE_TYPES.get(match.group()),\n        pygments.token.Name.Function\n    )\n    yield match.start(), response_type, match.group()\n\n\nclass SimplifiedHTTPLexer(pygments.lexer.RegexLexer):\n    \"\"\"Simplified HTTP lexer for Pygments.\n\n    It only operates on headers and provides a stronger contrast between\n    their names and values than the original one bundled with Pygments\n    (:class:`pygments.lexers.text import HttpLexer`), especially when\n    Solarized color scheme is used.\n\n    \"\"\"\n    name = 'HTTP'\n    aliases = ['http']\n    filenames = ['*.http']\n    tokens = {\n        'root': [\n            # Request-Line\n            (r'([A-Z]+)( +)([^ ]+)( +)(HTTP)(/)(\\d+\\.\\d+)',\n             pygments.lexer.bygroups(\n                 request_method,\n                 pygments.token.Text,\n                 pygments.token.Name.Namespace,\n                 pygments.token.Text,\n                 pygments.token.Keyword.Reserved,\n                 pygments.token.Operator,\n                 pygments.token.Number\n             )),\n            # Response Status-Line\n            (r'(HTTP)(/)(\\d+\\.\\d+)( +)(.+)',\n             pygments.lexer.bygroups(\n                 pygments.token.Keyword.Reserved,  # 'HTTP'\n                 pygments.token.Operator,  # '/'\n                 pygments.token.Number,  # Version\n                 pygments.token.Text,\n                 http_response_type,  # Status code and Reason\n             )),\n            # Header\n            (r'(.*?)( *)(:)( *)(.+)', pygments.lexer.bygroups(\n                pygments.token.Name.Attribute,  # Name\n                pygments.token.Text,\n                pygments.token.Operator,  # Colon\n                pygments.token.Text,\n                pygments.token.String  # Value\n            ))\n        ]\n    }\n"
  },
  {
    "path": "httpie/output/lexers/json.py",
    "content": "import re\n\nfrom pygments.lexer import bygroups, using, RegexLexer\nfrom pygments.lexers.data import JsonLexer\nfrom pygments.token import Token\n\nPREFIX_TOKEN = Token.Error\nPREFIX_REGEX = r'[^{\\[\"]+'\n\n\nclass EnhancedJsonLexer(RegexLexer):\n    \"\"\"\n    Enhanced JSON lexer for Pygments.\n\n    It adds support for eventual data prefixing the actual JSON body.\n\n    \"\"\"\n    name = 'JSON'\n    flags = re.IGNORECASE | re.DOTALL\n    tokens = {\n        'root': [\n            # Eventual non-JSON data prefix followed by actual JSON body.\n            # FIX: data prefix + number (integer or float) is not correctly handled.\n            (\n                fr'({PREFIX_REGEX})' + r'((?:[{\\[\"]|true|false|null).+)',\n                bygroups(PREFIX_TOKEN, using(JsonLexer))\n            ),\n            # JSON body.\n            (r'.+', using(JsonLexer)),\n        ],\n    }\n"
  },
  {
    "path": "httpie/output/lexers/metadata.py",
    "content": "import pygments\n\nfrom httpie.models import ELAPSED_TIME_LABEL\nfrom httpie.output.lexers.common import precise\n\nSPEED_TOKENS = {\n    0.45: pygments.token.Number.SPEED.FAST,\n    1.00: pygments.token.Number.SPEED.AVG,\n    2.50: pygments.token.Number.SPEED.SLOW,\n}\n\n\ndef speed_based_token(lexer, match, ctx):\n    try:\n        value = float(match.group())\n    except ValueError:\n        return pygments.token.Number\n\n    for limit, token in SPEED_TOKENS.items():\n        if value <= limit:\n            break\n    else:\n        token = pygments.token.Number.SPEED.VERY_SLOW\n\n    response_type = precise(\n        lexer,\n        token,\n        pygments.token.Number\n    )\n    yield match.start(), response_type, match.group()\n\n\nclass MetadataLexer(pygments.lexer.RegexLexer):\n    \"\"\"Simple HTTPie metadata lexer.\"\"\"\n\n    tokens = {\n        'root': [\n            (\n                fr'({ELAPSED_TIME_LABEL})( *)(:)( *)(\\d+\\.\\d+)(s)', pygments.lexer.bygroups(\n                    pygments.token.Name.Decorator,  # Name\n                    pygments.token.Text,\n                    pygments.token.Operator,  # Colon\n                    pygments.token.Text,\n                    speed_based_token,\n                    pygments.token.Name.Builtin  # Value\n                )\n            ),\n            # Generic item\n            (\n                r'(.*?)( *)(:)( *)(.+)', pygments.lexer.bygroups(\n                    pygments.token.Name.Decorator,  # Name\n                    pygments.token.Text,\n                    pygments.token.Operator,  # Colon\n                    pygments.token.Text,\n                    pygments.token.Text  # Value\n                )\n            ),\n        ]\n    }\n"
  },
  {
    "path": "httpie/output/models.py",
    "content": "import argparse\nfrom typing import Any, Dict, Union, List, NamedTuple, Optional\n\nfrom httpie.context import Environment\nfrom httpie.cli.constants import PrettyOptions, PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY\nfrom httpie.cli.argtypes import PARSED_DEFAULT_FORMAT_OPTIONS\nfrom httpie.output.formatters.colors import AUTO_STYLE\n\n\nclass ProcessingOptions(NamedTuple):\n    \"\"\"Represents a set of stylistic options\n    that are used when deciding which stream\n    should be used.\"\"\"\n\n    debug: bool = False\n    traceback: bool = False\n\n    stream: bool = False\n    style: str = AUTO_STYLE\n    prettify: Union[List[str], PrettyOptions] = PRETTY_STDOUT_TTY_ONLY\n\n    response_mime: Optional[str] = None\n    response_charset: Optional[str] = None\n\n    json: bool = False\n    format_options: Dict[str, Any] = PARSED_DEFAULT_FORMAT_OPTIONS\n\n    def get_prettify(self, env: Environment) -> List[str]:\n        if self.prettify is PRETTY_STDOUT_TTY_ONLY:\n            return PRETTY_MAP['all' if env.stdout_isatty else 'none']\n        else:\n            return self.prettify\n\n    @classmethod\n    def from_raw_args(cls, options: argparse.Namespace) -> 'ProcessingOptions':\n        fetched_options = {\n            option: getattr(options, option)\n            for option in cls._fields\n        }\n        return cls(**fetched_options)\n\n    @property\n    def show_traceback(self):\n        return self.debug or self.traceback\n"
  },
  {
    "path": "httpie/output/processing.py",
    "content": "import re\nfrom typing import Optional, List\n\nfrom ..plugins import ConverterPlugin\nfrom ..plugins.registry import plugin_manager\nfrom ..context import Environment\n\n\nMIME_RE = re.compile(r'^[^/]+/[^/]+$')\n\n\ndef is_valid_mime(mime):\n    return mime and MIME_RE.match(mime)\n\n\nclass Conversion:\n\n    @staticmethod\n    def get_converter(mime: str) -> Optional[ConverterPlugin]:\n        if is_valid_mime(mime):\n            for converter_class in plugin_manager.get_converters():\n                if converter_class.supports(mime):\n                    return converter_class(mime)\n\n\nclass Formatting:\n    \"\"\"A delegate class that invokes the actual processors.\"\"\"\n\n    def __init__(self, groups: List[str], env=Environment(), **kwargs):\n        \"\"\"\n        :param groups: names of processor groups to be applied\n        :param env: Environment\n        :param kwargs: additional keyword arguments for processors\n\n        \"\"\"\n        available_plugins = plugin_manager.get_formatters_grouped()\n        self.enabled_plugins = []\n        for group in groups:\n            for cls in available_plugins[group]:\n                p = cls(env=env, **kwargs)\n                if p.enabled:\n                    self.enabled_plugins.append(p)\n\n    def format_headers(self, headers: str) -> str:\n        for p in self.enabled_plugins:\n            headers = p.format_headers(headers)\n        return headers\n\n    def format_body(self, content: str, mime: str) -> str:\n        if is_valid_mime(mime):\n            for p in self.enabled_plugins:\n                content = p.format_body(content, mime)\n        return content\n\n    def format_metadata(self, metadata: str) -> str:\n        for p in self.enabled_plugins:\n            metadata = p.format_metadata(metadata)\n        return metadata\n"
  },
  {
    "path": "httpie/output/streams.py",
    "content": "from abc import ABCMeta, abstractmethod\nfrom itertools import chain\nfrom typing import Callable, Iterable, Optional, Union\n\nfrom .processing import Conversion, Formatting\nfrom ..context import Environment\nfrom ..encoding import smart_decode, smart_encode, UTF8\nfrom ..models import HTTPMessage, OutputOptions\nfrom ..utils import parse_content_type_header\n\n\nBINARY_SUPPRESSED_NOTICE = (\n    b'\\n'\n    b'+-----------------------------------------+\\n'\n    b'| NOTE: binary data not shown in terminal |\\n'\n    b'+-----------------------------------------+'\n)\n\n\nclass DataSuppressedError(Exception):\n    message = None\n\n\nclass BinarySuppressedError(DataSuppressedError):\n    \"\"\"An error indicating that the body is binary and won't be written,\n     e.g., for terminal output).\"\"\"\n    message = BINARY_SUPPRESSED_NOTICE\n\n\nclass BaseStream(metaclass=ABCMeta):\n    \"\"\"Base HTTP message output stream class.\"\"\"\n\n    def __init__(\n        self,\n        msg: HTTPMessage,\n        output_options: OutputOptions,\n        on_body_chunk_downloaded: Callable[[bytes], None] = None,\n        **kwargs\n    ):\n        \"\"\"\n        :param msg: a :class:`models.HTTPMessage` subclass\n        :param output_options: a :class:`OutputOptions` instance to represent\n                               which parts of the message is printed.\n        \"\"\"\n        assert output_options.any()\n        self.msg = msg\n        self.output_options = output_options\n        self.on_body_chunk_downloaded = on_body_chunk_downloaded\n        self.extra_options = kwargs\n\n    def get_headers(self) -> bytes:\n        \"\"\"Return the headers' bytes.\"\"\"\n        return self.msg.headers.encode()\n\n    def get_metadata(self) -> bytes:\n        \"\"\"Return the message metadata.\"\"\"\n        return self.msg.metadata.encode()\n\n    @abstractmethod\n    def iter_body(self) -> Iterable[bytes]:\n        \"\"\"Return an iterator over the message body.\"\"\"\n\n    def __iter__(self) -> Iterable[bytes]:\n        \"\"\"Return an iterator over `self.msg`.\"\"\"\n        if self.output_options.headers:\n            yield self.get_headers()\n            yield b'\\r\\n\\r\\n'\n\n        if self.output_options.body:\n            try:\n                for chunk in self.iter_body():\n                    yield chunk\n                    if self.on_body_chunk_downloaded:\n                        self.on_body_chunk_downloaded(chunk)\n            except DataSuppressedError as e:\n                if self.output_options.headers:\n                    yield b'\\n'\n                yield e.message\n\n        if self.output_options.meta:\n            if self.output_options.body:\n                yield b'\\n\\n'\n\n            yield self.get_metadata()\n            yield b'\\n\\n'\n\n\nclass RawStream(BaseStream):\n    \"\"\"The message is streamed in chunks with no processing.\"\"\"\n\n    CHUNK_SIZE = 1024 * 100\n    CHUNK_SIZE_BY_LINE = 1\n\n    def __init__(self, chunk_size=CHUNK_SIZE, **kwargs):\n        super().__init__(**kwargs)\n        self.chunk_size = chunk_size\n\n    def iter_body(self) -> Iterable[bytes]:\n        return self.msg.iter_body(self.chunk_size)\n\n\nENCODING_GUESS_THRESHOLD = 3\n\n\nclass EncodedStream(BaseStream):\n    \"\"\"Encoded HTTP message stream.\n\n    The message bytes are converted to an encoding suitable for\n    `self.env.stdout`. Unicode errors are replaced and binary data\n    is suppressed. The body is always streamed by line.\n\n    \"\"\"\n    CHUNK_SIZE = 1\n\n    def __init__(\n        self,\n        env=Environment(),\n        mime_overwrite: str = None,\n        encoding_overwrite: str = None,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        if mime_overwrite:\n            self.mime = mime_overwrite\n        else:\n            self.mime, _ = parse_content_type_header(self.msg.content_type)\n        self._encoding = encoding_overwrite or self.msg.encoding\n        self._encoding_guesses = []\n        if env.stdout_isatty:\n            # Use the encoding supported by the terminal.\n            output_encoding = env.stdout_encoding\n        else:\n            # Preserve the message encoding.\n            output_encoding = self.msg.encoding\n        # Default to UTF-8 when unsure.\n        self.output_encoding = output_encoding or UTF8\n\n    def iter_body(self) -> Iterable[bytes]:\n        for line, lf in self.msg.iter_lines(self.CHUNK_SIZE):\n            if b'\\0' in line:\n                raise BinarySuppressedError()\n            line = self.decode_chunk(line)\n            yield smart_encode(line, self.output_encoding) + lf\n\n    def decode_chunk(self, raw_chunk: str) -> str:\n        chunk, guessed_encoding = smart_decode(raw_chunk, self.encoding)\n        self._encoding_guesses.append(guessed_encoding)\n        return chunk\n\n    @property\n    def encoding(self) -> Optional[str]:\n        if self._encoding:\n            return self._encoding\n\n        # If we find a reliable (used consecutively) encoding, than\n        # use it for the next iterations.\n        if len(self._encoding_guesses) < ENCODING_GUESS_THRESHOLD:\n            return None\n\n        guess_1, guess_2 = self._encoding_guesses[-2:]\n        if guess_1 == guess_2:\n            self._encoding = guess_1\n            return guess_1\n\n    @encoding.setter\n    def encoding(self, value) -> None:\n        self._encoding = value\n\n\nclass PrettyStream(EncodedStream):\n    \"\"\"In addition to :class:`EncodedStream` behaviour, this stream applies\n    content processing.\n\n    Useful for long-lived HTTP responses that stream by lines\n    such as the Twitter streaming API.\n\n    \"\"\"\n\n    CHUNK_SIZE = 1\n\n    def __init__(\n        self, conversion: Conversion,\n        formatting: Formatting,\n        **kwargs,\n    ):\n        super().__init__(**kwargs)\n        self.formatting = formatting\n        self.conversion = conversion\n\n    def get_headers(self) -> bytes:\n        return self.formatting.format_headers(\n            self.msg.headers).encode(self.output_encoding)\n\n    def get_metadata(self) -> bytes:\n        return self.formatting.format_metadata(\n            self.msg.metadata).encode(self.output_encoding)\n\n    def iter_body(self) -> Iterable[bytes]:\n        first_chunk = True\n        iter_lines = self.msg.iter_lines(self.CHUNK_SIZE)\n        for line, lf in iter_lines:\n            if b'\\0' in line:\n                if first_chunk:\n                    converter = self.conversion.get_converter(self.mime)\n                    if converter:\n                        body = bytearray()\n                        # noinspection PyAssignmentToLoopOrWithParameter\n                        for line, lf in chain([(line, lf)], iter_lines):\n                            body.extend(line)\n                            body.extend(lf)\n                        self.mime, body = converter.convert(body)\n                        assert isinstance(body, str)\n                        yield self.process_body(body)\n                        return\n                raise BinarySuppressedError()\n            yield self.process_body(line) + lf\n            first_chunk = False\n\n    def process_body(self, chunk: Union[str, bytes]) -> bytes:\n        if not isinstance(chunk, str):\n            # Text when a converter has been used,\n            # otherwise it will always be bytes.\n            chunk = self.decode_chunk(chunk)\n        chunk = self.formatting.format_body(content=chunk, mime=self.mime)\n        return smart_encode(chunk, self.output_encoding)\n\n\nclass BufferedPrettyStream(PrettyStream):\n    \"\"\"The same as :class:`PrettyStream` except that the body is fully\n    fetched before it's processed.\n\n    Suitable regular HTTP responses.\n\n    \"\"\"\n\n    CHUNK_SIZE = 1024 * 10\n\n    def iter_body(self) -> Iterable[bytes]:\n        # Read the whole body before prettifying it,\n        # but bail out immediately if the body is binary.\n        converter = None\n        body = bytearray()\n\n        for chunk in self.msg.iter_body(self.CHUNK_SIZE):\n            if not converter and b'\\0' in chunk:\n                converter = self.conversion.get_converter(self.mime)\n                if not converter:\n                    raise BinarySuppressedError()\n            body.extend(chunk)\n\n        if converter:\n            self.mime, body = converter.convert(body)\n\n        yield self.process_body(body)\n"
  },
  {
    "path": "httpie/output/ui/__init__.py",
    "content": ""
  },
  {
    "path": "httpie/output/ui/man_pages.py",
    "content": "\"\"\"Logic for checking and displaying man pages.\"\"\"\n\nimport subprocess\nimport os\nfrom httpie.context import Environment\n\n\nMAN_COMMAND = 'man'\nNO_MAN_PAGES = os.getenv('HTTPIE_NO_MAN_PAGES', False)\n\n# On some systems, HTTP(n) might exist, but we are only interested in HTTP(1).\n# For more information on man page sections: <https://unix.stackexchange.com/a/138643>\nMAN_PAGE_SECTION = '1'\n\n\ndef is_available(program: str) -> bool:\n    \"\"\"\n    Check whether `program`'s man pages are available on this system.\n\n    \"\"\"\n    if NO_MAN_PAGES or os.system == 'nt':\n        return False\n    try:\n        process = subprocess.run(\n            [MAN_COMMAND, MAN_PAGE_SECTION, program],\n            shell=False,\n            stdout=subprocess.DEVNULL,\n            stderr=subprocess.DEVNULL\n        )\n    except Exception:\n        # There might be some errors outside the process, e.g\n        # a permission error to execute something that is not an\n        # executable.\n        return False\n    else:\n        return process.returncode == 0\n\n\ndef display_for(env: Environment, program: str) -> None:\n    \"\"\"\n    Open the system man page for the given command (http/https/httpie).\n\n    \"\"\"\n    subprocess.run(\n        [MAN_COMMAND, MAN_PAGE_SECTION, program],\n        stdout=env.stdout,\n        stderr=env.stderr\n    )\n"
  },
  {
    "path": "httpie/output/ui/palette.py",
    "content": "from dataclasses import dataclass, field\nfrom enum import Enum, auto\nfrom typing import Optional, List\n\n\nPYGMENTS_BRIGHT_BLACK = 'ansibrightblack'\n\nAUTO_STYLE = 'auto'  # Follows terminal ANSI color styles\n\n\nclass Styles(Enum):\n    PIE = auto()\n    ANSI = auto()\n\n\nclass PieStyle(str, Enum):\n    UNIVERSAL = 'pie'\n    DARK = 'pie-dark'\n    LIGHT = 'pie-light'\n\n\nPIE_STYLE_TO_SHADE = {\n    PieStyle.DARK: '500',\n    PieStyle.UNIVERSAL: '600',\n    PieStyle.LIGHT: '700',\n}\nSHADE_TO_PIE_STYLE = {\n    shade: style for style, shade in PIE_STYLE_TO_SHADE.items()\n}\n\n\nclass ColorString(str):\n    def __or__(self, other: str) -> 'ColorString':\n        \"\"\"Combine a style with a property.\n\n        E.g: PieColor.BLUE | BOLD | ITALIC\n        \"\"\"\n        if isinstance(other, str):\n            # In case of PieColor.BLUE | SOMETHING\n            # we just create a new string.\n            return ColorString(self + ' ' + other)\n        elif isinstance(other, GenericColor):\n            # If we see a GenericColor, then we'll wrap it\n            # in with the desired property in a different class.\n            return _StyledGenericColor(other, styles=self.split())\n        elif isinstance(other, _StyledGenericColor):\n            # And if it is already wrapped, we'll just extend the\n            # list of properties.\n            other.styles.extend(self.split())\n            return other\n        else:\n            return NotImplemented\n\n\nclass PieColor(ColorString, Enum):\n    \"\"\"Styles that are available only in Pie themes.\"\"\"\n\n    PRIMARY = 'primary'\n    SECONDARY = 'secondary'\n\n    WHITE = 'white'\n    BLACK = 'black'\n    GREY = 'grey'\n    AQUA = 'aqua'\n    PURPLE = 'purple'\n    ORANGE = 'orange'\n    RED = 'red'\n    BLUE = 'blue'\n    PINK = 'pink'\n    GREEN = 'green'\n    YELLOW = 'yellow'\n\n\nclass GenericColor(Enum):\n    \"\"\"Generic colors that are safe to use everywhere.\"\"\"\n\n    # <https://rich.readthedocs.io/en/stable/appendix/colors.html>\n\n    WHITE = {Styles.PIE: PieColor.WHITE, Styles.ANSI: 'white'}\n    BLACK = {Styles.PIE: PieColor.BLACK, Styles.ANSI: 'black'}\n    GREEN = {Styles.PIE: PieColor.GREEN, Styles.ANSI: 'green'}\n    ORANGE = {Styles.PIE: PieColor.ORANGE, Styles.ANSI: 'yellow'}\n    YELLOW = {Styles.PIE: PieColor.YELLOW, Styles.ANSI: 'bright_yellow'}\n    BLUE = {Styles.PIE: PieColor.BLUE, Styles.ANSI: 'blue'}\n    PINK = {Styles.PIE: PieColor.PINK, Styles.ANSI: 'bright_magenta'}\n    PURPLE = {Styles.PIE: PieColor.PURPLE, Styles.ANSI: 'magenta'}\n    RED = {Styles.PIE: PieColor.RED, Styles.ANSI: 'red'}\n    AQUA = {Styles.PIE: PieColor.AQUA, Styles.ANSI: 'cyan'}\n    GREY = {Styles.PIE: PieColor.GREY, Styles.ANSI: 'bright_black'}\n\n    def apply_style(\n        self, style: Styles, *, style_name: Optional[str] = None\n    ) -> str:\n        \"\"\"Apply the given style to a particular value.\"\"\"\n        exposed_color = self.value[style]\n        if style is Styles.PIE:\n            assert style_name is not None\n            shade = PIE_STYLE_TO_SHADE[PieStyle(style_name)]\n            return get_color(exposed_color, shade)\n        else:\n            return exposed_color\n\n\n@dataclass\nclass _StyledGenericColor:\n    color: 'GenericColor'\n    styles: List[str] = field(default_factory=list)\n\n\n# noinspection PyDictCreation\nCOLOR_PALETTE = {\n    # Copy the brand palette\n    PieColor.WHITE: '#F5F5F0',\n    PieColor.BLACK: '#1C1818',\n    PieColor.GREY: {\n        '50': '#F5F5F0',\n        '100': '#EDEDEB',\n        '200': '#D1D1CF',\n        '300': '#B5B5B2',\n        '400': '#999999',\n        '500': '#7D7D7D',\n        '600': '#666663',\n        '700': '#4F4D4D',\n        '800': '#363636',\n        '900': '#1C1818',\n        'DEFAULT': '#7D7D7D',\n    },\n    PieColor.AQUA: {\n        '50': '#E8F0F5',\n        '100': '#D6E3ED',\n        '200': '#C4D9E5',\n        '300': '#B0CCDE',\n        '400': '#9EBFD6',\n        '500': '#8CB4CD',\n        '600': '#7A9EB5',\n        '700': '#698799',\n        '800': '#597082',\n        '900': '#455966',\n        'DEFAULT': '#8CB4CD',\n    },\n    PieColor.PURPLE: {\n        '50': '#F0E0FC',\n        '100': '#E3C7FA',\n        '200': '#D9ADF7',\n        '300': '#CC96F5',\n        '400': '#BF7DF2',\n        '500': '#B464F0',\n        '600': '#9E54D6',\n        '700': '#8745BA',\n        '800': '#70389E',\n        '900': '#5C2982',\n        'DEFAULT': '#B464F0',\n    },\n    PieColor.ORANGE: {\n        '50': '#FFEDDB',\n        '100': '#FFDEBF',\n        '200': '#FFCFA3',\n        '300': '#FFBF87',\n        '400': '#FFB06B',\n        '500': '#FFA24E',\n        '600': '#F2913D',\n        '700': '#E3822B',\n        '800': '#D6701C',\n        '900': '#C75E0A',\n        'DEFAULT': '#FFA24E',\n    },\n    PieColor.RED: {\n        '50': '#FFE0DE',\n        '100': '#FFC7C4',\n        '200': '#FFB0AB',\n        '300': '#FF968F',\n        '400': '#FF8075',\n        '500': '#FF665B',\n        '600': '#E34F45',\n        '700': '#C7382E',\n        '800': '#AD2117',\n        '900': '#910A00',\n        'DEFAULT': '#FF665B',\n    },\n    PieColor.BLUE: {\n        '50': '#DBE3FA',\n        '100': '#BFCFF5',\n        '200': '#A1B8F2',\n        '300': '#85A3ED',\n        '400': '#698FEB',\n        '500': '#4B78E6',\n        '600': '#426BD1',\n        '700': '#3B5EBA',\n        '800': '#3354A6',\n        '900': '#2B478F',\n        'DEFAULT': '#4B78E6',\n    },\n    PieColor.PINK: {\n        '50': '#FFEBFF',\n        '100': '#FCDBFC',\n        '200': '#FCCCFC',\n        '300': '#FCBAFC',\n        '400': '#FAABFA',\n        '500': '#FA9BFA',\n        '600': '#DE85DE',\n        '700': '#C26EC2',\n        '800': '#A854A6',\n        '900': '#8C3D8A',\n        'DEFAULT': '#FA9BFA',\n    },\n    PieColor.GREEN: {\n        '50': '#E3F7E8',\n        '100': '#CCF2D6',\n        '200': '#B5EDC4',\n        '300': '#A1E8B0',\n        '400': '#8AE09E',\n        '500': '#73DC8C',\n        '600': '#63C27A',\n        '700': '#52AB66',\n        '800': '#429154',\n        '900': '#307842',\n        'DEFAULT': '#73DC8C',\n    },\n    PieColor.YELLOW: {\n        '50': '#F7F7DB',\n        '100': '#F2F2BF',\n        '200': '#EDEDA6',\n        '300': '#E5E88A',\n        '400': '#E0E36E',\n        '500': '#DBDE52',\n        '600': '#CCCC3D',\n        '700': '#BABA29',\n        '800': '#ABA614',\n        '900': '#999400',\n        'DEFAULT': '#DBDE52',\n    },\n}\nCOLOR_PALETTE.update(\n    {\n        # Terminal-specific palette customizations.\n        PieColor.GREY: {\n            # Grey is the same no matter shade for the colors\n            shade: COLOR_PALETTE[PieColor.GREY]['500']\n            for shade in COLOR_PALETTE[PieColor.GREY].keys()\n        },\n        PieColor.PRIMARY: {\n            '700': COLOR_PALETTE[PieColor.BLACK],\n            '600': PYGMENTS_BRIGHT_BLACK,\n            '500': COLOR_PALETTE[PieColor.WHITE],\n        },\n        PieColor.SECONDARY: {\n            '700': '#37523C',\n            '600': '#6c6969',\n            '500': '#6c6969',\n        },\n    }\n)\n\n\ndef boldify(color: PieColor) -> str:\n    return f'bold {color}'\n\n\n# noinspection PyDefaultArgument\ndef get_color(\n    color: PieColor, shade: str, *, palette=COLOR_PALETTE\n) -> Optional[str]:\n    if color not in palette:\n        return None\n    color_code = palette[color]\n    if isinstance(color_code, dict) and shade in color_code:\n        return color_code[shade]\n    else:\n        return color_code\n"
  },
  {
    "path": "httpie/output/ui/rich_help.py",
    "content": "import re\nimport textwrap\nfrom typing import AbstractSet, Iterable, Optional, Tuple\n\nfrom rich.console import RenderableType\nfrom rich.highlighter import RegexHighlighter\nfrom rich.padding import Padding\nfrom rich.table import Table\nfrom rich.text import Text\n\nfrom httpie.cli.constants import SEPARATOR_GROUP_ALL_ITEMS\nfrom httpie.cli.options import Argument, ParserSpec, Qualifiers\nfrom httpie.output.ui.palette import GenericColor\n\nSEPARATORS = '|'.join(map(re.escape, SEPARATOR_GROUP_ALL_ITEMS))\n\nSTYLE_METAVAR = GenericColor.YELLOW\nSTYLE_SWITCH = GenericColor.GREEN\nSTYLE_PROGRAM_NAME = GenericColor.GREEN  # .boldify()\nSTYLE_USAGE_OPTIONAL = GenericColor.GREY\nSTYLE_USAGE_REGULAR = GenericColor.WHITE\nSTYLE_USAGE_ERROR = GenericColor.RED\nSTYLE_USAGE_MISSING = GenericColor.YELLOW\nSTYLE_BOLD = 'bold'\n\nMAX_CHOICE_CHARS = 80\n\nLEFT_PADDING_2 = (0, 0, 0, 2)\nLEFT_PADDING_3 = (0, 0, 0, 3)\nLEFT_PADDING_4 = (0, 0, 0, 4)\nLEFT_PADDING_5 = (0, 0, 0, 4)\n\nLEFT_INDENT_2 = (1, 0, 0, 2)\nLEFT_INDENT_3 = (1, 0, 0, 3)\nLEFT_INDENT_BOTTOM_3 = (0, 0, 1, 3)\n\nMORE_INFO_COMMANDS = \"\"\"\nTo learn more, you can try:\n    -> running 'http --manual'\n    -> visiting our full documentation at https://httpie.io/docs/cli\n\"\"\"\n\n\nclass OptionsHighlighter(RegexHighlighter):\n    highlights = [\n        r'(^|\\W)(?P<option>\\-{1,2}[\\w|-]+)(?![a-zA-Z0-9])',\n        r'(?P<bold>HTTPie)',\n    ]\n\n\noptions_highlighter = OptionsHighlighter()\n\n\ndef unpack_argument(\n    argument: Argument,\n) -> Tuple[Text, Text]:\n    opt1 = opt2 = ''\n\n    style = None\n    if argument.aliases:\n        if len(argument.aliases) >= 2:\n            opt2, opt1 = argument.aliases\n        else:\n            (opt1,) = argument.aliases\n    else:\n        opt1 = argument.metavar\n        style = STYLE_USAGE_REGULAR\n\n    return Text(opt1, style=style), Text(opt2)\n\n\ndef to_usage(\n    spec: ParserSpec,\n    *,\n    program_name: Optional[str] = None,\n    whitelist: AbstractSet[str] = frozenset()\n) -> RenderableType:\n    shown_arguments = [\n        argument\n        for group in spec.groups\n        for argument in group.arguments\n        if (not argument.aliases or whitelist.intersection(argument.aliases))\n    ]\n\n    # Sort the shown_arguments so that --dash options are\n    # shown first\n    shown_arguments.sort(key=lambda argument: argument.aliases, reverse=True)\n\n    text = Text(program_name or spec.program, style=STYLE_BOLD)\n    for argument in shown_arguments:\n        text.append(' ')\n\n        is_whitelisted = whitelist.intersection(argument.aliases)\n        if argument.aliases:\n            name = '/'.join(sorted(argument.aliases, key=len))\n        else:\n            name = argument.metavar\n\n        nargs = argument.configuration.get('nargs')\n        if nargs is Qualifiers.OPTIONAL:\n            text.append('[' + name + ']', style=STYLE_USAGE_OPTIONAL)\n        elif nargs is Qualifiers.ZERO_OR_MORE:\n            text.append(\n                '[' + name + ' ...]',\n                style=STYLE_USAGE_OPTIONAL,\n            )\n        else:\n            text.append(\n                name,\n                style=STYLE_USAGE_ERROR\n                if is_whitelisted\n                else STYLE_USAGE_REGULAR,\n            )\n\n        raw_form = argument.serialize()\n        if raw_form.get('choices'):\n            text.append(' ')\n            text.append(\n                '{' + ', '.join(raw_form['choices']) + '}',\n                style=STYLE_USAGE_MISSING,\n            )\n\n    return text\n\n\n# This part is loosely based on the rich-click's help message\n# generation.\ndef to_help_message(\n    spec: ParserSpec,\n) -> Iterable[RenderableType]:\n    yield Padding(\n        options_highlighter(spec.description),\n        LEFT_INDENT_2,\n    )\n\n    yield Padding(\n        Text('Usage', style=STYLE_SWITCH),\n        LEFT_INDENT_2,\n    )\n    yield Padding(to_usage(spec), LEFT_INDENT_3)\n\n    group_rows = {}\n    for group in spec.groups:\n        options_rows = []\n\n        for argument in group.arguments:\n            if argument.is_hidden:\n                continue\n\n            opt1, opt2 = unpack_argument(argument)\n            if opt2:\n                opt1.append('/')\n                opt1.append(opt2)\n\n            # Column for a metavar, if we have one\n            metavar = Text(style=STYLE_METAVAR)\n            metavar.append(argument.configuration.get('metavar', ''))\n\n            if opt1 == metavar:\n                metavar = Text('')\n\n            raw_form = argument.serialize()\n            desc = raw_form.get('short_description', '')\n            if raw_form.get('choices'):\n                desc += ' (choices: '\n                desc += textwrap.shorten(\n                    ', '.join(raw_form.get('choices')),\n                    MAX_CHOICE_CHARS,\n                )\n                desc += ')'\n\n            rows = [\n                Padding(\n                    options_highlighter(opt1),\n                    LEFT_PADDING_2,\n                ),\n                metavar,\n                options_highlighter(desc),\n            ]\n\n            options_rows.append(rows)\n            if argument.configuration.get('nested_options'):\n                options_rows.extend(\n                    [\n                        (\n                            Padding(\n                                Text(\n                                    key,\n                                    style=STYLE_USAGE_OPTIONAL,\n                                ),\n                                LEFT_PADDING_4,\n                            ),\n                            value,\n                            dec,\n                        )\n                        for key, value, dec in argument.nested_options\n                    ]\n                )\n\n        group_rows[group.name] = options_rows\n\n    options_table = Table(highlight=False, box=None, show_header=False)\n    for group_name, options_rows in group_rows.items():\n        options_table.add_row(Text(), Text(), Text())\n        options_table.add_row(\n            Text(group_name, style=STYLE_SWITCH),\n            Text(),\n            Text(),\n        )\n        options_table.add_row(Text(), Text(), Text())\n        for row in options_rows:\n            options_table.add_row(*row)\n\n    yield Padding(\n        Text('Options', style=STYLE_SWITCH),\n        LEFT_INDENT_2,\n    )\n    yield Padding(options_table, LEFT_PADDING_2)\n    yield Padding(\n        Text('More Information', style=STYLE_SWITCH),\n        LEFT_INDENT_2,\n    )\n    yield Padding(\n        MORE_INFO_COMMANDS.rstrip('\\n'),\n        LEFT_PADDING_3\n    )\n    yield Padding(\n        spec.epilog.rstrip('\\n'),\n        LEFT_INDENT_BOTTOM_3,\n    )\n"
  },
  {
    "path": "httpie/output/ui/rich_palette.py",
    "content": "from collections import ChainMap\nfrom typing import TYPE_CHECKING, Any, Optional\n\nif TYPE_CHECKING:\n    from rich.theme import Theme\n\nfrom httpie.output.ui.palette import GenericColor, PieStyle, Styles, ColorString, _StyledGenericColor  # noqa\n\nRICH_BOLD = ColorString('bold')\n\n# Rich-specific color code declarations\n# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py>\nCUSTOM_STYLES = {\n    'progress.description': RICH_BOLD | GenericColor.WHITE,\n    'progress.data.speed': RICH_BOLD | GenericColor.GREEN,\n    'progress.percentage': RICH_BOLD | GenericColor.AQUA,\n    'progress.download': RICH_BOLD | GenericColor.AQUA,\n    'progress.remaining': RICH_BOLD | GenericColor.ORANGE,\n    'bar.complete': RICH_BOLD | GenericColor.PURPLE,\n    'bar.finished': RICH_BOLD | GenericColor.GREEN,\n    'bar.pulse': RICH_BOLD | GenericColor.PURPLE,\n    'option': RICH_BOLD | GenericColor.PINK,\n}\n\n\nclass _GenericColorCaster(dict):\n    \"\"\"\n    Translate GenericColor to a regular string on the attribute access\n    phase.\n    \"\"\"\n\n    def _translate(self, key: Any) -> Any:\n        if isinstance(key, GenericColor):\n            return key.name.lower()\n        else:\n            return key\n\n    def __getitem__(self, key: Any) -> Any:\n        return super().__getitem__(self._translate(key))\n\n    def get(self, key: Any) -> Any:\n        return super().get(self._translate(key))\n\n\ndef _make_rich_color_theme(style_name: Optional[str] = None) -> 'Theme':\n    from rich.style import Style\n    from rich.theme import Theme\n\n    try:\n        PieStyle(style_name)\n    except ValueError:\n        style = Styles.ANSI\n    else:\n        style = Styles.PIE\n\n    theme = Theme()\n    for color, color_set in ChainMap(\n        GenericColor.__members__, CUSTOM_STYLES\n    ).items():\n        if isinstance(color_set, _StyledGenericColor):\n            properties = dict.fromkeys(color_set.styles, True)\n            color_set = color_set.color\n        else:\n            properties = {}\n\n        theme.styles[color.lower()] = Style(\n            color=color_set.apply_style(style, style_name=style_name),\n            **properties,\n        )\n\n    # E.g translate GenericColor.BLUE into blue on key access\n    theme.styles = _GenericColorCaster(theme.styles)\n    return theme\n"
  },
  {
    "path": "httpie/output/ui/rich_progress.py",
    "content": "from dataclasses import dataclass\nfrom typing import TYPE_CHECKING, Optional\n\nfrom httpie.context import Environment\n\nif TYPE_CHECKING:\n    from rich.console import Console\n\n\n@dataclass\nclass BaseDisplay:\n    env: Environment\n\n    def start(\n        self, *, total: Optional[float], at: float, description: str\n    ) -> None:\n        ...\n\n    def update(self, steps: float) -> None:\n        ...\n\n    def stop(self, time_spent: float) -> None:\n        ...\n\n    @property\n    def console(self) -> 'Console':\n        \"\"\"Returns the default console to be used with displays (stderr).\"\"\"\n        return self.env.rich_error_console\n\n    def _print_summary(\n        self, is_finished: bool, observed_steps: int, time_spent: float\n    ):\n        from rich import filesize\n\n        if is_finished:\n            verb = 'Done'\n        else:\n            verb = 'Interrupted'\n\n        total_size = filesize.decimal(observed_steps)\n        avg_speed = filesize.decimal(observed_steps / time_spent)\n\n        minutes, seconds = divmod(time_spent, 60)\n        hours, minutes = divmod(int(minutes), 60)\n        if hours:\n            total_time = f'{hours:d}:{minutes:02d}:{seconds:0.5f}'\n        else:\n            total_time = f'{minutes:02d}:{seconds:0.5f}'\n\n        self.console.print(\n            f'[progress.description]{verb}. {total_size} in {total_time} ({avg_speed}/s)'\n        )\n\n\nclass DummyDisplay(BaseDisplay):\n    \"\"\"\n    A dummy display object to be used when the progress bars,\n    spinners etc. are disabled globally (or during tests).\n    \"\"\"\n\n\nclass StatusDisplay(BaseDisplay):\n    def start(\n        self, *, total: Optional[float], at: float, description: str\n    ) -> None:\n        self.observed = at\n        self.description = (\n            f'[progress.description]{description}[/progress.description]'\n        )\n\n        self.status = self.console.status(self.description, spinner='line')\n        self.status.start()\n\n    def update(self, steps: float) -> None:\n        from rich import filesize\n\n        self.observed += steps\n\n        observed_amount, observed_unit = filesize.decimal(\n            self.observed\n        ).split()\n        self.status.update(\n            status=f'{self.description} [progress.download]{observed_amount}/? {observed_unit}[/progress.download]'\n        )\n\n    def stop(self, time_spent: float) -> None:\n        self.status.stop()\n        self.console.print(self.description)\n        if time_spent:\n            self._print_summary(\n                is_finished=True,\n                observed_steps=self.observed,\n                time_spent=time_spent,\n            )\n\n\nclass ProgressDisplay(BaseDisplay):\n    def start(\n        self, *, total: Optional[float], at: float, description: str\n    ) -> None:\n        from rich.progress import (\n            Progress,\n            BarColumn,\n            DownloadColumn,\n            TimeRemainingColumn,\n            TransferSpeedColumn,\n        )\n\n        assert total is not None\n        self.console.print(f'[progress.description]{description}')\n        self.progress_bar = Progress(\n            '[',\n            BarColumn(),\n            ']',\n            '[progress.percentage]{task.percentage:>3.0f}%',\n            '(',\n            DownloadColumn(),\n            ')',\n            TimeRemainingColumn(),\n            TransferSpeedColumn(),\n            console=self.console,\n            transient=True,\n        )\n        self.progress_bar.start()\n        self.transfer_task = self.progress_bar.add_task(\n            description, completed=at, total=total\n        )\n\n    def update(self, steps: float) -> None:\n        self.progress_bar.advance(self.transfer_task, steps)\n\n    def stop(self, time_spent: Optional[float]) -> None:\n        self.progress_bar.stop()\n\n        if time_spent:\n            [task] = self.progress_bar.tasks\n            self._print_summary(\n                is_finished=task.finished,\n                observed_steps=task.completed,\n                time_spent=time_spent,\n            )\n"
  },
  {
    "path": "httpie/output/ui/rich_utils.py",
    "content": "import os\n\nfrom typing import Iterator\nfrom contextlib import contextmanager\n\nfrom rich.console import Console, RenderableType\nfrom rich.highlighter import Highlighter\n\nfrom httpie.output.ui.rich_palette import _make_rich_color_theme\n\n\ndef render_as_string(renderable: RenderableType) -> str:\n    \"\"\"Render any `rich` object in a fake console and\n    return a *style-less* version of it as a string.\"\"\"\n\n    with open(os.devnull, 'w') as null_stream:\n        fake_console = Console(file=null_stream, record=True, theme=_make_rich_color_theme())\n        fake_console.print(renderable)\n        return fake_console.export_text()\n\n\n@contextmanager\ndef enable_highlighter(\n    console: Console,\n    highlighter: Highlighter,\n) -> Iterator[Console]:\n    \"\"\"Enable a highlighter temporarily.\"\"\"\n\n    original_highlighter = console.highlighter\n    try:\n        console.highlighter = highlighter\n        yield console\n    finally:\n        console.highlighter = original_highlighter\n"
  },
  {
    "path": "httpie/output/utils.py",
    "content": "import json\nimport re\nfrom typing import Tuple\n\nfrom ..utils import load_json_preserve_order_and_dupe_keys\nfrom .lexers.json import PREFIX_REGEX\n\n\ndef load_prefixed_json(data: str) -> Tuple[str, json.JSONDecoder]:\n    \"\"\"Simple JSON loading from `data`.\n\n    \"\"\"\n    # First, the full data.\n    try:\n        return '', load_json_preserve_order_and_dupe_keys(data)\n    except ValueError:\n        pass\n\n    # Then, try to find the start of the actual body.\n    data_prefix, body = parse_prefixed_json(data)\n    try:\n        return data_prefix, load_json_preserve_order_and_dupe_keys(body)\n    except ValueError:\n        raise ValueError('Invalid JSON')\n\n\ndef parse_prefixed_json(data: str) -> Tuple[str, str]:\n    \"\"\"Find the potential JSON body from `data`.\n\n    Sometimes the JSON body is prefixed with a XSSI magic string, specific to the server.\n    Return a tuple (data prefix, actual JSON body).\n\n    \"\"\"\n    matches = re.findall(PREFIX_REGEX, data)\n    data_prefix = matches[0] if matches else ''\n    body = data[len(data_prefix):]\n    return data_prefix, body\n"
  },
  {
    "path": "httpie/output/writer.py",
    "content": "import errno\nimport requests\nfrom typing import Any, Dict, IO, Optional, TextIO, Tuple, Type, Union\n\nfrom ..cli.dicts import HTTPHeadersDict\nfrom ..context import Environment\nfrom ..models import (\n    HTTPRequest,\n    HTTPResponse,\n    HTTPMessage,\n    RequestsMessage,\n    RequestsMessageKind,\n    OutputOptions,\n)\nfrom .models import ProcessingOptions\nfrom .processing import Conversion, Formatting\nfrom .streams import (\n    BaseStream, BufferedPrettyStream, EncodedStream, PrettyStream, RawStream,\n)\nfrom ..utils import parse_content_type_header\n\n\nMESSAGE_SEPARATOR = '\\n\\n'\nMESSAGE_SEPARATOR_BYTES = MESSAGE_SEPARATOR.encode()\n\n\ndef write_message(\n    requests_message: RequestsMessage,\n    env: Environment,\n    output_options: OutputOptions,\n    processing_options: ProcessingOptions,\n    extra_stream_kwargs: Optional[Dict[str, Any]] = None\n):\n    if not output_options.any():\n        return\n    write_stream_kwargs = {\n        'stream': build_output_stream_for_message(\n            env=env,\n            requests_message=requests_message,\n            output_options=output_options,\n            processing_options=processing_options,\n            extra_stream_kwargs=extra_stream_kwargs\n        ),\n        # NOTE: `env.stdout` will in fact be `stderr` with `--download`\n        'outfile': env.stdout,\n        'flush': env.stdout_isatty or processing_options.stream\n    }\n    try:\n        if env.is_windows and 'colors' in processing_options.get_prettify(env):\n            write_stream_with_colors_win(**write_stream_kwargs)\n        else:\n            write_stream(**write_stream_kwargs)\n    except OSError as e:\n        if processing_options.show_traceback and e.errno == errno.EPIPE:\n            # Ignore broken pipes unless --traceback.\n            env.stderr.write('\\n')\n        else:\n            raise\n\n\ndef write_stream(\n    stream: BaseStream,\n    outfile: Union[IO, TextIO],\n    flush: bool\n):\n    \"\"\"Write the output stream.\"\"\"\n    try:\n        # Writing bytes so we use the buffer interface.\n        buf = outfile.buffer\n    except AttributeError:\n        buf = outfile\n\n    for chunk in stream:\n        buf.write(chunk)\n        if flush:\n            outfile.flush()\n\n\ndef write_stream_with_colors_win(\n    stream: 'BaseStream',\n    outfile: TextIO,\n    flush: bool\n):\n    \"\"\"Like `write`, but colorized chunks are written as text\n    directly to `outfile` to ensure it gets processed by colorama.\n    Applies only to Windows and colorized terminal output.\n\n    \"\"\"\n    color = b'\\x1b['\n    encoding = outfile.encoding\n    for chunk in stream:\n        if color in chunk:\n            outfile.write(chunk.decode(encoding))\n        else:\n            outfile.buffer.write(chunk)\n        if flush:\n            outfile.flush()\n\n\ndef write_raw_data(\n    env: Environment,\n    data: Any,\n    *,\n    processing_options: Optional[ProcessingOptions] = None,\n    headers: Optional[HTTPHeadersDict] = None,\n    stream_kwargs: Optional[Dict[str, Any]] = None\n):\n    msg = requests.PreparedRequest()\n    msg.is_body_upload_chunk = True\n    msg.body = data\n    msg.headers = headers or HTTPHeadersDict()\n    msg_output_options = OutputOptions.from_message(msg, body=True, headers=False)\n    return write_message(\n        requests_message=msg,\n        env=env,\n        output_options=msg_output_options,\n        processing_options=processing_options or ProcessingOptions(),\n        extra_stream_kwargs=stream_kwargs\n    )\n\n\ndef build_output_stream_for_message(\n    env: Environment,\n    requests_message: RequestsMessage,\n    output_options: OutputOptions,\n    processing_options: ProcessingOptions,\n    extra_stream_kwargs: Optional[Dict[str, Any]] = None\n):\n    message_type = {\n        RequestsMessageKind.REQUEST: HTTPRequest,\n        RequestsMessageKind.RESPONSE: HTTPResponse,\n    }[output_options.kind]\n    stream_class, stream_kwargs = get_stream_type_and_kwargs(\n        env=env,\n        processing_options=processing_options,\n        message_type=message_type,\n        headers=requests_message.headers\n    )\n    if extra_stream_kwargs:\n        stream_kwargs.update(extra_stream_kwargs)\n    yield from stream_class(\n        msg=message_type(requests_message),\n        output_options=output_options,\n        **stream_kwargs,\n    )\n    if (env.stdout_isatty and output_options.body and not output_options.meta\n            and not getattr(requests_message, 'is_body_upload_chunk', False)):\n        # Ensure a blank line after the response body.\n        # For terminal output only.\n        yield MESSAGE_SEPARATOR_BYTES\n\n\ndef get_stream_type_and_kwargs(\n    env: Environment,\n    processing_options: ProcessingOptions,\n    message_type: Type[HTTPMessage],\n    headers: HTTPHeadersDict,\n) -> Tuple[Type['BaseStream'], dict]:\n    \"\"\"Pick the right stream type and kwargs for it based on `env` and `args`.\n\n    \"\"\"\n    is_stream = processing_options.stream\n    prettify_groups = processing_options.get_prettify(env)\n    if not is_stream and message_type is HTTPResponse:\n        # If this is a response, then check the headers for determining\n        # auto-streaming.\n        raw_content_type_header = headers.get('Content-Type', None)\n        if raw_content_type_header:\n            content_type_header, _ = parse_content_type_header(raw_content_type_header)\n            is_stream = (content_type_header == 'text/event-stream')\n\n    if not env.stdout_isatty and not prettify_groups:\n        stream_class = RawStream\n        stream_kwargs = {\n            'chunk_size': (\n                RawStream.CHUNK_SIZE_BY_LINE\n                if is_stream\n                else RawStream.CHUNK_SIZE\n            )\n        }\n    else:\n        stream_class = EncodedStream\n        stream_kwargs = {\n            'env': env,\n        }\n        if message_type is HTTPResponse:\n            stream_kwargs.update({\n                'mime_overwrite': processing_options.response_mime,\n                'encoding_overwrite': processing_options.response_charset,\n            })\n        if prettify_groups:\n            stream_class = PrettyStream if is_stream else BufferedPrettyStream\n            stream_kwargs.update({\n                'conversion': Conversion(),\n                'formatting': Formatting(\n                    env=env,\n                    groups=prettify_groups,\n                    color_scheme=processing_options.style,\n                    explicit_json=processing_options.json,\n                    format_options=processing_options.format_options,\n                )\n            })\n\n    return stream_class, stream_kwargs\n"
  },
  {
    "path": "httpie/plugins/__init__.py",
    "content": "\"\"\"\nWARNING: The plugin API is still work in progress and will\n         probably be completely reworked in the future.\n\n\"\"\"\nfrom .base import (\n    AuthPlugin, FormatterPlugin,\n    ConverterPlugin, TransportPlugin\n)\n\n__all__ = ('AuthPlugin', 'ConverterPlugin', 'FormatterPlugin', 'TransportPlugin')\n"
  },
  {
    "path": "httpie/plugins/base.py",
    "content": "from typing import Tuple\n\n\nclass BasePlugin:\n    # The name of the plugin, eg. \"My auth\".\n    name = None\n\n    # Optional short description. It will be shown in the help\n    # under --auth-type.\n    description = None\n\n    # This be set automatically once the plugin has been loaded.\n    package_name = None\n\n\nclass AuthPlugin(BasePlugin):\n    \"\"\"\n    Base auth plugin class.\n\n    See httpie-ntlm for an example auth plugin:\n\n        <https://github.com/httpie/httpie-ntlm>\n\n    See also `test_auth_plugins.py`\n\n    \"\"\"\n    # The value that should be passed to --auth-type\n    # to use this auth plugin. Eg. \"my-auth\"\n    auth_type = None\n\n    # Set to `False` to make it possible to invoke this auth\n    # plugin without requiring the user to specify credentials\n    # through `--auth, -a`.\n    auth_require = True\n\n    # By default the `-a` argument is parsed for `username:password`.\n    # Set this to `False` to disable the parsing and error handling.\n    auth_parse = True\n\n    # Set to `True` to make it possible for this auth\n    # plugin to acquire credentials from the user’s netrc file(s).\n    # It is used as a fallback when the credentials are not provided explicitly\n    # through `--auth, -a`. Enabling this will allow skipping `--auth, -a`\n    # even when `auth_require` is set `True` (provided that netrc provides\n    # credential for a given host).\n    netrc_parse = False\n\n    # If both `auth_parse` and `prompt_password` are set to `True`,\n    # and the value of `-a` lacks the password part,\n    # then the user will be prompted to type the password in.\n    prompt_password = True\n\n    # Will be set to the raw value of `-a` (if provided) before\n    # `get_auth()` gets called. If the credentials came from a netrc file,\n    # then this is `None`.\n    raw_auth = None\n\n    def get_auth(self, username: str = None, password: str = None):\n        \"\"\"\n        If `auth_parse` is set to `True`, then `username`\n        and `password` contain the parsed credentials.\n\n        Use `self.raw_auth` to access the raw value passed through\n        `--auth, -a`.\n\n        Return a ``requests.auth.AuthBase`` subclass instance.\n\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass TransportPlugin(BasePlugin):\n    \"\"\"\n    Requests transport adapter docs:\n\n        <https://requests.readthedocs.io/en/latest/user/advanced/#transport-adapters>\n\n    See httpie-unixsocket for an example transport plugin:\n\n        <https://github.com/httpie/httpie-unixsocket>\n\n    \"\"\"\n\n    # The URL prefix the adapter should be mount to.\n    prefix = None\n\n    def get_adapter(self):\n        \"\"\"\n        Return a ``requests.adapters.BaseAdapter`` subclass instance to be\n        mounted to ``self.prefix``.\n\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass ConverterPlugin(BasePlugin):\n    \"\"\"\n    Possibly converts binary response data for prettified terminal display.\n\n    See httpie-msgpack for an example converter plugin:\n\n        <https://github.com/rasky/httpie-msgpack>.\n\n    \"\"\"\n\n    def __init__(self, mime: str):\n        self.mime = mime\n\n    def convert(self, body: bytes) -> Tuple[str, str]:\n        \"\"\"\n        Convert a binary body to a textual representation for the terminal\n        and return a tuple containing the new Content-Type and content, e.g.:\n\n        ('application/json', '{}')\n\n        \"\"\"\n        raise NotImplementedError\n\n    @classmethod\n    def supports(cls, mime: str) -> bool:\n        raise NotImplementedError\n\n\nclass FormatterPlugin(BasePlugin):\n    \"\"\"\n    Possibly formats response body & headers for prettified terminal display.\n\n    \"\"\"\n    group_name = 'format'\n\n    def __init__(self, **kwargs):\n        \"\"\"\n        :param env: an class:`Environment` instance\n        :param kwargs: additional keyword argument that some\n                       formatters might require.\n\n        \"\"\"\n        self.enabled = True\n        self.kwargs = kwargs\n        self.format_options = kwargs['format_options']\n\n    def format_headers(self, headers: str) -> str:\n        \"\"\"Return processed `headers`\n\n        :param headers: The headers as text.\n\n        \"\"\"\n        return headers\n\n    def format_body(self, content: str, mime: str) -> str:\n        \"\"\"Return processed `content`.\n\n        :param mime: E.g., 'application/atom+xml'.\n        :param content: The body content as text\n\n        \"\"\"\n        return content\n\n    def format_metadata(self, metadata: str) -> str:\n        \"\"\"Return processed `metadata`.\n\n        :param metadata: The metadata as text.\n\n        \"\"\"\n        return metadata\n"
  },
  {
    "path": "httpie/plugins/builtin.py",
    "content": "from base64 import b64encode\n\nimport requests.auth\n\nfrom .base import AuthPlugin\n\n\n# noinspection PyAbstractClass\nclass BuiltinAuthPlugin(AuthPlugin):\n    package_name = '(builtin)'\n\n\nclass HTTPBasicAuth(requests.auth.HTTPBasicAuth):\n\n    def __call__(\n        self,\n        request: requests.PreparedRequest\n    ) -> requests.PreparedRequest:\n        \"\"\"\n        Override username/password serialization to allow unicode.\n\n        See https://github.com/httpie/cli/issues/212\n\n        \"\"\"\n        # noinspection PyTypeChecker\n        request.headers['Authorization'] = type(self).make_header(\n            self.username, self.password).encode('latin1')\n        return request\n\n    @staticmethod\n    def make_header(username: str, password: str) -> str:\n        credentials = f'{username}:{password}'\n        token = b64encode(credentials.encode()).strip().decode('latin1')\n        return f'Basic {token}'\n\n\nclass HTTPBearerAuth(requests.auth.AuthBase):\n\n    def __init__(self, token: str) -> None:\n        self.token = token\n\n    def __call__(self, request: requests.PreparedRequest) -> requests.PreparedRequest:\n        request.headers['Authorization'] = f'Bearer {self.token}'\n        return request\n\n\nclass BasicAuthPlugin(BuiltinAuthPlugin):\n    name = 'Basic HTTP auth'\n    auth_type = 'basic'\n    netrc_parse = True\n\n    # noinspection PyMethodOverriding\n    def get_auth(self, username: str, password: str) -> HTTPBasicAuth:\n        return HTTPBasicAuth(username, password)\n\n\nclass DigestAuthPlugin(BuiltinAuthPlugin):\n    name = 'Digest HTTP auth'\n    auth_type = 'digest'\n    netrc_parse = True\n\n    # noinspection PyMethodOverriding\n    def get_auth(\n        self,\n        username: str,\n        password: str\n    ) -> requests.auth.HTTPDigestAuth:\n        return requests.auth.HTTPDigestAuth(username, password)\n\n\nclass BearerAuthPlugin(BuiltinAuthPlugin):\n    name = 'Bearer HTTP Auth'\n    auth_type = 'bearer'\n    netrc_parse = False\n    auth_parse = False\n\n    # noinspection PyMethodOverriding\n    def get_auth(self, **kwargs) -> requests.auth.HTTPDigestAuth:\n        return HTTPBearerAuth(self.raw_auth)\n"
  },
  {
    "path": "httpie/plugins/manager.py",
    "content": "import sys\nimport os\nimport warnings\n\nfrom itertools import groupby\nfrom operator import attrgetter\nfrom typing import Dict, List, Type, Iterator, Iterable, Optional, ContextManager\nfrom pathlib import Path\nfrom contextlib import contextmanager, nullcontext\n\nfrom ..compat import importlib_metadata, find_entry_points, get_dist_name\n\nfrom ..utils import repr_dict, get_site_paths\nfrom . import AuthPlugin, ConverterPlugin, FormatterPlugin, TransportPlugin\nfrom .base import BasePlugin\n\n\nENTRY_POINT_CLASSES = {\n    'httpie.plugins.auth.v1': AuthPlugin,\n    'httpie.plugins.converter.v1': ConverterPlugin,\n    'httpie.plugins.formatter.v1': FormatterPlugin,\n    'httpie.plugins.transport.v1': TransportPlugin\n}\nENTRY_POINT_NAMES = list(ENTRY_POINT_CLASSES.keys())\n\n\n@contextmanager\ndef _load_directories(site_dirs: Iterable[Path]) -> Iterator[None]:\n    plugin_dirs = [\n        os.fspath(site_dir)\n        for site_dir in site_dirs\n    ]\n    sys.path.extend(plugin_dirs)\n    try:\n        yield\n    finally:\n        for plugin_dir in plugin_dirs:\n            sys.path.remove(plugin_dir)\n\n\ndef enable_plugins(plugins_dir: Optional[Path]) -> ContextManager[None]:\n    if plugins_dir is None:\n        return nullcontext()\n    else:\n        return _load_directories(get_site_paths(plugins_dir))\n\n\nclass PluginManager(list):\n    def register(self, *plugins: Type[BasePlugin]):\n        for plugin in plugins:\n            self.append(plugin)\n\n    def unregister(self, plugin: Type[BasePlugin]):\n        self.remove(plugin)\n\n    def filter(self, by_type=Type[BasePlugin]):\n        return [plugin for plugin in self if issubclass(plugin, by_type)]\n\n    def iter_entry_points(self, directory: Optional[Path] = None):\n        with enable_plugins(directory):\n            eps = importlib_metadata.entry_points()\n\n            for entry_point_name in ENTRY_POINT_NAMES:\n                yield from find_entry_points(eps, group=entry_point_name)\n\n    def load_installed_plugins(self, directory: Optional[Path] = None):\n        for entry_point in self.iter_entry_points(directory):\n            plugin_name = get_dist_name(entry_point)\n            try:\n                plugin = entry_point.load()\n            except BaseException as exc:\n                warnings.warn(\n                    f'While loading \"{plugin_name}\", an error occurred: {exc}\\n'\n                    f'For uninstallations, please use either \"httpie plugins uninstall {plugin_name}\" '\n                    f'or \"pip uninstall {plugin_name}\" (depending on how you installed it in the first '\n                    'place).'\n                )\n                continue\n            plugin.package_name = plugin_name\n            self.register(plugin)\n\n    # Auth\n    def get_auth_plugins(self) -> List[Type[AuthPlugin]]:\n        return self.filter(AuthPlugin)\n\n    def get_auth_plugin_mapping(self) -> Dict[str, Type[AuthPlugin]]:\n        return {\n            plugin.auth_type: plugin for plugin in self.get_auth_plugins()\n        }\n\n    def get_auth_plugin(self, auth_type: str) -> Type[AuthPlugin]:\n        return self.get_auth_plugin_mapping()[auth_type]\n\n    # Output processing\n    def get_formatters(self) -> List[Type[FormatterPlugin]]:\n        return self.filter(FormatterPlugin)\n\n    def get_formatters_grouped(self) -> Dict[str, List[Type[FormatterPlugin]]]:\n        return {\n            group_name: list(group)\n            for group_name, group\n            in groupby(self.get_formatters(), key=attrgetter('group_name'))\n        }\n\n    def get_converters(self) -> List[Type[ConverterPlugin]]:\n        return self.filter(ConverterPlugin)\n\n    # Adapters\n    def get_transport_plugins(self) -> List[Type[TransportPlugin]]:\n        return self.filter(TransportPlugin)\n\n    def __str__(self):\n        return repr_dict({\n            'adapters': self.get_transport_plugins(),\n            'auth': self.get_auth_plugins(),\n            'converters': self.get_converters(),\n            'formatters': self.get_formatters(),\n        })\n\n    def __repr__(self):\n        return f'<{type(self).__name__} {self}>'\n"
  },
  {
    "path": "httpie/plugins/registry.py",
    "content": "from .manager import PluginManager\nfrom .builtin import BasicAuthPlugin, DigestAuthPlugin, BearerAuthPlugin\nfrom ..output.formatters.headers import HeadersFormatter\nfrom ..output.formatters.json import JSONFormatter\nfrom ..output.formatters.xml import XMLFormatter\nfrom ..output.formatters.colors import ColorFormatter\n\n\nplugin_manager = PluginManager()\n\n\n# Register all built-in plugins.\nplugin_manager.register(\n    BasicAuthPlugin,\n    DigestAuthPlugin,\n    BearerAuthPlugin,\n    HeadersFormatter,\n    JSONFormatter,\n    XMLFormatter,\n    ColorFormatter,\n)\n"
  },
  {
    "path": "httpie/sessions.py",
    "content": "\"\"\"\nPersistent, JSON-serialized sessions.\n\n\"\"\"\nimport os\nimport re\n\nfrom http.cookies import SimpleCookie\nfrom http.cookiejar import Cookie\nfrom pathlib import Path\nfrom typing import Any, Dict, List, Optional, Union\n\nfrom requests.auth import AuthBase\nfrom requests.cookies import RequestsCookieJar, remove_cookie_by_name\n\nfrom .context import Environment, LogLevel\nfrom .cookies import HTTPieCookiePolicy\nfrom .cli.dicts import HTTPHeadersDict\nfrom .config import BaseConfigDict, DEFAULT_CONFIG_DIR\nfrom .utils import url_as_host\nfrom .plugins.registry import plugin_manager\n\nfrom .legacy import (\n    v3_1_0_session_cookie_format as legacy_cookies,\n    v3_2_0_session_header_format as legacy_headers\n)\n\n\nSESSIONS_DIR_NAME = 'sessions'\nDEFAULT_SESSIONS_DIR = DEFAULT_CONFIG_DIR / SESSIONS_DIR_NAME\nVALID_SESSION_NAME_PATTERN = re.compile('^[a-zA-Z0-9_.-]+$')\n# Request headers starting with these prefixes won't be stored in sessions.\n# They are specific to each request.\n# <https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Requests>\nSESSION_IGNORED_HEADER_PREFIXES = ['Content-', 'If-']\n\n# Cookie related options\nKEPT_COOKIE_OPTIONS = ['name', 'expires', 'path', 'value', 'domain', 'secure']\nDEFAULT_COOKIE_PATH = '/'\n\n\ndef is_anonymous_session(session_name: str) -> bool:\n    return os.path.sep in session_name\n\n\ndef session_hostname_to_dirname(hostname: str, session_name: str) -> str:\n    # host:port => host_port\n    hostname = hostname.replace(':', '_')\n    return os.path.join(\n        SESSIONS_DIR_NAME,\n        hostname,\n        f'{session_name}.json'\n    )\n\n\ndef strip_port(hostname: str) -> str:\n    return hostname.split(':')[0]\n\n\ndef materialize_cookie(cookie: Cookie) -> Dict[str, Any]:\n    materialized_cookie = {\n        option: getattr(cookie, option)\n        for option in KEPT_COOKIE_OPTIONS\n    }\n\n    if (\n        cookie._rest.get('is_explicit_none')\n        and materialized_cookie['domain'] == ''\n    ):\n        materialized_cookie['domain'] = None\n\n    return materialized_cookie\n\n\ndef materialize_cookies(jar: RequestsCookieJar) -> List[Dict[str, Any]]:\n    return [\n        materialize_cookie(cookie)\n        for cookie in jar\n    ]\n\n\ndef materialize_headers(headers: Dict[str, str]) -> List[Dict[str, Any]]:\n    return [\n        {\n            'name': name,\n            'value': value\n        }\n        for name, value in headers.copy().items()\n    ]\n\n\ndef get_httpie_session(\n    env: Environment,\n    config_dir: Path,\n    session_name: str,\n    host: Optional[str],\n    url: str,\n    *,\n    suppress_legacy_warnings: bool = False\n) -> 'Session':\n    bound_hostname = host or url_as_host(url)\n    if not bound_hostname:\n        # HACK/FIXME: httpie-unixsocket's URLs have no hostname.\n        bound_hostname = 'localhost'\n\n    if is_anonymous_session(session_name):\n        path = os.path.expanduser(session_name)\n        session_id = path\n    else:\n        path = config_dir / session_hostname_to_dirname(bound_hostname, session_name)\n        session_id = session_name\n\n    session = Session(\n        path,\n        env=env,\n        session_id=session_id,\n        bound_host=strip_port(bound_hostname),\n        suppress_legacy_warnings=suppress_legacy_warnings\n    )\n    session.load()\n    return session\n\n\nclass Session(BaseConfigDict):\n    helpurl = 'https://httpie.io/docs#sessions'\n    about = 'HTTPie session file'\n\n    def __init__(\n        self,\n        path: Union[str, Path],\n        env: Environment,\n        bound_host: str,\n        session_id: str,\n        suppress_legacy_warnings: bool = False,\n    ):\n        super().__init__(path=Path(path))\n\n        # Default values for the session files\n        self['headers'] = []\n        self['cookies'] = []\n        self['auth'] = {\n            'type': None,\n            'username': None,\n            'password': None\n        }\n\n        # Runtime state of the Session objects.\n        self.env = env\n        self._headers = HTTPHeadersDict()\n        self.cookie_jar = RequestsCookieJar(\n            # See also a temporary workaround for a Requests bug in `compat.py`.\n            policy=HTTPieCookiePolicy(),\n        )\n        self.session_id = session_id\n        self.bound_host = bound_host\n        self.suppress_legacy_warnings = suppress_legacy_warnings\n\n    def _add_cookies(self, cookies: List[Dict[str, Any]]) -> None:\n        for cookie in cookies:\n            domain = cookie.get('domain', '')\n            if domain is None:\n                # domain = None means explicitly lack of cookie, though\n                # requests requires domain to be a string so we'll cast it\n                # manually.\n                cookie['domain'] = ''\n                cookie['rest'] = {'is_explicit_none': True}\n\n            self.cookie_jar.set(**cookie)\n\n    def pre_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]:\n        for key, deserializer, importer in [\n            ('cookies', legacy_cookies.pre_process, self._add_cookies),\n            ('headers', legacy_headers.pre_process, self._headers.update),\n        ]:\n            values = data.get(key)\n            if values:\n                normalized_values = deserializer(self, values)\n            else:\n                normalized_values = []\n\n            importer(normalized_values)\n\n        return data\n\n    def post_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]:\n        for key, store, serializer, exporter in [\n            ('cookies', self.cookie_jar, materialize_cookies, legacy_cookies.post_process),\n            ('headers', self._headers, materialize_headers, legacy_headers.post_process),\n        ]:\n            original_type = type(data.get(key))\n            values = serializer(store)\n\n            data[key] = exporter(\n                values,\n                original_type=original_type\n            )\n\n        return data\n\n    def _compute_new_headers(self, request_headers: HTTPHeadersDict) -> HTTPHeadersDict:\n        new_headers = HTTPHeadersDict()\n        for name, value in request_headers.copy().items():\n            if value is None:\n                continue  # Ignore explicitly unset headers\n\n            original_value = value\n            if type(value) is not str:\n                value = value.decode()\n\n            if name.lower() == 'user-agent' and value.startswith('HTTPie/'):\n                continue\n\n            if name.lower() == 'cookie':\n                for cookie_name, morsel in SimpleCookie(value).items():\n                    if not morsel['path']:\n                        morsel['path'] = DEFAULT_COOKIE_PATH\n                    self.cookie_jar.set(cookie_name, morsel)\n\n                request_headers.remove_item(name, original_value)\n                continue\n\n            for prefix in SESSION_IGNORED_HEADER_PREFIXES:\n                if name.lower().startswith(prefix.lower()):\n                    break\n            else:\n                new_headers.add(name, value)\n\n        return new_headers\n\n    def update_headers(self, request_headers: HTTPHeadersDict):\n        \"\"\"\n        Update the session headers with the request ones while ignoring\n        certain name prefixes.\n\n        \"\"\"\n\n        new_headers = self._compute_new_headers(request_headers)\n        new_keys = new_headers.copy().keys()\n\n        # New headers will take priority over the existing ones, and override\n        # them directly instead of extending them.\n        for key, value in self._headers.copy().items():\n            if key in new_keys:\n                continue\n\n            new_headers.add(key, value)\n\n        self._headers = new_headers\n\n    @property\n    def headers(self) -> HTTPHeadersDict:\n        return self._headers.copy()\n\n    @property\n    def cookies(self) -> RequestsCookieJar:\n        self.cookie_jar.clear_expired_cookies()\n        return self.cookie_jar\n\n    @cookies.setter\n    def cookies(self, jar: RequestsCookieJar):\n        self.cookie_jar = jar\n\n    def remove_cookies(self, cookies: List[Dict[str, str]]):\n        for cookie in cookies:\n            remove_cookie_by_name(\n                self.cookie_jar,\n                cookie['name'],\n                domain=cookie.get('domain', None),\n                path=cookie.get('path', None)\n            )\n\n    @property\n    def auth(self) -> Optional[AuthBase]:\n        auth = self.get('auth', None)\n        if not auth or not auth['type']:\n            return\n\n        plugin = plugin_manager.get_auth_plugin(auth['type'])()\n\n        credentials = {'username': None, 'password': None}\n        try:\n            # New style\n            plugin.raw_auth = auth['raw_auth']\n        except KeyError:\n            # Old style\n            credentials = {\n                'username': auth['username'],\n                'password': auth['password'],\n            }\n        else:\n            if plugin.auth_parse:\n                from .cli.argtypes import parse_auth\n                parsed = parse_auth(plugin.raw_auth)\n                credentials = {\n                    'username': parsed.key,\n                    'password': parsed.value,\n                }\n\n        return plugin.get_auth(**credentials)\n\n    @auth.setter\n    def auth(self, auth: dict):\n        assert {'type', 'raw_auth'} == auth.keys()\n        self['auth'] = auth\n\n    @property\n    def is_anonymous(self):\n        return is_anonymous_session(self.session_id)\n\n    def warn_legacy_usage(self, warning: str) -> None:\n        if self.suppress_legacy_warnings:\n            return None\n\n        self.env.log_error(\n            warning,\n            level=LogLevel.WARNING\n        )\n\n        # We don't want to spam multiple warnings on each usage,\n        # so if there is already a warning for the legacy usage\n        # we'll skip the next ones.\n        self.suppress_legacy_warnings = True\n"
  },
  {
    "path": "httpie/ssl_.py",
    "content": "import ssl\nfrom typing import NamedTuple, Optional\n\n# noinspection PyPackageRequirements\nfrom urllib3.util.ssl_ import (\n    create_urllib3_context,\n    resolve_ssl_version,\n)\n\nfrom .adapters import HTTPAdapter\nfrom .compat import ensure_default_certs_loaded\n\n\nSSL_VERSION_ARG_MAPPING = {\n    'ssl2.3': 'PROTOCOL_SSLv23',\n    'ssl3': 'PROTOCOL_SSLv3',\n    'tls1': 'PROTOCOL_TLSv1',\n    'tls1.1': 'PROTOCOL_TLSv1_1',\n    'tls1.2': 'PROTOCOL_TLSv1_2',\n    'tls1.3': 'PROTOCOL_TLSv1_3',\n}\nAVAILABLE_SSL_VERSION_ARG_MAPPING = {\n    arg: getattr(ssl, constant_name)\n    for arg, constant_name in SSL_VERSION_ARG_MAPPING.items()\n    if hasattr(ssl, constant_name)\n}\n\n\nclass HTTPieCertificate(NamedTuple):\n    cert_file: Optional[str] = None\n    key_file: Optional[str] = None\n    key_password: Optional[str] = None\n\n    def to_raw_cert(self):\n        \"\"\"Synthesize a requests-compatible (2-item tuple of cert and key file)\n        object from HTTPie's internal representation of a certificate.\"\"\"\n        return self.cert_file, self.key_file\n\n\nclass HTTPieHTTPSAdapter(HTTPAdapter):\n    def __init__(\n        self,\n        verify: bool,\n        ssl_version: str = None,\n        ciphers: str = None,\n        **kwargs\n    ):\n        self._ssl_context = self._create_ssl_context(\n            verify=verify,\n            ssl_version=ssl_version,\n            ciphers=ciphers,\n        )\n        super().__init__(**kwargs)\n\n    def init_poolmanager(self, *args, **kwargs):\n        kwargs['ssl_context'] = self._ssl_context\n        return super().init_poolmanager(*args, **kwargs)\n\n    def proxy_manager_for(self, *args, **kwargs):\n        kwargs['ssl_context'] = self._ssl_context\n        return super().proxy_manager_for(*args, **kwargs)\n\n    def cert_verify(self, conn, url, verify, cert):\n        if isinstance(cert, HTTPieCertificate):\n            conn.key_password = cert.key_password\n            cert = cert.to_raw_cert()\n\n        return super().cert_verify(conn, url, verify, cert)\n\n    @staticmethod\n    def _create_ssl_context(\n        verify: bool,\n        ssl_version: str = None,\n        ciphers: str = None,\n    ) -> 'ssl.SSLContext':\n        ssl_context = create_urllib3_context(\n            ciphers=ciphers,\n            ssl_version=resolve_ssl_version(ssl_version),\n            # Since we are using a custom SSL context, we need to pass this\n            # here manually, even though it’s also passed to the connection\n            # in `super().cert_verify()`.\n            cert_reqs=ssl.CERT_REQUIRED if verify else ssl.CERT_NONE\n        )\n        ensure_default_certs_loaded(ssl_context)\n        return ssl_context\n\n    @classmethod\n    def get_default_ciphers_names(cls):\n        return [cipher['name'] for cipher in cls._create_ssl_context(verify=False).get_ciphers()]\n\n\ndef _is_key_file_encrypted(key_file):\n    \"\"\"Detects if a key file is encrypted or not.\n\n    Copy of the internal urllib function (urllib3.util.ssl_)\"\"\"\n\n    with open(key_file, \"r\") as f:\n        for line in f:\n            # Look for Proc-Type: 4,ENCRYPTED\n            if \"ENCRYPTED\" in line:\n                return True\n\n    return False\n\n\n# We used to import the default set of TLS ciphers from urllib3, but they removed it.\n# Instead, now urllib3 uses the list of ciphers configured by the system.\n# <https://github.com/httpie/cli/pull/1501>\nDEFAULT_SSL_CIPHERS_STRING = ':'.join(HTTPieHTTPSAdapter.get_default_ciphers_names())\n"
  },
  {
    "path": "httpie/status.py",
    "content": "from enum import IntEnum, unique\n\n\n@unique\nclass ExitStatus(IntEnum):\n    \"\"\"Program exit status code constants.\"\"\"\n    SUCCESS = 0\n    ERROR = 1\n    ERROR_TIMEOUT = 2\n\n    # See --check-status\n    ERROR_HTTP_3XX = 3\n    ERROR_HTTP_4XX = 4\n    ERROR_HTTP_5XX = 5\n\n    ERROR_TOO_MANY_REDIRECTS = 6\n    PLUGIN_ERROR = 7\n    # 128+2 SIGINT\n    # <http://www.tldp.org/LDP/abs/html/exitcodes.html>\n    ERROR_CTRL_C = 130\n\n\ndef http_status_to_exit_status(http_status: int, follow=False) -> ExitStatus:\n    \"\"\"\n    Translate HTTP status code to exit status code.\n\n    (Relevant only when invoked with --check-status or --download.)\n\n    \"\"\"\n    if 300 <= http_status <= 399 and not follow:\n        # Redirect\n        return ExitStatus.ERROR_HTTP_3XX\n    elif 400 <= http_status <= 499:\n        # Client Error\n        return ExitStatus.ERROR_HTTP_4XX\n    elif 500 <= http_status <= 599:\n        # Server Error\n        return ExitStatus.ERROR_HTTP_5XX\n    else:\n        return ExitStatus.SUCCESS\n"
  },
  {
    "path": "httpie/uploads.py",
    "content": "import sys\nimport os\nimport zlib\nimport functools\nimport threading\nfrom typing import Any, Callable, IO, Iterable, Optional, Tuple, Union, TYPE_CHECKING\nfrom urllib.parse import urlencode\n\nimport requests\nfrom requests.utils import super_len\n\nif TYPE_CHECKING:\n    from requests_toolbelt import MultipartEncoder\n\nfrom .context import Environment\nfrom .cli.dicts import MultipartRequestDataDict, RequestDataDict\nfrom .compat import is_windows\n\n\nclass ChunkedStream:\n    def __iter__(self) -> Iterable[Union[str, bytes]]:\n        raise NotImplementedError\n\n\nclass ChunkedUploadStream(ChunkedStream):\n    def __init__(\n        self,\n        stream: Iterable,\n        callback: Callable,\n        event: Optional[threading.Event] = None\n    ) -> None:\n        self.callback = callback\n        self.stream = stream\n        self.event = event\n\n    def __iter__(self) -> Iterable[Union[str, bytes]]:\n        for chunk in self.stream:\n            if self.event:\n                self.event.set()\n            self.callback(chunk)\n            yield chunk\n\n\nclass ChunkedMultipartUploadStream(ChunkedStream):\n    chunk_size = 100 * 1024\n\n    def __init__(\n        self,\n        encoder: 'MultipartEncoder',\n        event: Optional[threading.Event] = None\n    ) -> None:\n        self.encoder = encoder\n        self.event = event\n\n    def __iter__(self) -> Iterable[Union[str, bytes]]:\n        while True:\n            chunk = self.encoder.read(self.chunk_size)\n            if self.event:\n                self.event.set()\n            if not chunk:\n                break\n            yield chunk\n\n\ndef as_bytes(data: Union[str, bytes]) -> bytes:\n    if isinstance(data, str):\n        return data.encode()\n    else:\n        return data\n\n\nCallbackT = Callable[[bytes], bytes]\n\n\ndef _wrap_function_with_callback(\n    func: Callable[..., Any],\n    callback: CallbackT\n) -> Callable[..., Any]:\n    @functools.wraps(func)\n    def wrapped(*args, **kwargs):\n        chunk = func(*args, **kwargs)\n        callback(chunk)\n        return chunk\n    return wrapped\n\n\ndef is_stdin(file: IO) -> bool:\n    try:\n        file_no = file.fileno()\n    except Exception:\n        return False\n    else:\n        return file_no == sys.stdin.fileno()\n\n\nREAD_THRESHOLD = float(os.getenv('HTTPIE_STDIN_READ_WARN_THRESHOLD', 10.0))\n\n\ndef observe_stdin_for_data_thread(env: Environment, file: IO, read_event: threading.Event) -> None:\n    # Windows unfortunately does not support select() operation\n    # on regular files, like stdin in our use case.\n    # https://docs.python.org/3/library/select.html#select.select\n    if is_windows:\n        return None\n\n    # If the user configures READ_THRESHOLD to be 0, then\n    # disable this warning.\n    if READ_THRESHOLD == 0:\n        return None\n\n    def worker(event: threading.Event) -> None:\n        if not event.wait(timeout=READ_THRESHOLD):\n            env.stderr.write(\n                f'> warning: no stdin data read in {READ_THRESHOLD}s '\n                f'(perhaps you want to --ignore-stdin)\\n'\n                f'> See: https://httpie.io/docs/cli/best-practices\\n'\n            )\n\n    # Making it a daemon ensures that if the user exits from the main program\n    # (e.g. either regularly or with Ctrl-C), the thread will not\n    # block them.\n    thread = threading.Thread(\n        target=worker,\n        args=(read_event,),\n        daemon=True\n    )\n    thread.start()\n\n\ndef _read_file_with_selectors(file: IO, read_event: threading.Event) -> bytes:\n    if is_windows or not is_stdin(file):\n        return as_bytes(file.read())\n\n    import select\n\n    # Try checking whether there is any incoming data for READ_THRESHOLD\n    # seconds. If there isn't anything in the given period, issue\n    # a warning about a misusage.\n    read_selectors, _, _ = select.select([file], [], [], READ_THRESHOLD)\n    if read_selectors:\n        read_event.set()\n\n    return as_bytes(file.read())\n\n\ndef _prepare_file_for_upload(\n    env: Environment,\n    file: Union[IO, 'MultipartEncoder'],\n    callback: CallbackT,\n    chunked: bool = False,\n    content_length_header_value: Optional[int] = None,\n) -> Union[bytes, IO, ChunkedStream]:\n    read_event = threading.Event()\n    if not super_len(file):\n        if is_stdin(file):\n            observe_stdin_for_data_thread(env, file, read_event)\n\n        # Zero-length -> assume stdin.\n        if content_length_header_value is None and not chunked:\n            # Read the whole stdin to determine `Content-Length`.\n            #\n            # TODO: Instead of opt-in --chunked, consider making\n            #   `Transfer-Encoding: chunked` for STDIN opt-out via\n            #   something like --no-chunked.\n            #   This would be backwards-incompatible so wait until v3.0.0.\n            #\n            file = _read_file_with_selectors(file, read_event)\n    else:\n        file.read = _wrap_function_with_callback(\n            file.read,\n            callback\n        )\n\n    if chunked:\n        from requests_toolbelt import MultipartEncoder\n        if isinstance(file, MultipartEncoder):\n            return ChunkedMultipartUploadStream(\n                encoder=file,\n                event=read_event,\n            )\n        else:\n            return ChunkedUploadStream(\n                stream=file,\n                callback=callback,\n                event=read_event\n            )\n    else:\n        return file\n\n\ndef prepare_request_body(\n    env: Environment,\n    raw_body: Union[str, bytes, IO, 'MultipartEncoder', RequestDataDict],\n    body_read_callback: CallbackT,\n    offline: bool = False,\n    chunked: bool = False,\n    content_length_header_value: Optional[int] = None,\n) -> Union[bytes, IO, 'MultipartEncoder', ChunkedStream]:\n    is_file_like = hasattr(raw_body, 'read')\n    if isinstance(raw_body, (bytes, str)):\n        body = as_bytes(raw_body)\n    elif isinstance(raw_body, RequestDataDict):\n        body = as_bytes(urlencode(raw_body, doseq=True))\n    else:\n        body = raw_body\n\n    if offline:\n        if is_file_like:\n            return as_bytes(raw_body.read())\n        else:\n            return body\n\n    if is_file_like:\n        return _prepare_file_for_upload(\n            env,\n            body,\n            chunked=chunked,\n            callback=body_read_callback,\n            content_length_header_value=content_length_header_value\n        )\n    elif chunked:\n        return ChunkedUploadStream(\n            stream=iter([body]),\n            callback=body_read_callback\n        )\n    else:\n        return body\n\n\ndef get_multipart_data_and_content_type(\n    data: MultipartRequestDataDict,\n    boundary: str = None,\n    content_type: str = None,\n) -> Tuple['MultipartEncoder', str]:\n    from requests_toolbelt import MultipartEncoder\n\n    encoder = MultipartEncoder(\n        fields=data.items(),\n        boundary=boundary,\n    )\n    if content_type:\n        content_type = content_type.strip()\n        if 'boundary=' not in content_type:\n            content_type = f'{content_type}; boundary={encoder.boundary_value}'\n    else:\n        content_type = encoder.content_type\n\n    data = encoder\n    return data, content_type\n\n\ndef compress_request(\n    request: requests.PreparedRequest,\n    always: bool,\n):\n    deflater = zlib.compressobj()\n    if isinstance(request.body, str):\n        body_bytes = request.body.encode()\n    elif hasattr(request.body, 'read'):\n        body_bytes = request.body.read()\n    else:\n        body_bytes = request.body\n    deflated_data = deflater.compress(body_bytes)\n    deflated_data += deflater.flush()\n    is_economical = len(deflated_data) < len(body_bytes)\n    if is_economical or always:\n        request.body = deflated_data\n        request.headers['Content-Encoding'] = 'deflate'\n        request.headers['Content-Length'] = str(len(deflated_data))\n"
  },
  {
    "path": "httpie/utils.py",
    "content": "import os\nimport base64\nimport json\nimport mimetypes\nimport re\nimport sys\nimport time\nimport tempfile\nimport sysconfig\n\nfrom collections import OrderedDict\nfrom contextlib import contextmanager\nfrom http.cookiejar import parse_ns_headers\nfrom pathlib import Path\nfrom pprint import pformat\nfrom urllib.parse import urlsplit\nfrom typing import Any, List, Optional, Tuple, Generator, Callable, Iterable, IO, TypeVar\n\nimport requests.auth\n\nRE_COOKIE_SPLIT = re.compile(r', (?=[^ ;]+=)')\nItem = Tuple[str, Any]\nItems = List[Item]\nT = TypeVar(\"T\")\n\n\nclass JsonDictPreservingDuplicateKeys(OrderedDict):\n    \"\"\"A specialized JSON dict preserving duplicate keys.\"\"\"\n\n    # Python versions prior to 3.8 suffer from an issue with multiple keys with the same name.\n    # `json.dumps(obj, indent=N, sort_keys=True)` will output sorted keys when they are unique, and\n    # duplicate keys will be outputted as they were defined in the original data.\n    # See <https://bugs.python.org/issue23493#msg400929> for the behavior change between Python versions.\n    SUPPORTS_SORTING = sys.version_info >= (3, 8)\n\n    def __init__(self, items: Items):\n        self._items = items\n        self._ensure_items_used()\n\n    def _ensure_items_used(self) -> None:\n        \"\"\"HACK: Force `json.dumps()` to use `self.items()` instead of an empty dict.\n\n        Two JSON encoders are available on CPython: pure-Python (1) and C (2) implementations.\n\n        (1) The pure-python implementation will do a simple `if not dict: return '{}'`,\n        and we could fake that check by implementing the `__bool__()` method.\n        Source:\n            - <https://github.com/python/cpython/blob/9d318ad/Lib/json/encoder.py#L334-L336>\n\n        (2) On the other hand, the C implementation will do a check on the number of\n        items contained inside the dict, using a verification on `dict->ma_used`, which\n        is updated only when an item is added/removed from the dict. For that case,\n        there is no workaround but to add an item into the dict.\n        Sources:\n            - <https://github.com/python/cpython/blob/9d318ad/Modules/_json.c#L1581-L1582>\n            - <https://github.com/python/cpython/blob/9d318ad/Include/cpython/dictobject.h#L53>\n            - <https://github.com/python/cpython/blob/9d318ad/Include/cpython/dictobject.h#L17-L18>\n\n        To please both implementations, we simply add one item to the dict.\n\n        \"\"\"\n        if self._items:\n            self['__hack__'] = '__hack__'\n\n    def items(self) -> Items:\n        \"\"\"Return all items, duplicate ones included.\n\n        \"\"\"\n        return self._items\n\n\ndef load_json_preserve_order_and_dupe_keys(s):\n    return json.loads(s, object_pairs_hook=JsonDictPreservingDuplicateKeys)\n\n\ndef repr_dict(d: dict) -> str:\n    return pformat(d)\n\n\ndef humanize_bytes(n, precision=2):\n    # Author: Doug Latornell\n    # Licence: MIT\n    # URL: https://code.activestate.com/recipes/577081/\n    \"\"\"Return a humanized string representation of a number of bytes.\n\n    >>> humanize_bytes(1)\n    '1 B'\n    >>> humanize_bytes(1024, precision=1)\n    '1.0 kB'\n    >>> humanize_bytes(1024 * 123, precision=1)\n    '123.0 kB'\n    >>> humanize_bytes(1024 * 12342, precision=1)\n    '12.1 MB'\n    >>> humanize_bytes(1024 * 12342, precision=2)\n    '12.05 MB'\n    >>> humanize_bytes(1024 * 1234, precision=2)\n    '1.21 MB'\n    >>> humanize_bytes(1024 * 1234 * 1111, precision=2)\n    '1.31 GB'\n    >>> humanize_bytes(1024 * 1234 * 1111, precision=1)\n    '1.3 GB'\n\n    \"\"\"\n    abbrevs = [\n        (1 << 50, 'PB'),\n        (1 << 40, 'TB'),\n        (1 << 30, 'GB'),\n        (1 << 20, 'MB'),\n        (1 << 10, 'kB'),\n        (1, 'B')\n    ]\n\n    if n == 1:\n        return '1 B'\n\n    for factor, suffix in abbrevs:\n        if n >= factor:\n            break\n\n    # noinspection PyUnboundLocalVariable\n    return f'{n / factor:.{precision}f} {suffix}'\n\n\nclass ExplicitNullAuth(requests.auth.AuthBase):\n    \"\"\"Forces requests to ignore the ``.netrc``.\n    <https://github.com/psf/requests/issues/2773#issuecomment-174312831>\n    \"\"\"\n\n    def __call__(self, r):\n        return r\n\n\ndef get_content_type(filename):\n    \"\"\"\n    Return the content type for ``filename`` in format appropriate\n    for Content-Type headers, or ``None`` if the file type is unknown\n    to ``mimetypes``.\n\n    \"\"\"\n    return mimetypes.guess_type(filename, strict=False)[0]\n\n\ndef split_cookies(cookies):\n    \"\"\"\n    When ``requests`` stores cookies in ``response.headers['Set-Cookie']``\n    it concatenates all of them through ``, ``.\n\n    This function splits cookies apart being careful to not to\n    split on ``, `` which may be part of cookie value.\n    \"\"\"\n    if not cookies:\n        return []\n    return RE_COOKIE_SPLIT.split(cookies)\n\n\ndef get_expired_cookies(\n    cookies: str,\n    now: float = None\n) -> List[dict]:\n\n    now = now or time.time()\n\n    def is_expired(expires: Optional[float]) -> bool:\n        return expires is not None and expires <= now\n\n    attr_sets: List[Tuple[str, str]] = parse_ns_headers(\n        split_cookies(cookies)\n    )\n\n    cookies = [\n        # The first attr name is the cookie name.\n        dict(attrs[1:], name=attrs[0][0])\n        for attrs in attr_sets\n    ]\n\n    _max_age_to_expires(cookies=cookies, now=now)\n\n    return [\n        {\n            'name': cookie['name'],\n            'path': cookie.get('path', '/')\n        }\n        for cookie in cookies\n        if is_expired(expires=cookie.get('expires'))\n    ]\n\n\ndef _max_age_to_expires(cookies, now):\n    \"\"\"\n    Translate `max-age` into `expires` for Requests to take it into account.\n\n    HACK/FIXME: <https://github.com/psf/requests/issues/5743>\n\n    \"\"\"\n    for cookie in cookies:\n        if 'expires' in cookie:\n            continue\n        max_age = cookie.get('max-age')\n        if max_age and max_age.isdigit():\n            cookie['expires'] = now + float(max_age)\n\n\ndef parse_content_type_header(header):\n    \"\"\"Borrowed from requests.\"\"\"\n    tokens = header.split(';')\n    content_type, params = tokens[0].strip(), tokens[1:]\n    params_dict = {}\n    items_to_strip = \"\\\"' \"\n    for param in params:\n        param = param.strip()\n        if param:\n            key, value = param, True\n            index_of_equals = param.find(\"=\")\n            if index_of_equals != -1:\n                key = param[:index_of_equals].strip(items_to_strip)\n                value = param[index_of_equals + 1:].strip(items_to_strip)\n            params_dict[key.lower()] = value\n    return content_type, params_dict\n\n\ndef as_site(path: Path, **extra_vars) -> Path:\n    site_packages_path = sysconfig.get_path(\n        'purelib',\n        vars={'base': str(path), **extra_vars}\n    )\n    return Path(site_packages_path)\n\n\ndef get_site_paths(path: Path) -> Iterable[Path]:\n    from httpie.compat import (\n        MIN_SUPPORTED_PY_VERSION,\n        MAX_SUPPORTED_PY_VERSION,\n        is_frozen\n    )\n\n    if is_frozen:\n        [major, min_minor] = MIN_SUPPORTED_PY_VERSION\n        [major, max_minor] = MAX_SUPPORTED_PY_VERSION\n        for minor in range(min_minor, max_minor + 1):\n            yield as_site(\n                path,\n                py_version_short=f'{major}.{minor}'\n            )\n    else:\n        yield as_site(path)\n\n\ndef split_iterable(iterable: Iterable[T], key: Callable[[T], bool]) -> Tuple[List[T], List[T]]:\n    left, right = [], []\n    for item in iterable:\n        if key(item):\n            left.append(item)\n        else:\n            right.append(item)\n    return left, right\n\n\ndef unwrap_context(exc: Exception) -> Optional[Exception]:\n    context = exc.__context__\n    if isinstance(context, Exception):\n        return unwrap_context(context)\n    else:\n        return exc\n\n\ndef url_as_host(url: str) -> str:\n    return urlsplit(url).netloc.split('@')[-1]\n\n\nclass LockFileError(ValueError):\n    pass\n\n\n@contextmanager\ndef open_with_lockfile(file: Path, *args, **kwargs) -> Generator[IO[Any], None, None]:\n    file_id = base64.b64encode(os.fsencode(file)).decode()\n    target_file = Path(tempfile.gettempdir()) / file_id\n\n    # Have an atomic-like touch here, so we'll tighten the possibility of\n    # a race occurring between multiple processes accessing the same file.\n    try:\n        target_file.touch(exist_ok=False)\n    except FileExistsError as exc:\n        raise LockFileError(\"Can't modify a locked file.\") from exc\n\n    try:\n        with open(file, *args, **kwargs) as stream:\n            yield stream\n    finally:\n        target_file.unlink()\n\n\ndef is_version_greater(version_1: str, version_2: str) -> bool:\n    # In an ideal scenario, we would depend on `packaging` in order\n    # to offer PEP 440 compatible parsing. But since it might not be\n    # commonly available for outside packages, and since we are only\n    # going to parse HTTPie's own version it should be fine to compare\n    # this in a SemVer subset fashion.\n\n    def split_version(version: str) -> Tuple[int, ...]:\n        parts = []\n        for part in version.split('.')[:3]:\n            try:\n                parts.append(int(part))\n            except ValueError:\n                break\n        return tuple(parts)\n\n    return split_version(version_1) > split_version(version_2)\n"
  },
  {
    "path": "pytest.ini",
    "content": "[pytest]\nmarkers =\n    # If you want to run tests without a full HTTPie installation\n    # we advise you to disable the markers below, e.g:\n    # pytest -m 'not requires_installation and not requires_external_processes'\n    requires_installation\n    requires_external_processes\n"
  },
  {
    "path": "setup.cfg",
    "content": "# Please keep all characters in this file in ASCII\n# distutils uses system's locale to interpret it and not everybody\n# uses UTF-8. See https://github.com/httpie/cli/issues/1039\n# for an example\n[wheel]\n\n\n[tool:pytest]\n# <https://docs.pytest.org/en/latest/customize.html>\ntestpaths = httpie tests\nnorecursedirs = tests/fixtures\naddopts = --tb=native --doctest-modules --verbose\nxfail_strict = True\n\n[metadata]\nname = httpie\nversion = attr: httpie.__version__\nauthor = Jakub Roztocil\nauthor_email = jakub@roztocil.co\nlicense = BSD\ndescription = HTTPie: modern, user-friendly command-line HTTP client for the API era.\nurl = https://httpie.io/\nlong_description = file: README.md\nlong_description_content_type = text/markdown\nclassifiers =\n    Development Status :: 5 - Production/Stable\n    Programming Language :: Python\n    Programming Language :: Python :: 3 :: Only\n    Environment :: Console\n    Intended Audience :: Developers\n    Intended Audience :: System Administrators\n    License :: OSI Approved :: BSD License\n    Topic :: Internet :: WWW/HTTP\n    Topic :: Software Development\n    Topic :: System :: Networking\n    Topic :: Terminals\n    Topic :: Text Processing\n    Topic :: Utilities\nproject_urls =\n    GitHub = https://github.com/httpie/cli\n    Twitter = https://twitter.com/httpie\n    Discord = https://httpie.io/discord\n    Documentation = https://httpie.io/docs\n    Online Demo = https://httpie.io/run\n\n\n[options]\npackages = find:\ninstall_requires =\n    pip\n    charset_normalizer>=2.0.0\n    defusedxml>=0.6.0\n    requests[socks] >=2.22.0\n    Pygments>=2.5.2\n    requests-toolbelt>=0.9.1\n    multidict>=4.7.0\n    setuptools\n    importlib-metadata>=1.4.0; python_version<\"3.8\"\n    rich>=9.10.0\n    colorama>=0.2.4; sys_platform==\"win32\"\npython_requires = >=3.7\n\n\n[flake8]\n# <https://flake8.pycqa.org/en/latest/user/error-codes.html>\n# E501 - line too long\n# W503 - line break before binary operator\nignore = E501,W503\n\n[options.packages.find]\ninclude =\n    httpie\n    httpie.*\n\n[options.entry_points]\nconsole_scripts =\n    http = httpie.__main__:main\n    https = httpie.__main__:main\n    httpie = httpie.manager.__main__:main\n\n[options.extras_require]\ndev =\n    pytest\n    pytest-httpbin>=0.0.6\n    responses\n    pytest-mock\n    werkzeug<2.1.0\n    flake8\n    flake8-comprehensions\n    flake8-deprecated\n    flake8-mutable\n    flake8-tuple\n    pyopenssl\n    pytest-cov\n    pyyaml\n    twine\n    wheel\n    Jinja2\ntest =\n    pytest\n    pytest-httpbin>=0.0.6\n    responses\n    pytest-mock\n    werkzeug<2.1.0\n\n[options.data_files]\nshare/man/man1 =\n    extras/man/http.1\n    extras/man/https.1\n    extras/man/httpie.1\n"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup\n\nsetup()\n"
  },
  {
    "path": "snapcraft.yaml",
    "content": "name: httpie\ntitle: HTTPie\nsummary: Modern, user-friendly command-line HTTP client for the API era\ndescription: |\n  HTTPie *aitch-tee-tee-pie* is a user-friendly command-line HTTP client\n  for the API era.\n  It comes with JSON support, syntax highlighting, persistent sessions,\n  wget-like downloads, plugins, and more.\n\n  The project's goal is to make CLI interaction with web services as\n  human-friendly as possible. HTTPie is designed for testing, debugging,\n  and generally interacting with APIs & HTTP servers.\n  The http & https commands allow for creating and sending arbitrary HTTP\n  requests. They use simple and natural syntax and provide formatted and\n  colorized output.\n\n  Main features:\n    - Built-in JSON support\n    - Colorized and formatted terminal output\n    - Sensible defaults for the API era\n    - Persistent sessions\n    - Forms and file uploads\n    - HTTPS, proxies, and authentication support\n    - Support for arbitrary request data and headers\n    - Wget-like downloads\n    - Extensions API\n    - Expressive and intuitive syntax\n    - Linux, macOS, Windows, and FreeBSD support\n    - All that & more in 2 simple commands: http + https\n\n  Links\n    - Documentation: https://httpie.io/docs\n    - Try in browser: https://httpie.io/run\n    - GitHub: https://github.com/httpie/cli\n    - Twitter: https://twitter.com/httpie\n    - Discord: https://httpie.io/chat\nlicense: BSD-3-Clause-LBNL\n\n# Automatically change the current version based on the source code\nadopt-info: httpie\n\n# https://snapcraft.io/docs/snapcraft-top-level-metadata#heading--icon\n# icon:\n\nbase: core20\nconfinement: strict\ngrade: stable\ncompression: lzo\n\nparts:\n  httpie:\n    source: .\n    plugin: python\n\n    # Guess the current version from sources\n    override-pull: |\n      snapcraftctl pull\n      snapcraftctl set-version $(grep '__version__' httpie/__init__.py | cut -d\"'\" -f2)\n\n    override-build: |\n      snapcraftctl build\n\n      echo \"Adding HTTPie plugins ...\"\n      python -m pip install httpie-unixsocket\n      python -m pip install httpie-snapdsocket\n\n    override-prime: |\n      snapcraftctl prime\n\n      echo \"Removing useless files ...\"\n      packages=$SNAPCRAFT_PRIME/lib/python3.8/site-packages\n      rm -rfv $packages/pkg_resources/tests\n      rm -rfv $packages/requests_unixsocket/test*\n\n      echo \"Compiling pyc files ...\"\n      python -m compileall -f $packages\n\n      echo \"Copying extra files ...\"\n      cp $SNAPCRAFT_PART_SRC/extras/httpie-completion.bash $SNAPCRAFT_PRIME/\n\nplugs:\n  dot-config-httpie:\n    interface: personal-files\n    write:\n      - $HOME/.config/httpie\n  dot-httpie:\n    interface: personal-files\n    write:\n      - $HOME/.httpie\n\napps:\n  http:\n    command: bin/http\n    plugs: &plugs\n      - dot-config-httpie\n      - dot-httpie\n      - home\n      - network\n      - removable-media\n    completer: httpie-completion.bash\n    environment:\n      LC_ALL: C.UTF-8\n\n  https:\n    command: bin/https\n    plugs: *plugs\n    completer: httpie-completion.bash\n    environment:\n      LC_ALL: C.UTF-8\n\n  httpie:\n    command: bin/httpie\n    plugs: *plugs\n    environment:\n      LC_ALL: C.UTF-8\n"
  },
  {
    "path": "tests/README.md",
    "content": "# HTTPie Test Suite\n\nPlease see [CONTRIBUTING](https://github.com/httpie/cli/blob/master/CONTRIBUTING.md) for contribution and testing guidelines.\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/client_certs/client.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIUNMIIO7cG2Lkx+qo0Z43k4+voT4swDQYJKoZIhvcNAQEN\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDA3MDQxMDE5NDBaFw0yMTA3\nMDQxMDE5NDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQCpnv/bnF8qkRoFu2M/+btxR5kRDAMqvbBivG2F4Uop\n37mxwW0YJFOiMtzCN76w8JgEZrTeH3zG0fNNdIKIKjjwf+8j3KSbQi60oDOelkL5\n34Yt1o+lW9ricKQCsVl/XkYHh4RPnzNE8XRZmcZtL/6+1vVjTlxe6iW1Q0tzU2l3\nRHPhHbmsVclwFOd/eE+D6WB5tb6SVvhDyOfLdZwxWWpgARx6aboR/+/CKazE0wt4\nIJtTpe3M7IHt3i/8EkCZyFNdV+pQ8qz3PIOKBQws8aCpuQ+IHnvq4wSiyUV6eEaU\nbfOguWHGKlyVuN9AIiNl8A4xlU6QHKwzisTuRywschlvT8LaK1WGk+BNGBcidogh\nyp73KrDpiUd+Udv3TPDg5Q7pE6LT+sZxFrCidvZEZ1YdBDfXUhOaCTmtlFFYJiMT\n2+FnPQCfFv53D79llGaovE7t6KBf+qYRpIkSDoYhSSZ5GhFGTVsgQERYG39MSnbz\n4b1CQtg7Q8e9DJq8d/ChKUCfymJ+HSQIXEMu1FXrlEbEoyGvRyvA5cnUHjvY7GPY\n2HGHHaTFhiB9qRQhD3TdK4G6iIHF9tuxi2V+7waYp7q9N8KHfZRhIZbGSWQlaM9f\nnjAUy8NAX6W4cL/ZpDf8PpVeMhLolvO8D8qCNZyWD+x5HtqDfqFkFPvr2vOSxZ+v\n6wIDAQABo1MwUTAdBgNVHQ4EFgQUkJwSpoGIxHUaArfJrX602HdHUWcwHwYDVR0j\nBBgwFoAUkJwSpoGIxHUaArfJrX602HdHUWcwDwYDVR0TAQH/BAUwAwEB/zANBgkq\nhkiG9w0BAQ0FAAOCAgEAqDuULnNBNJsydUXDyGTzCrXjJuqhuOhi1eALyCLwuT+F\n+/l7hOgdKWn4KJF4vcfNObcWh7sJ+iIcXEOYKaL3dPW9nqj+oCoPBKNAX+u3ZKXy\nI4O5wVAd3X0beh1ba69nOfdn9PMlVEB80TzTda0My9+tI5SD84OXUc7AWQXnh5Sb\ntHkul7cKcBA7/phnlC83qa6WoMlmNfqo8s2u+quDkhshKdrLFGGBI17gUQH3GbHN\nWBymHi/BCCIKYJB9+vt+M5L5C8FtNCMrCwTGtIOgC9IMre4wF2gODbjuRtkO2w6k\nsXOtKweCdgMd2H3SwE4txEU2hUHE1IYPYnG1fg0YwYfKfbTLZQtn7xgEK93+nkp8\nufnnHgUxd//+pFPkbEOTnShuepl7g45qOBGUX4fBh78EVeL7NIZ9F8dHGsawD/CT\n/tATlH9gQ+JRvXCNCKO8jNgeu3v2gVw+haXP1d4F7NysVIr4A5LiFufJk5Zyizcm\nWyjgfI99CnEwvqzv4yMQCoHAOK3awhH7uR+QHhCpG9D91PlzdJu7yP7O7zQaKobg\nYTqMoMkYr63WbMrH21Tokoc/6CBPAAp3g8rC/E024SquJE7OUG0If5JkvlfJU5EP\nK+e7hFNoD4uc+0cgAccpEb9hCc0oPfC+3WM5poVBKSnukfs4KyqcVIt4ZaNoYic=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tests/client_certs/client.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKQIBAAKCAgEAqZ7/25xfKpEaBbtjP/m7cUeZEQwDKr2wYrxtheFKKd+5scFt\nGCRTojLcwje+sPCYBGa03h98xtHzTXSCiCo48H/vI9ykm0IutKAznpZC+d+GLdaP\npVva4nCkArFZf15GB4eET58zRPF0WZnGbS/+vtb1Y05cXuoltUNLc1Npd0Rz4R25\nrFXJcBTnf3hPg+lgebW+klb4Q8jny3WcMVlqYAEcemm6Ef/vwimsxNMLeCCbU6Xt\nzOyB7d4v/BJAmchTXVfqUPKs9zyDigUMLPGgqbkPiB576uMEoslFenhGlG3zoLlh\nxipclbjfQCIjZfAOMZVOkBysM4rE7kcsLHIZb0/C2itVhpPgTRgXInaIIcqe9yqw\n6YlHflHb90zw4OUO6ROi0/rGcRawonb2RGdWHQQ311ITmgk5rZRRWCYjE9vhZz0A\nnxb+dw+/ZZRmqLxO7eigX/qmEaSJEg6GIUkmeRoRRk1bIEBEWBt/TEp28+G9QkLY\nO0PHvQyavHfwoSlAn8pifh0kCFxDLtRV65RGxKMhr0crwOXJ1B472Oxj2Nhxhx2k\nxYYgfakUIQ903SuBuoiBxfbbsYtlfu8GmKe6vTfCh32UYSGWxklkJWjPX54wFMvD\nQF+luHC/2aQ3/D6VXjIS6JbzvA/KgjWclg/seR7ag36hZBT769rzksWfr+sCAwEA\nAQKCAgBmZ1W0si1KN5vsRftfjle5xi4E+qmWzjqFAZllsGPj7+veAxbn8laDoA1j\nO+BmVnqQfalISN498lbfNi3wIv2JRNONZRIDoesspWNEpRb+YBJT7it++3ukJbj+\n3y9XFAVXWlto7oY3Y0aJKauAE+/KK2CueYqOyvHFA0Gz+HG9zZfgGuATyR76CcTR\nUkM/MlBKao0JMHRmCA7Y6MJJkOAF4eXdiaMKZufK4vopQfi0p4re71gn1cmDYBa8\nKhDSRvz9Z6xQ/pGqGeCYHQACykXi8ZUM6sqJPlF4LedCTwbdaZwiNolu5/hJc/lk\ncLfKPSl0id2KZ6UW4PqPmGx00NXFP/XcCxzzht8ejrI1GY9LXR6fKpmoYZvUoXba\nSK58l+OcAaxJ7JoTCvH2adas5mhNGyHTTghceNlFPuT+LC7nNq6rJD0QLouDQMr5\n0my2lJtDiafa+Z3aGt759vkTT7k4wnfWNkjZJDIVf6UkAoMFtN5nOgR36OaDLegA\n7udascC3hKRUi2BIlc713hl2dlcPVMcCQArpvbwgwPFXiZO9PW+Qc7IWogHqWNWY\nMs9JsDcAE5Q5PRlAA8QSveSyl3QNJpeHT9PVx159a28E8xEWCs9nfpI/jXfYxFnr\ndfS7gn8XW1WNUJvtHsKIhdSRD/4ks6VRPm6KMskR+j+zpTbmcQKCAQEA3CvDiT/E\noD2VK9rE0KNDZBljED2p7IVE+zED5olGPUGC3F+WiEl9ldd6DKL6K0Xv/zAEv7Nt\nhHJ4m3B8siOQf2wzrX6JTvqDhBnrYjsD3VU7Zpys4ZjMOAp/aIM124ZRDECe2do3\nyzfV+oR0qw9KmyywjMwPa/8LL9d+kwYSQX6Y2hy+5TquDghKCmQzBw0iCDlmWfNP\njqfztSc1oBPcij+X98h3EI3Ai7R+hlolWlowXy0qBY8qCWegbguRDFkDhTXDCPwW\nRMiQobI3xWfhZybSohx42/HUYMi5Uis++CV3XeE/aRdLw/O3gHTz5n9Z3v0i0Xnd\nKIWxpCKzLzLAVwKCAQEAxTlZHVlNaVz8fsSajAyq3n4LnOxGEwhYspzY7U2tHnbr\nU1QXTlvGN97u9hMdHgvvPu7OULfeJM0EPNBdQC2B2Y2vkAZBcdw2cgXdzVksv+gO\n//ryo37xBZXY46prGyPZCrfrrBXHNOHlxY1AklQUu8PnNKU+Z02hirMtY6pm/WyI\n2fbUJRqQu3nTMiuqFeee+5vaKbWXPRWKjpF/KZxoA4YSymGhG+fVIJVKxWjz1ns/\n0Kkx/a4D3xWZO+vY9LE24PZzygUfr3/ZsCe8N+UpvZ60h7eJT9DJB1ETgqPFL8zr\nEhGxoNDLRpm0b1JELAuclCHuHdqQ/uTJB2DjSFpAjQKCAQEAxmNU3R4toan7+Tk2\ncT07oz3Q6rh1nd70KlefSSLWvKmELeif7owx8kvn+Oz9+PIa8FmnXcli3J59GKsC\nYU30jSzFYAaN2TGYQfdNBwVgVRbQ4IQ6r0kMc07aQSVB6V4dN6oeuPSNo7rbP9IM\ngnrT4gEh0KyrFMgKn4BQ2E/3MTbOqnKOfGUkoxZLCRQCes8VpE18cX7xZ/zkd44u\nHuDmr1fgKnBjAPKJ1hi8jXk7ATAVOB2tKLc4zKKoh6A6geLPbj/kTvs/YZlL4beB\n04noLBdqYpK/QIimstMLUgQPyG+SIHCvv5UzOw0ng0Ne5opIQ8rajeB+LF5TlC+E\nP/o+HwKCAQAurZcI2jT3JfngqvmFAg6C4EQxXL5tDMGpbHPvHj5GApFJxJJLim8M\nlCfsd7Ohg+OY+n48HnhmL1u8ZPhdEygzbFRL+x8MKrl8HSVUz7FGrk62iRdaWNYE\no2WU5KW6464f2k3eCb1/J6PxMLBCscHCeuhCzoVJf9cm86dfeloryr9NDx1Attvg\nc0HoEuuLialYFZf53S+xVmLXwVneaFU52EakPZ0a9LC9qHfs5x0m+z6sTQ824jOq\nXftJclWD/FlnvwzCmJnaOKE2DwF+HS/W4DQMFwVZramWoLrEZaxq1s4gFa37yM8D\no6dP3aGi5xClAq7PxAYjPdTSeTzxx+KVAoIBAQDGwk1/sJW99Oif+7RXvV99l+BL\n1R0BI1Dgc+aXkXSX4OeWJdLdiGLztrJ/lEzesKEdVHmG+wamexaxWzYgUeKklcAA\nIPrEawh3qB9gmlWei4BrK+e0cGjPZwq5bQi7gkpsMdxlHYkCmO12DzZ7/4CaGqET\n+Az0Xa7wjlRbSv62HvKbCm1yMizs8l9k3E8vMo9vU1soyEvR3r/aHzo7KyiXJaio\nioppLcx/FVQCkaFQ1/H4dBZCSxviJxQmnOWlTkJT1mH44GLQnv21UsEWUrpz13VK\n8Dp0zWwNtSKoEQ6YJYl1Nwt04OhUrxG5fStSOpRiQ2r8bUAM0d4qDSjV92Yf\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "tests/client_certs/client.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIUNMIIO7cG2Lkx+qo0Z43k4+voT4swDQYJKoZIhvcNAQEN\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDA3MDQxMDE5NDBaFw0yMTA3\nMDQxMDE5NDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQCpnv/bnF8qkRoFu2M/+btxR5kRDAMqvbBivG2F4Uop\n37mxwW0YJFOiMtzCN76w8JgEZrTeH3zG0fNNdIKIKjjwf+8j3KSbQi60oDOelkL5\n34Yt1o+lW9ricKQCsVl/XkYHh4RPnzNE8XRZmcZtL/6+1vVjTlxe6iW1Q0tzU2l3\nRHPhHbmsVclwFOd/eE+D6WB5tb6SVvhDyOfLdZwxWWpgARx6aboR/+/CKazE0wt4\nIJtTpe3M7IHt3i/8EkCZyFNdV+pQ8qz3PIOKBQws8aCpuQ+IHnvq4wSiyUV6eEaU\nbfOguWHGKlyVuN9AIiNl8A4xlU6QHKwzisTuRywschlvT8LaK1WGk+BNGBcidogh\nyp73KrDpiUd+Udv3TPDg5Q7pE6LT+sZxFrCidvZEZ1YdBDfXUhOaCTmtlFFYJiMT\n2+FnPQCfFv53D79llGaovE7t6KBf+qYRpIkSDoYhSSZ5GhFGTVsgQERYG39MSnbz\n4b1CQtg7Q8e9DJq8d/ChKUCfymJ+HSQIXEMu1FXrlEbEoyGvRyvA5cnUHjvY7GPY\n2HGHHaTFhiB9qRQhD3TdK4G6iIHF9tuxi2V+7waYp7q9N8KHfZRhIZbGSWQlaM9f\nnjAUy8NAX6W4cL/ZpDf8PpVeMhLolvO8D8qCNZyWD+x5HtqDfqFkFPvr2vOSxZ+v\n6wIDAQABo1MwUTAdBgNVHQ4EFgQUkJwSpoGIxHUaArfJrX602HdHUWcwHwYDVR0j\nBBgwFoAUkJwSpoGIxHUaArfJrX602HdHUWcwDwYDVR0TAQH/BAUwAwEB/zANBgkq\nhkiG9w0BAQ0FAAOCAgEAqDuULnNBNJsydUXDyGTzCrXjJuqhuOhi1eALyCLwuT+F\n+/l7hOgdKWn4KJF4vcfNObcWh7sJ+iIcXEOYKaL3dPW9nqj+oCoPBKNAX+u3ZKXy\nI4O5wVAd3X0beh1ba69nOfdn9PMlVEB80TzTda0My9+tI5SD84OXUc7AWQXnh5Sb\ntHkul7cKcBA7/phnlC83qa6WoMlmNfqo8s2u+quDkhshKdrLFGGBI17gUQH3GbHN\nWBymHi/BCCIKYJB9+vt+M5L5C8FtNCMrCwTGtIOgC9IMre4wF2gODbjuRtkO2w6k\nsXOtKweCdgMd2H3SwE4txEU2hUHE1IYPYnG1fg0YwYfKfbTLZQtn7xgEK93+nkp8\nufnnHgUxd//+pFPkbEOTnShuepl7g45qOBGUX4fBh78EVeL7NIZ9F8dHGsawD/CT\n/tATlH9gQ+JRvXCNCKO8jNgeu3v2gVw+haXP1d4F7NysVIr4A5LiFufJk5Zyizcm\nWyjgfI99CnEwvqzv4yMQCoHAOK3awhH7uR+QHhCpG9D91PlzdJu7yP7O7zQaKobg\nYTqMoMkYr63WbMrH21Tokoc/6CBPAAp3g8rC/E024SquJE7OUG0If5JkvlfJU5EP\nK+e7hFNoD4uc+0cgAccpEb9hCc0oPfC+3WM5poVBKSnukfs4KyqcVIt4ZaNoYic=\n-----END CERTIFICATE-----\n-----BEGIN RSA PRIVATE KEY-----\nMIIJKQIBAAKCAgEAqZ7/25xfKpEaBbtjP/m7cUeZEQwDKr2wYrxtheFKKd+5scFt\nGCRTojLcwje+sPCYBGa03h98xtHzTXSCiCo48H/vI9ykm0IutKAznpZC+d+GLdaP\npVva4nCkArFZf15GB4eET58zRPF0WZnGbS/+vtb1Y05cXuoltUNLc1Npd0Rz4R25\nrFXJcBTnf3hPg+lgebW+klb4Q8jny3WcMVlqYAEcemm6Ef/vwimsxNMLeCCbU6Xt\nzOyB7d4v/BJAmchTXVfqUPKs9zyDigUMLPGgqbkPiB576uMEoslFenhGlG3zoLlh\nxipclbjfQCIjZfAOMZVOkBysM4rE7kcsLHIZb0/C2itVhpPgTRgXInaIIcqe9yqw\n6YlHflHb90zw4OUO6ROi0/rGcRawonb2RGdWHQQ311ITmgk5rZRRWCYjE9vhZz0A\nnxb+dw+/ZZRmqLxO7eigX/qmEaSJEg6GIUkmeRoRRk1bIEBEWBt/TEp28+G9QkLY\nO0PHvQyavHfwoSlAn8pifh0kCFxDLtRV65RGxKMhr0crwOXJ1B472Oxj2Nhxhx2k\nxYYgfakUIQ903SuBuoiBxfbbsYtlfu8GmKe6vTfCh32UYSGWxklkJWjPX54wFMvD\nQF+luHC/2aQ3/D6VXjIS6JbzvA/KgjWclg/seR7ag36hZBT769rzksWfr+sCAwEA\nAQKCAgBmZ1W0si1KN5vsRftfjle5xi4E+qmWzjqFAZllsGPj7+veAxbn8laDoA1j\nO+BmVnqQfalISN498lbfNi3wIv2JRNONZRIDoesspWNEpRb+YBJT7it++3ukJbj+\n3y9XFAVXWlto7oY3Y0aJKauAE+/KK2CueYqOyvHFA0Gz+HG9zZfgGuATyR76CcTR\nUkM/MlBKao0JMHRmCA7Y6MJJkOAF4eXdiaMKZufK4vopQfi0p4re71gn1cmDYBa8\nKhDSRvz9Z6xQ/pGqGeCYHQACykXi8ZUM6sqJPlF4LedCTwbdaZwiNolu5/hJc/lk\ncLfKPSl0id2KZ6UW4PqPmGx00NXFP/XcCxzzht8ejrI1GY9LXR6fKpmoYZvUoXba\nSK58l+OcAaxJ7JoTCvH2adas5mhNGyHTTghceNlFPuT+LC7nNq6rJD0QLouDQMr5\n0my2lJtDiafa+Z3aGt759vkTT7k4wnfWNkjZJDIVf6UkAoMFtN5nOgR36OaDLegA\n7udascC3hKRUi2BIlc713hl2dlcPVMcCQArpvbwgwPFXiZO9PW+Qc7IWogHqWNWY\nMs9JsDcAE5Q5PRlAA8QSveSyl3QNJpeHT9PVx159a28E8xEWCs9nfpI/jXfYxFnr\ndfS7gn8XW1WNUJvtHsKIhdSRD/4ks6VRPm6KMskR+j+zpTbmcQKCAQEA3CvDiT/E\noD2VK9rE0KNDZBljED2p7IVE+zED5olGPUGC3F+WiEl9ldd6DKL6K0Xv/zAEv7Nt\nhHJ4m3B8siOQf2wzrX6JTvqDhBnrYjsD3VU7Zpys4ZjMOAp/aIM124ZRDECe2do3\nyzfV+oR0qw9KmyywjMwPa/8LL9d+kwYSQX6Y2hy+5TquDghKCmQzBw0iCDlmWfNP\njqfztSc1oBPcij+X98h3EI3Ai7R+hlolWlowXy0qBY8qCWegbguRDFkDhTXDCPwW\nRMiQobI3xWfhZybSohx42/HUYMi5Uis++CV3XeE/aRdLw/O3gHTz5n9Z3v0i0Xnd\nKIWxpCKzLzLAVwKCAQEAxTlZHVlNaVz8fsSajAyq3n4LnOxGEwhYspzY7U2tHnbr\nU1QXTlvGN97u9hMdHgvvPu7OULfeJM0EPNBdQC2B2Y2vkAZBcdw2cgXdzVksv+gO\n//ryo37xBZXY46prGyPZCrfrrBXHNOHlxY1AklQUu8PnNKU+Z02hirMtY6pm/WyI\n2fbUJRqQu3nTMiuqFeee+5vaKbWXPRWKjpF/KZxoA4YSymGhG+fVIJVKxWjz1ns/\n0Kkx/a4D3xWZO+vY9LE24PZzygUfr3/ZsCe8N+UpvZ60h7eJT9DJB1ETgqPFL8zr\nEhGxoNDLRpm0b1JELAuclCHuHdqQ/uTJB2DjSFpAjQKCAQEAxmNU3R4toan7+Tk2\ncT07oz3Q6rh1nd70KlefSSLWvKmELeif7owx8kvn+Oz9+PIa8FmnXcli3J59GKsC\nYU30jSzFYAaN2TGYQfdNBwVgVRbQ4IQ6r0kMc07aQSVB6V4dN6oeuPSNo7rbP9IM\ngnrT4gEh0KyrFMgKn4BQ2E/3MTbOqnKOfGUkoxZLCRQCes8VpE18cX7xZ/zkd44u\nHuDmr1fgKnBjAPKJ1hi8jXk7ATAVOB2tKLc4zKKoh6A6geLPbj/kTvs/YZlL4beB\n04noLBdqYpK/QIimstMLUgQPyG+SIHCvv5UzOw0ng0Ne5opIQ8rajeB+LF5TlC+E\nP/o+HwKCAQAurZcI2jT3JfngqvmFAg6C4EQxXL5tDMGpbHPvHj5GApFJxJJLim8M\nlCfsd7Ohg+OY+n48HnhmL1u8ZPhdEygzbFRL+x8MKrl8HSVUz7FGrk62iRdaWNYE\no2WU5KW6464f2k3eCb1/J6PxMLBCscHCeuhCzoVJf9cm86dfeloryr9NDx1Attvg\nc0HoEuuLialYFZf53S+xVmLXwVneaFU52EakPZ0a9LC9qHfs5x0m+z6sTQ824jOq\nXftJclWD/FlnvwzCmJnaOKE2DwF+HS/W4DQMFwVZramWoLrEZaxq1s4gFa37yM8D\no6dP3aGi5xClAq7PxAYjPdTSeTzxx+KVAoIBAQDGwk1/sJW99Oif+7RXvV99l+BL\n1R0BI1Dgc+aXkXSX4OeWJdLdiGLztrJ/lEzesKEdVHmG+wamexaxWzYgUeKklcAA\nIPrEawh3qB9gmlWei4BrK+e0cGjPZwq5bQi7gkpsMdxlHYkCmO12DzZ7/4CaGqET\n+Az0Xa7wjlRbSv62HvKbCm1yMizs8l9k3E8vMo9vU1soyEvR3r/aHzo7KyiXJaio\nioppLcx/FVQCkaFQ1/H4dBZCSxviJxQmnOWlTkJT1mH44GLQnv21UsEWUrpz13VK\n8Dp0zWwNtSKoEQ6YJYl1Nwt04OhUrxG5fStSOpRiQ2r8bUAM0d4qDSjV92Yf\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "tests/client_certs/password_protected/client.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-128-CBC,93DA845817852FB576163AA829C232E9\n\nVauvxiyC0lQbLJFoEGlnIIZO2/+b66DjTwSccqSdVg+Zaxvbc0jeVhS43SQ01ft0\nhB/oISgJB/1I/oKbGwx07T9j78Q8G9AxQV6hzvozK5Etjew4RrvV4DYyOSzwZNQr\nqB9S0qhBKyemA2vx4aH/8nazHh+zrRD3y0oMbuCHLxSGuqncNXIKCTYgMb8NUucJ\nfEArYHijZ0iotoOEpP31JOUPCpKhEewQxzNK0HLws0lv6nl6fmBlkdi603qmsG5U\nuinuiGodrh9SpCUc/A4OhVWKwoiQSxGnz+SiNaXyUByf9CR8RLPWqi5pTGHC8xrJ\nuHI6Cw8ZfrJ2clYtuCWv6g6c4F7sz6eAJHqCZNnU32kKu3uH/9E/7Z8uH7JOVyFa\n9DlBHCWHdyaHs8mY+/pDcxeMyWeC837sBelIBF1iEwU/sMw43HipZBNhrekMLAkx\ny5HRYQDstTvk1Nvj8fKysYuhGCiF/V6PWYo5RaQszZLhS+uyFEBwa0ojYNZh4LyB\n5uIdBaqtL9FD4RXqTYfN96eEyoYaUUY5KXqQMZkuZpotGYmH9OGMTVCgR7eU0a62\ndgbQw4UCQd4YTNx1PyboH72oIi+Rqp2LEYEQSHP/dIUtBiA/kmWhgapZVGvfJ+fF\nu9MPgPUDvH3oLVm4Mr+biLX/oUQVEup85q8++E2csDe2HoC4JdmJ0D9rZM2OqpYV\nYZAPcPhx2pYnK5d6RvMFwtLPNfHxgYQXMVg6BFtu5GCxxqr+dhF7TGrN5s6AKC8U\nbkVQIXwO8bYVTLj2Sb44fe+Xl1X/09yHnkZC0u/Kb2KvUm7Gnltn3tUmj7fGI0I6\naI6G3T1xc0jz9WhjdnM3uDYYI66GpgRgv81n7IkfRjclNArW4OStf30K4pXXjGeP\nvgopPJ1yNpaM4QNbx3cqzP0eBy+Ss7aCXca4I3BzjXtuo9ZcEzGb+1FkS7ASEdex\ncAroJOmm9KJ+3KOxsVs5fxXtQqzzeD8cdZeGV0eckJNfjWSBH2zyhaxwdlCvG1I9\ndTvdd6q31FjlnUq9SvGEkfoy4myIUtt4DJQ4lSktvKQv9qepUjoX0k3xipgSmiPO\nyxE+VdJdJ9/tDUf3psD01XLIss7hOX9aED3svN3uXB2ZVCSH6e2l4IrBMirdKNwR\nfB4Yrul0qt9knmn11p2aWav055hb1Il5Tm8/WnaXkgtr20zP4RgR7P19mSjTBxUm\n7iUIiWqU43Sx2LWsYpg7Lbj5XGLcvxv5WjYsE4Km0ltZCLKzMHfQ76qv4ZOQkHcR\n9UevRmzU45095eASztedrYyxDNwU6YSdUcOYTP6383G9azbStlQY+w2Em++UoNoH\n3eYj6KHKx+hkZOdc8PLaLg2f98jOiADpKYJTGnkKoLjTCfr9nzBeNxwRCQ4F4vO/\n+tuRo3i1ODpJQbbZys9Mz+9PSwBH31UAib0+v0GYLDJN2rJcyGal/0DH5zON9Ogi\n5bZQ9oS91p9K5hUAnHpd3zOzeX1lCoZnmtOI8wah79SVSpK1xoE6BAxAHfRiYiS3\n1tDmkThJBOGXmkpLjtgNW3MqYKBnO3tRzrDDCjTKi5jFX/SD2FPpExOyA2+I0lrr\na9b+Sjbl1Z7B1yZmmTGMKB7prwK00LaF6yqKOhE+bx1yJAaWrbdPCD6vDmbq5YV6\n87woIiA16Q2I1x77/Kg3TDO9LMDiwI5BFyjR+4Q5SvufIaxtsmTBuaBuPif+f4DT\nMPQcfk5ozQIKY4qiSqMAOXAf2t+/UQROjgYvayRz0fOv2rV0vS4i9ELj/8Dn65Dq\n7aQzLwM0psToGIVyzAV+hF3jeQP+Xu7VjtSxTJ+ajz7PeIXeBH/mwJKMk7hpRwGj\n4fZ92S00Iat2kA6wn55u6EGewgcaQrN2zr75a9gvXQwMDmsjszq2uWWxxJg6pAPZ\nrNqhM9tJ2UAJ1lLZzUDfhK4wU4pGWIhT+BmdDgJ40hI4b1WEmKSTxsj8AYNcVDRf\ni2Ox1QhZQX9bH5kTOX373/6cALFR5DcU8qh2FJtf+3uiZHNloEeID//H2Gdoxz0Y\n5CC/VDiIa4Gj4D+ATsLMgTDt4eUOinMeC1H6w+QBd9UvceqEvrgu+1WB8UCK/Hm/\n7fZ0srsGg/WRqdSuO8/7998PEHgP8+wnTbxi9Y3EEbkaKUL6esJfeOjBibuGPyaf\n2Y9QLcpVKaD7pmVeb97qExZZjEiID6QYmFUO8j0koS2fki0l+z8XEZ3JLZKa9XS+\nuiMPQKg41j+9ZrGmwPNj7brjwA0cdSb4CLgxg4FwuwB660XaXpW3aRsiRryi0YcM\nhn2l6b4JgBz8gUkFiTXQ8wRvAKDC1hUkUysqCAC+Yg3cWxlDZVeSeqVGr5jhHgN1\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "tests/client_certs/password_protected/client.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEazCCAtOgAwIBAgIUIWojJySwCenyvcJrZY1aKOMQfTUwDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjAzMDExMzM5MzRaFw00OTA3\nMTcxMzM5MzRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggGiMA0GCSqGSIb3DQEB\nAQUAA4IBjwAwggGKAoIBgQC0+8GOIhknLgLmWEQGgdDJffntbDfGdtG7JFUFfvuN\nsibTHL4RPNhe+UrT/LR+JBfIfmdeb+NlmKzsTeR0+8PkX5ZjXMShf5icghVukK7G\nOoQS7olQqlGzpIX76VqktVr4tFNXmMJeBO0NIaXHm0ASsoz3fIfDx9ttJETPs6lm\nWv/PUPemvtUgcbAb+kjz9QqcUV8B1xcCvQma6NSpxcmJHqAuI6HkdbDzyedKuuSi\nM6yNFjh3EJjsufReQgkcfDsqh+RA3zQoIyPXLNqjzGD71z24jUtvIxb5ZNEtv0xp\n5zCOCavuRNNyKGvvnlIeyup7bMe0QIds566miG49osVpPVvVmg+q+w2YYAE+7svb\nnJp7NYn2tryRqsmvnASLVQD6T9wTWUa8w/tT1+ltnhfqbwDcVACzsw/U4FFwcfWw\n5BnUcJacoDkj/3TCqgkA8XFe1/DVU8XCcsvEaoLzwHhHu2+QDpqal8rNouyTpFGA\n/wioVBQGpksPZjl8lumsz3kCAwEAAaNTMFEwHQYDVR0OBBYEFGJhl1BPOXCVqRo3\nU/ruuedvlDqsMB8GA1UdIwQYMBaAFGJhl1BPOXCVqRo3U/ruuedvlDqsMA8GA1Ud\nEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggGBAE9NtrE5qSHlK9DXdH6bPW6z\nGO9uWBl3rJVtqVvPoH8RxJG/jtaD/Pnc3MkIxMoliCleNK6BdYVGV9u8x9W1jQo8\nH+mnH3/Ise8ZY1zpblZJF0z9xs5sWW7qO8U06GmJWRSPn3LKEZjLsNmThhUW09wN\n8EZX914zCWtzCrUTNg8Au1Dz9zA9ScfpCVPhKORTCnrpoTL6iXsPxmCx+5awmNLE\nuh9kw4NScEyq33RTPosMpwSMlXGRuASltx/J7Rn0DNR0r1p0XzDS4CG1iDwXHlEF\nMwsOvSahNyz5RInrU3cgN70tafoRIHScLYycnRml8dydxrDoFgdJk5sI4zgq24Sg\nTktTq9ShrT4yQX+lrGS6eZQK/YZEBPD7BdTLYp3vlfYQMJ4Jz9SyQ8b9/9jIFVFS\ndFfWiCqEuhTvGfptAzYX+K9OaegZnIk3X7R6O+YQ3oHCbLbnV3bpKlgNnOKBwa2X\nkJ5GRp+rZOJ97yjrspKjpR5tNCiJnp7NnnA5VA6mfw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "import socket\n\nimport pytest\nfrom pytest_httpbin import certs\nfrom pytest_httpbin.serve import Server as PyTestHttpBinServer\n\nfrom .utils import (  # noqa\n    HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN,\n    HTTPBIN_WITH_CHUNKED_SUPPORT,\n    REMOTE_HTTPBIN_DOMAIN,\n    IS_PYOPENSSL,\n    mock_env\n)\nfrom .utils.plugins_cli import (  # noqa\n    broken_plugin,\n    dummy_plugin,\n    dummy_plugins,\n    httpie_plugins,\n    httpie_plugins_success,\n    interface,\n)\nfrom .utils.http_server import http_server, localhost_http_server  # noqa\n\n\n# Patch to support `url = str(server)` in addition to `url = server + '/foo'`.\nPyTestHttpBinServer.__str__ = lambda self: self.url\n\n\n@pytest.fixture(scope='function', autouse=True)\ndef httpbin_add_ca_bundle(monkeypatch):\n    \"\"\"\n    Make pytest-httpbin's CA trusted by default.\n\n    (Same as `httpbin_ca_bundle`, just auto-used.).\n\n    \"\"\"\n    monkeypatch.setenv('REQUESTS_CA_BUNDLE', certs.where())\n\n\n@pytest.fixture(scope='function')\ndef httpbin_secure_untrusted(monkeypatch, httpbin_secure):\n    \"\"\"\n    Like the `httpbin_secure` fixture, but without the\n    make-CA-trusted-by-default.\n\n    \"\"\"\n    monkeypatch.delenv('REQUESTS_CA_BUNDLE')\n    return httpbin_secure\n\n\n@pytest.fixture(scope='session')\ndef _httpbin_with_chunked_support_available():\n    try:\n        socket.gethostbyname(HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN)\n        return True\n    except OSError:\n        return False\n\n\n@pytest.fixture(scope='function')\ndef httpbin_with_chunked_support(_httpbin_with_chunked_support_available):\n    if _httpbin_with_chunked_support_available:\n        return HTTPBIN_WITH_CHUNKED_SUPPORT\n    pytest.skip(f'{HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN} not resolvable')\n\n\n@pytest.fixture(scope='session')\ndef _remote_httpbin_available():\n    try:\n        socket.gethostbyname(REMOTE_HTTPBIN_DOMAIN)\n        return True\n    except OSError:\n        return False\n\n\n@pytest.fixture\ndef remote_httpbin(_remote_httpbin_available):\n    if _remote_httpbin_available:\n        return 'http://' + REMOTE_HTTPBIN_DOMAIN\n    pytest.skip(f'{REMOTE_HTTPBIN_DOMAIN} not resolvable')\n\n\n@pytest.fixture(autouse=True, scope='session')\ndef pyopenssl_inject():\n    \"\"\"\n    Injects `pyOpenSSL` module to make sure `requests` will use it.\n    <https://github.com/psf/requests/pull/5443#issuecomment-645740394>\n    \"\"\"\n    if IS_PYOPENSSL:\n        try:\n            import urllib3.contrib.pyopenssl\n            urllib3.contrib.pyopenssl.inject_into_urllib3()\n        except ModuleNotFoundError:\n            pytest.fail('Missing \"pyopenssl\" module.')\n\n    yield\n"
  },
  {
    "path": "tests/fixtures/.editorconfig",
    "content": "# https://editorconfig.org\n\n[{*.txt, *.json}]\ntrim_trailing_whitespace = false\ninsert_final_newline = false\n\n"
  },
  {
    "path": "tests/fixtures/__init__.py",
    "content": "\"\"\"Test data\"\"\"\nimport json\nfrom pathlib import Path\nfrom typing import Optional, Dict, Any\n\nimport httpie\nfrom httpie.encoding import UTF8\nfrom httpie.output.formatters.xml import pretty_xml, parse_xml\n\n\ndef patharg(path):\n    \"\"\"\n    Back slashes need to be escaped in ITEM args,\n    even in Windows paths.\n\n    \"\"\"\n    return str(path).replace('\\\\', '\\\\\\\\\\\\')\n\n\nFIXTURES_ROOT = Path(__file__).parent\nFILE_PATH = FIXTURES_ROOT / 'test.txt'\nJSON_FILE_PATH = FIXTURES_ROOT / 'test.json'\nJSON_WITH_DUPE_KEYS_FILE_PATH = FIXTURES_ROOT / 'test_with_dupe_keys.json'\nBIN_FILE_PATH = FIXTURES_ROOT / 'test.bin'\n\nXML_FILES_PATH = FIXTURES_ROOT / 'xmldata'\nXML_FILES_VALID = list((XML_FILES_PATH / 'valid').glob('*_raw.xml'))\nXML_FILES_INVALID = list((XML_FILES_PATH / 'invalid').glob('*.xml'))\n\nSESSION_FILES_PATH = FIXTURES_ROOT / 'session_data'\nSESSION_FILES_OLD = sorted((SESSION_FILES_PATH / 'old').glob('*.json'))\nSESSION_FILES_NEW = sorted((SESSION_FILES_PATH / 'new').glob('*.json'))\n\nSESSION_VARIABLES = {\n    '__version__': httpie.__version__,\n    '__host__': 'null',\n}\n\nFILE_PATH_ARG = patharg(FILE_PATH)\nBIN_FILE_PATH_ARG = patharg(BIN_FILE_PATH)\nJSON_FILE_PATH_ARG = patharg(JSON_FILE_PATH)\n\n# Strip because we don't want new lines in the data so that we can\n# easily count occurrences also when embedded in JSON (where the new\n# line would be escaped).\nFILE_CONTENT = FILE_PATH.read_text(encoding=UTF8).strip()\n\nASCII_FILE_CONTENT = \"random text\" * 10\n\n\nJSON_FILE_CONTENT = JSON_FILE_PATH.read_text(encoding=UTF8)\nBIN_FILE_CONTENT = BIN_FILE_PATH.read_bytes()\nUNICODE = FILE_CONTENT\nXML_DATA_RAW = '<?xml version=\"1.0\" encoding=\"utf-8\"?><root><e>text</e></root>'\nXML_DATA_FORMATTED = pretty_xml(parse_xml(XML_DATA_RAW))\n\n\ndef read_session_file(session_file: Path, *, extra_variables: Optional[Dict[str, str]] = None) -> Any:\n    with open(session_file) as stream:\n        data = stream.read()\n\n    session_vars = {**SESSION_VARIABLES, **(extra_variables or {})}\n    for variable, value in session_vars.items():\n        data = data.replace(variable, value)\n\n    return json.loads(data)\n"
  },
  {
    "path": "tests/fixtures/session_data/new/cookies_dict.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"__version__\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [\n        {\n            \"domain\": __host__,\n            \"expires\": null,\n            \"name\": \"baz\",\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"quux\"\n        },\n        {\n            \"domain\": __host__,\n            \"expires\": null,\n            \"name\": \"foo\",\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"bar\"\n        }\n    ],\n    \"headers\": []\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/new/cookies_dict_dev_version.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"__version__\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [\n        {\n            \"domain\": __host__,\n            \"expires\": null,\n            \"name\": \"baz\",\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"quux\"\n        },\n        {\n            \"domain\": __host__,\n            \"expires\": null,\n            \"name\": \"foo\",\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"bar\"\n        }\n    ],\n    \"headers\": []\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/new/cookies_dict_with_extras.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"__version__\"\n    },\n    \"auth\": {\n        \"raw_auth\": \"foo:bar\",\n        \"type\": \"basic\"\n    },\n    \"cookies\": [\n        {\n            \"domain\": __host__,\n            \"expires\": null,\n            \"name\": \"baz\",\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"quux\"\n        },\n        {\n            \"domain\": __host__,\n            \"expires\": null,\n            \"name\": \"foo\",\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"bar\"\n        }\n    ],\n    \"headers\": [\n        {\n            \"name\": \"X-Data\",\n            \"value\": \"value\"\n        },\n        {\n            \"name\": \"X-Foo\",\n            \"value\": \"bar\"\n        }\n    ]\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/new/empty_cookies_dict.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"__version__\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [],\n    \"headers\": []\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/new/empty_cookies_list.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"__version__\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [],\n    \"headers\": []\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/new/empty_headers_dict.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"__version__\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [],\n    \"headers\": []\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/new/empty_headers_list.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"__version__\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [],\n    \"headers\": []\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/new/headers_cookies_dict_mixed.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"__version__\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [\n        {\n            \"domain\": __host__,\n            \"expires\": null,\n            \"name\": \"baz\",\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"quux\"\n        },\n        {\n            \"domain\": __host__,\n            \"expires\": null,\n            \"name\": \"foo\",\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"bar\"\n        }\n    ],\n    \"headers\": [\n        {\n            \"name\": \"X-Data\",\n            \"value\": \"value\"\n        },\n        {\n            \"name\": \"X-Foo\",\n            \"value\": \"bar\"\n        }\n    ]\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/new/headers_dict.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"__version__\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [],\n    \"headers\": [\n        {\n            \"name\": \"foo\",\n            \"value\": \"bar\"\n        },\n        {\n            \"name\": \"baz\",\n            \"value\": \"quux\"\n        }\n    ]\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/new/headers_dict_extras.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"__version__\"\n    },\n    \"auth\": {\n        \"raw_auth\": \"foo:bar\",\n        \"type\": \"basic\"\n    },\n    \"cookies\": [\n        {\n            \"domain\": null,\n            \"expires\": null,\n            \"name\": \"baz\",\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"quux\"\n        },\n        {\n            \"domain\": null,\n            \"expires\": null,\n            \"name\": \"foo\",\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"bar\"\n        }\n    ],\n    \"headers\": [\n        {\n            \"name\": \"X-Data\",\n            \"value\": \"value\"\n        },\n        {\n            \"name\": \"X-Foo\",\n            \"value\": \"bar\"\n        }\n    ]\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/new/headers_list.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"3.2.0\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [],\n    \"headers\": [\n        {\n            \"name\": \"X-Data\",\n            \"value\": \"value\"\n        },\n        {\n            \"name\": \"X-Foo\",\n            \"value\": \"bar\"\n        }\n    ]\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/old/cookies_dict.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"3.0.2\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": {\n        \"baz\": {\n            \"expires\": null,\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"quux\"\n        },\n        \"foo\": {\n            \"expires\": null,\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"bar\"\n        }\n    },\n    \"headers\": []\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/old/cookies_dict_dev_version.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"2.7.0.dev0\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": {\n        \"baz\": {\n            \"expires\": null,\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"quux\"\n        },\n        \"foo\": {\n            \"expires\": null,\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"bar\"\n        }\n    },\n    \"headers\": []\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/old/cookies_dict_with_extras.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"3.0.2\"\n    },\n    \"auth\": {\n        \"raw_auth\": \"foo:bar\",\n        \"type\": \"basic\"\n    },\n    \"cookies\": {\n        \"baz\": {\n            \"expires\": null,\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"quux\"\n        },\n        \"foo\": {\n            \"expires\": null,\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"bar\"\n        }\n    },\n    \"headers\": [\n        {\n            \"name\": \"X-Data\",\n            \"value\": \"value\"\n        },\n        {\n            \"name\": \"X-Foo\",\n            \"value\": \"bar\"\n        }\n    ]\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/old/empty_cookies_dict.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"3.0.2\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": {},\n    \"headers\": []\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/old/empty_cookies_list.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"3.0.2\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [],\n    \"headers\": []\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/old/empty_headers_dict.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"3.0.2\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [],\n    \"headers\": {}\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/old/empty_headers_list.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"3.0.2\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [],\n    \"headers\": []\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/old/headers_cookies_dict_mixed.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"3.0.2\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": {\n        \"baz\": {\n            \"expires\": null,\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"quux\"\n        },\n        \"foo\": {\n            \"expires\": null,\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"bar\"\n        }\n    },\n    \"headers\": {\n        \"X-Data\": \"value\",\n        \"X-Foo\": \"bar\"\n    }\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/old/headers_dict.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"3.1.0\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [],\n    \"headers\": {\n        \"foo\": \"bar\",\n        \"baz\": \"quux\"\n    }\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/old/headers_dict_extras.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"3.1.0\"\n    },\n    \"auth\": {\n        \"raw_auth\": \"foo:bar\",\n        \"type\": \"basic\"\n    },\n    \"cookies\": [\n        {\n            \"domain\": null,\n            \"name\": \"baz\",\n            \"expires\": null,\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"quux\"\n        },\n        {\n            \"domain\": null,\n            \"name\": \"foo\",\n            \"expires\": null,\n            \"path\": \"/\",\n            \"secure\": false,\n            \"value\": \"bar\"\n        }\n    ],\n    \"headers\": {\n        \"X-Data\": \"value\",\n        \"X-Foo\": \"bar\"\n    }\n}\n"
  },
  {
    "path": "tests/fixtures/session_data/old/headers_list.json",
    "content": "{\n    \"__meta__\": {\n        \"about\": \"HTTPie session file\",\n        \"help\": \"https://httpie.io/docs#sessions\",\n        \"httpie\": \"3.2.0\"\n    },\n    \"auth\": {\n        \"password\": null,\n        \"type\": null,\n        \"username\": null\n    },\n    \"cookies\": [],\n    \"headers\": [\n        {\n            \"name\": \"X-Data\",\n            \"value\": \"value\"\n        },\n        {\n            \"name\": \"X-Foo\",\n            \"value\": \"bar\"\n        }\n    ]\n}\n"
  },
  {
    "path": "tests/fixtures/test.json",
    "content": "{\n    \"name\": \"Jakub Roztočil\",\n    \"unicode\": \"χρυσαφὶ 太陽 เลิศ ♜♞♝♛♚♝♞♜ оживлённым तान्यहानि 有朋\"\n}"
  },
  {
    "path": "tests/fixtures/test.txt",
    "content": "[one line of UTF8-encoded unicode text] χρυσαφὶ 太陽 เลิศ ♜♞♝♛♚♝♞♜ оживлённым तान्यहानि 有朋 ஸ்றீனிவாஸ ٱلرَّحْمـَبنِ"
  },
  {
    "path": "tests/fixtures/test_with_dupe_keys.json",
    "content": "{\"key\":15,\"key\":15,\"key\":3,\"key\":7}\n"
  },
  {
    "path": "tests/fixtures/xmldata/invalid/cyclic.xml",
    "content": "<!DOCTYPE xmlbomb [\n<!ENTITY a \"123 &b;\" >\n<!ENTITY b \"&a;\">\n]>\n<bomb>&a;</bomb>\n"
  },
  {
    "path": "tests/fixtures/xmldata/invalid/external.xml",
    "content": "<!DOCTYPE external [\n<!ENTITY ee SYSTEM \"http://www.w3schools.com/xml/note.xml\">\n]>\n<root>&ee;</root>\n"
  },
  {
    "path": "tests/fixtures/xmldata/invalid/external_file.xml",
    "content": "<!DOCTYPE external [\n<!ENTITY ee SYSTEM \"file:///PATH/TO/xmltestdata/simple.xml\">\n]>\n<root>&ee;</root>\n\n"
  },
  {
    "path": "tests/fixtures/xmldata/invalid/not-xml.xml",
    "content": "some string"
  },
  {
    "path": "tests/fixtures/xmldata/invalid/quadratic.xml",
    "content": "<!DOCTYPE bomb [\n<!ENTITY a \"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\" >\n]>\n<bomb>&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;</bomb>\n"
  },
  {
    "path": "tests/fixtures/xmldata/invalid/xalan_exec.xsl",
    "content": "<!-- Tested with xalan-j_2_7_1-bin.zip, Xerces-J-bin.2.11.0.tar.gz on\n     OpenJDK 1.7.0_15\n\n    $ LC_ALL=C java -cp xalan.jar:serializer.jar:xercesImpl.jar:xml-apis.jar \\\n      org.apache.xalan.xslt.Process -in simple.xml -xsl xalan_exec.xsl\n-->\n<xsl:stylesheet version=\"1.0\"\n     xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n     xmlns:rt=\"http://xml.apache.org/xalan/java/java.lang.Runtime\"\n     xmlns:ob=\"http://xml.apache.org/xalan/java/java.lang.Object\"\n     exclude-result-prefixes=\"rt ob\">\n  <xsl:template match=\"/\">\n  <xsl:variable name=\"runtimeObject\" select=\"rt:getRuntime()\"/>\n  <xsl:variable name=\"command\"\n     select=\"rt:exec($runtimeObject, &apos;/usr/bin/notify-send SomethingBadHappensHere&apos;)\"/>\n  <xsl:variable name=\"commandAsString\" select=\"ob:toString($command)\"/>\n  <xsl:value-of select=\"$commandAsString\"/>\n  </xsl:template>\n</xsl:stylesheet>\n\n"
  },
  {
    "path": "tests/fixtures/xmldata/invalid/xalan_write.xsl",
    "content": "<!-- Tested with xalan-j_2_7_1-bin.zip, Xerces-J-bin.2.11.0.tar.gz on\n     OpenJDK 1.7.0_15\n\n    $ LC_ALL=C java -cp xalan.jar:serializer.jar:xercesImpl.jar:xml-apis.jar \\\n      org.apache.xalan.xslt.Process -in simple.xml -xsl xalan_write.xsl\n-->\n<xsl:stylesheet version=\"1.0\"\n    xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n    xmlns:redirect=\"http://xml.apache.org/xalan/redirect\"\n    extension-element-prefixes=\"redirect\">\n  <xsl:output omit-xml-declaration=\"yes\" indent=\"yes\"/>\n  <xsl:template match=\"/\">\n    <redirect:write file=\"xalan_redirect.txt\" method=\"text\">\n      <xsl:text>Something bad happens here!&#13;</xsl:text>\n    </redirect:write>\n  </xsl:template>\n</xsl:stylesheet>\n\n"
  },
  {
    "path": "tests/fixtures/xmldata/invalid/xmlbomb.xml",
    "content": "<!DOCTYPE xmlbomb [\n<!ENTITY a \"1234567890\" >\n<!ENTITY b \"&a;&a;&a;&a;&a;&a;&a;&a;\">\n<!ENTITY c \"&b;&b;&b;&b;&b;&b;&b;&b;\">\n<!ENTITY d \"&c;&c;&c;&c;&c;&c;&c;&c;\">\n]>\n<bomb>&c;</bomb>\n"
  },
  {
    "path": "tests/fixtures/xmldata/invalid/xmlbomb2.xml",
    "content": "<!DOCTYPE xmlbomb [\n<!ENTITY a \"1234567890\">\n]>\n<root>text<bomb>&a;</bomb><tag/></root>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/custom-header.xml",
    "content": "<?xml                version=\"1.0\" encoding=\"utf-8\"?><!-- comment -->\n<root><element key=\"value\">text</element><element>text</element>tail<empty-element/></root>\n<!-- comment -->\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/custom-header_formatted.xml",
    "content": "<?xml                version=\"1.0\" encoding=\"utf-8\"?>\n<!-- comment -->\n<root>\n  <element key=\"value\">text</element>\n  <element>text</element>\n  tail\n  <empty-element/>\n</root>\n\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/dtd_formatted.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html\n  PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN'\n  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n<html>\n  <head/>\n  <body>text</body>\n</html>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/dtd_raw.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html><head/><body>text</body></html>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/simple-ns_formatted.xml",
    "content": "<?pi data?>\n<!-- comment -->\n<root xmlns=\"namespace\">\n  <element key=\"value\">text</element>\n  <element>text</element>\n  tail\n  <empty-element/>\n</root>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/simple-ns_raw.xml",
    "content": "<?pi data?><!-- comment --><root xmlns='namespace'><element key='value'>text</element><element>text</element>tail<empty-element/></root>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/simple-single-tag_formatted.xml",
    "content": "<a/>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/simple-single-tag_raw.xml",
    "content": "<a></a>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/simple-standalone-no_formatted.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE s1>\n<s1>........</s1>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/simple-standalone-no_raw.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><!DOCTYPE s1>\n<s1>........</s1>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/simple-standalone-yes_formatted.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<!DOCTYPE s1>\n<s1>........</s1>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/simple-standalone-yes_raw.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><!DOCTYPE s1>\n<s1>........</s1>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/simple_formatted.xml",
    "content": "<!-- comment -->\n<root>\n  <element key=\"value\">text</element>\n  <element>text</element>\n  tail\n  <empty-element/>\n</root>\n"
  },
  {
    "path": "tests/fixtures/xmldata/valid/simple_raw.xml",
    "content": "<!-- comment --><root><element key='value'>text</element><element>text</element>tail<empty-element/></root>\n"
  },
  {
    "path": "tests/fixtures/xmldata/xhtml/xhtml_formatted.xml",
    "content": "<!DOCTYPE html\n  PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'\n  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n    <title>XHTML 1.0 Strict Example</title>\n    <script type=\"text/javascript\">\n //\n<![CDATA[\n function loadpdf() {\n    document.getElementById(\"pdf-object\").src=\"http://www.w3.org/TR/xhtml1/xhtml1.pdf\";\n }\n //]]>      \n    </script>\n  </head>\n  <body onload=\"loadpdf()\">\n    <p>\n      This is an example of an\n      <abbr title=\"Extensible HyperText Markup Language\">XHTML</abbr>\n       1.0 Strict document.\n      <br/>\n      <img id=\"validation-icon\" src=\"http://www.w3.org/Icons/valid-xhtml10\" alt=\"Valid XHTML 1.0 Strict\"/>\n      <br/>\n      <object id=\"pdf-object\" name=\"pdf-object\" type=\"application/pdf\" data=\"http://www.w3.org/TR/xhtml1/xhtml1.pdf\" width=\"100%\" height=\"500\">\n </object>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/fixtures/xmldata/xhtml/xhtml_formatted_python_less_than_3.8.xml",
    "content": "<!DOCTYPE html\n  PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'\n  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n<html lang=\"en\" xml:lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n  <head>\n    <meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\"/>\n    <title>XHTML 1.0 Strict Example</title>\n    <script type=\"text/javascript\">\n //\n<![CDATA[\n function loadpdf() {\n    document.getElementById(\"pdf-object\").src=\"http://www.w3.org/TR/xhtml1/xhtml1.pdf\";\n }\n //]]>      \n    </script>\n  </head>\n  <body onload=\"loadpdf()\">\n    <p>\n      This is an example of an\n      <abbr title=\"Extensible HyperText Markup Language\">XHTML</abbr>\n       1.0 Strict document.\n      <br/>\n      <img alt=\"Valid XHTML 1.0 Strict\" id=\"validation-icon\" src=\"http://www.w3.org/Icons/valid-xhtml10\"/>\n      <br/>\n      <object data=\"http://www.w3.org/TR/xhtml1/xhtml1.pdf\" height=\"500\" id=\"pdf-object\" name=\"pdf-object\" type=\"application/pdf\" width=\"100%\">\n </object>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/fixtures/xmldata/xhtml/xhtml_raw.xml",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n <head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n <title>XHTML 1.0 Strict Example</title>\n <script type=\"text/javascript\">\n //<![CDATA[\n function loadpdf() {\n    document.getElementById(\"pdf-object\").src=\"http://www.w3.org/TR/xhtml1/xhtml1.pdf\";\n }\n //]]>\n </script>\n </head>\n <body onload=\"loadpdf()\">\n <p>This is an example of an\n <abbr title=\"Extensible HyperText Markup Language\">XHTML</abbr> 1.0 Strict document.<br />\n <img id=\"validation-icon\"\n    src=\"http://www.w3.org/Icons/valid-xhtml10\"\n    alt=\"Valid XHTML 1.0 Strict\"/><br />\n <object id=\"pdf-object\"\n    name=\"pdf-object\"\n    type=\"application/pdf\"\n    data=\"http://www.w3.org/TR/xhtml1/xhtml1.pdf\"\n    width=\"100%\"\n    height=\"500\">\n </object>\n </p>\n </body>\n</html>\n"
  },
  {
    "path": "tests/test_auth.py",
    "content": "\"\"\"HTTP authentication-related tests.\"\"\"\nfrom unittest import mock\nimport pytest\n\nfrom httpie.plugins.builtin import HTTPBasicAuth\nfrom httpie.status import ExitStatus\nfrom httpie.utils import ExplicitNullAuth\nfrom .utils import http, add_auth, HTTP_OK, MockEnvironment\nimport httpie.cli.constants\nimport httpie.cli.definition\n\n\ndef test_basic_auth(httpbin_both):\n    r = http('--auth=user:password',\n             'GET', httpbin_both + '/basic-auth/user/password')\n    assert HTTP_OK in r\n    assert r.json == {'authenticated': True, 'user': 'user'}\n\n\n@pytest.mark.parametrize('argument_name', ['--auth-type', '-A'])\ndef test_digest_auth(httpbin_both, argument_name):\n    r = http(argument_name + '=digest', '--auth=user:password',\n             'GET', httpbin_both.url + '/digest-auth/auth/user/password')\n    assert HTTP_OK in r\n    assert r.json == {'authenticated': True, 'user': 'user'}\n\n\n@pytest.mark.parametrize('token', [\n    'token_1',\n    'long_token' * 5,\n    'user:style',\n])\ndef test_bearer_auth(httpbin_both, token):\n    r = http('--auth-type', 'bearer', '--auth', token,\n             httpbin_both + '/bearer')\n\n    assert HTTP_OK in r\n    assert r.json == {'authenticated': True, 'token': token}\n\n\n@mock.patch('httpie.cli.argtypes.AuthCredentials._getpass',\n            new=lambda self, prompt: 'password')\ndef test_password_prompt(httpbin):\n    r = http('--auth', 'user',\n             'GET', httpbin + '/basic-auth/user/password')\n    assert HTTP_OK in r\n    assert r.json == {'authenticated': True, 'user': 'user'}\n\n\ndef test_credentials_in_url(httpbin_both):\n    url = add_auth(httpbin_both.url + '/basic-auth/user/password',\n                   auth='user:password')\n    r = http('GET', url)\n    assert HTTP_OK in r\n    assert r.json == {'authenticated': True, 'user': 'user'}\n\n\ndef test_credentials_in_url_auth_flag_has_priority(httpbin_both):\n    \"\"\"When credentials are passed in URL and via -a at the same time,\n     then the ones from -a are used.\"\"\"\n    url = add_auth(httpbin_both.url + '/basic-auth/user/password',\n                   auth='user:wrong')\n    r = http('--auth=user:password', 'GET', url)\n    assert HTTP_OK in r\n    assert r.json == {'authenticated': True, 'user': 'user'}\n\n\n@pytest.mark.parametrize('url', [\n    'username@example.org',\n    'username:@example.org',\n])\ndef test_only_username_in_url(url):\n    \"\"\"\n    https://github.com/httpie/cli/issues/242\n\n    \"\"\"\n    args = httpie.cli.definition.parser.parse_args(args=[url], env=MockEnvironment())\n    assert args.auth\n    assert args.auth.username == 'username'\n    assert args.auth.password == ''\n\n\ndef test_missing_auth(httpbin):\n    r = http(\n        '--auth-type=basic',\n        'GET',\n        httpbin + '/basic-auth/user/password',\n        tolerate_error_exit_status=True\n    )\n    assert HTTP_OK not in r\n    assert '--auth required' in r.stderr\n\n\ndef test_netrc(httpbin_both):\n    # This one gets handled by requests (no --auth, --auth-type present),\n    # that’s why we patch inside `requests.sessions`.\n    with mock.patch('requests.sessions.get_netrc_auth') as get_netrc_auth:\n        get_netrc_auth.return_value = ('httpie', 'password')\n        r = http(httpbin_both + '/basic-auth/httpie/password')\n        assert get_netrc_auth.call_count == 1\n        assert HTTP_OK in r\n\n\ndef test_ignore_netrc(httpbin_both):\n    with mock.patch('httpie.cli.argparser.get_netrc_auth') as get_netrc_auth:\n        get_netrc_auth.return_value = ('httpie', 'password')\n        r = http('--ignore-netrc', httpbin_both + '/basic-auth/httpie/password')\n        assert get_netrc_auth.call_count == 0\n        assert 'HTTP/1.1 401 UNAUTHORIZED' in r\n\n\ndef test_ignore_netrc_together_with_auth():\n    args = httpie.cli.definition.parser.parse_args(\n        args=['--ignore-netrc', '--auth=username:password', 'example.org'],\n        env=MockEnvironment(),\n    )\n    assert isinstance(args.auth, HTTPBasicAuth)\n\n\ndef test_ignore_netrc_with_auth_type_resulting_in_missing_auth(httpbin):\n    with mock.patch('httpie.cli.argparser.get_netrc_auth') as get_netrc_auth:\n        get_netrc_auth.return_value = ('httpie', 'password')\n        r = http(\n            '--ignore-netrc',\n            '--auth-type=basic',\n            httpbin + '/basic-auth/httpie/password',\n            tolerate_error_exit_status=True,\n        )\n    assert get_netrc_auth.call_count == 0\n    assert r.exit_status == ExitStatus.ERROR\n    assert '--auth required' in r.stderr\n\n\n@pytest.mark.parametrize(\n    'auth_type, endpoint',\n    [\n        ('basic', '/basic-auth/httpie/password'),\n        ('digest', '/digest-auth/auth/httpie/password'),\n    ]\n)\ndef test_auth_plugin_netrc_parse(auth_type, endpoint, httpbin):\n    # Test\n    with mock.patch('httpie.cli.argparser.get_netrc_auth') as get_netrc_auth:\n        get_netrc_auth.return_value = ('httpie', 'password')\n        r = http('--auth-type', auth_type, httpbin + endpoint)\n    assert get_netrc_auth.call_count == 1\n    assert HTTP_OK in r\n\n\ndef test_ignore_netrc_null_auth():\n    args = httpie.cli.definition.parser.parse_args(\n        args=['--ignore-netrc', 'example.org'],\n        env=MockEnvironment(),\n    )\n    assert isinstance(args.auth, ExplicitNullAuth)\n"
  },
  {
    "path": "tests/test_auth_plugins.py",
    "content": "from unittest import mock\n\nfrom httpie.cli.constants import SEPARATOR_CREDENTIALS\nfrom httpie.plugins import AuthPlugin\nfrom httpie.plugins.registry import plugin_manager\nfrom .utils import http, HTTP_OK\n\n\n# TODO: run all these tests in session mode as well\n\nUSERNAME = 'user'\nPASSWORD = 'password'\n# Basic auth encoded `USERNAME` and `PASSWORD`\n# noinspection SpellCheckingInspection\nBASIC_AUTH_HEADER_VALUE = 'Basic dXNlcjpwYXNzd29yZA=='\nBASIC_AUTH_URL = f'/basic-auth/{USERNAME}/{PASSWORD}'\nAUTH_OK = {'authenticated': True, 'user': USERNAME}\n\n\ndef basic_auth(header=BASIC_AUTH_HEADER_VALUE):\n\n    def inner(r):\n        r.headers['Authorization'] = header\n        return r\n\n    return inner\n\n\ndef test_auth_plugin_parse_auth_false(httpbin):\n\n    class Plugin(AuthPlugin):\n        auth_type = 'test-parse-false'\n        auth_parse = False\n\n        def get_auth(self, username=None, password=None):\n            assert username is None\n            assert password is None\n            assert self.raw_auth == BASIC_AUTH_HEADER_VALUE\n            return basic_auth(self.raw_auth)\n\n    plugin_manager.register(Plugin)\n    try:\n        r = http(\n            httpbin + BASIC_AUTH_URL,\n            '--auth-type',\n            Plugin.auth_type,\n            '--auth',\n            BASIC_AUTH_HEADER_VALUE,\n        )\n        assert HTTP_OK in r\n        assert r.json == AUTH_OK\n    finally:\n        plugin_manager.unregister(Plugin)\n\n\ndef test_auth_plugin_require_auth_false(httpbin):\n\n    class Plugin(AuthPlugin):\n        auth_type = 'test-require-false'\n        auth_require = False\n\n        def get_auth(self, username=None, password=None):\n            assert self.raw_auth is None\n            assert username is None\n            assert password is None\n            return basic_auth()\n\n    plugin_manager.register(Plugin)\n    try:\n        r = http(\n            httpbin + BASIC_AUTH_URL,\n            '--auth-type',\n            Plugin.auth_type,\n        )\n        assert HTTP_OK in r\n        assert r.json == AUTH_OK\n    finally:\n        plugin_manager.unregister(Plugin)\n\n\ndef test_auth_plugin_require_auth_false_and_auth_provided(httpbin):\n\n    class Plugin(AuthPlugin):\n        auth_type = 'test-require-false-yet-provided'\n        auth_require = False\n\n        def get_auth(self, username=None, password=None):\n            assert self.raw_auth == USERNAME + SEPARATOR_CREDENTIALS + PASSWORD\n            assert username == USERNAME\n            assert password == PASSWORD\n            return basic_auth()\n\n    plugin_manager.register(Plugin)\n    try:\n        r = http(\n            httpbin + BASIC_AUTH_URL,\n            '--auth-type',\n            Plugin.auth_type,\n            '--auth',\n            USERNAME + SEPARATOR_CREDENTIALS + PASSWORD,\n        )\n        assert HTTP_OK in r\n        assert r.json == AUTH_OK\n    finally:\n        plugin_manager.unregister(Plugin)\n\n\n@mock.patch('httpie.cli.argtypes.AuthCredentials._getpass',\n            new=lambda self, prompt: 'UNEXPECTED_PROMPT_RESPONSE')\ndef test_auth_plugin_prompt_password_false(httpbin):\n\n    class Plugin(AuthPlugin):\n        auth_type = 'test-prompt-false'\n        prompt_password = False\n\n        def get_auth(self, username=None, password=None):\n            assert self.raw_auth == USERNAME\n            assert username == USERNAME\n            assert password is None\n            return basic_auth()\n\n    plugin_manager.register(Plugin)\n\n    try:\n        r = http(\n            httpbin + BASIC_AUTH_URL,\n            '--auth-type',\n            Plugin.auth_type,\n            '--auth',\n            USERNAME,\n        )\n        assert HTTP_OK in r\n        assert r.json == AUTH_OK\n    finally:\n        plugin_manager.unregister(Plugin)\n"
  },
  {
    "path": "tests/test_binary.py",
    "content": "\"\"\"Tests for dealing with binary request and response data.\"\"\"\nimport requests\n\nfrom .fixtures import BIN_FILE_PATH, BIN_FILE_CONTENT, BIN_FILE_PATH_ARG\nfrom httpie.output.streams import BINARY_SUPPRESSED_NOTICE\nfrom .utils import MockEnvironment, http\n\n\nclass TestBinaryRequestData:\n\n    def test_binary_stdin(self, httpbin):\n        with open(BIN_FILE_PATH, 'rb') as stdin:\n            env = MockEnvironment(\n                stdin=stdin,\n                stdin_isatty=False,\n                stdout_isatty=False\n            )\n            r = http('--print=B', 'POST', httpbin + '/post', env=env)\n            assert r == BIN_FILE_CONTENT\n\n    def test_binary_file_path(self, httpbin):\n        env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)\n        r = http('--print=B', 'POST', httpbin + '/post',\n                 '@' + BIN_FILE_PATH_ARG, env=env)\n        assert r == BIN_FILE_CONTENT\n\n    def test_binary_file_form(self, httpbin):\n        env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)\n        r = http('--print=B', '--form', 'POST', httpbin + '/post',\n                 'test@' + BIN_FILE_PATH_ARG, env=env)\n        assert bytes(BIN_FILE_CONTENT) in bytes(r)\n\n\nclass TestBinaryResponseData:\n\n    def test_binary_suppresses_when_terminal(self, httpbin):\n        r = http('GET', httpbin + '/bytes/1024?seed=1')\n        assert BINARY_SUPPRESSED_NOTICE.decode() in r\n\n    def test_binary_suppresses_when_not_terminal_but_pretty(self, httpbin):\n        env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)\n        r = http('--pretty=all', 'GET', httpbin + '/bytes/1024?seed=1', env=env)\n        assert BINARY_SUPPRESSED_NOTICE.decode() in r\n\n    def test_binary_included_and_correct_when_suitable(self, httpbin):\n        env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)\n        url = httpbin + '/bytes/1024?seed=1'\n        r = http('GET', url, env=env)\n        expected = requests.get(url).content\n        assert r == expected\n"
  },
  {
    "path": "tests/test_cli.py",
    "content": "\"\"\"CLI argument parsing related tests.\"\"\"\nimport argparse\n\nimport pytest\nfrom requests.exceptions import InvalidSchema\n\nimport httpie.cli.argparser\nfrom httpie.cli import constants\nfrom httpie.cli.definition import parser\nfrom httpie.cli.argtypes import KeyValueArg, KeyValueArgType\nfrom httpie.cli.requestitems import RequestItems\nfrom httpie.status import ExitStatus\nfrom httpie.utils import load_json_preserve_order_and_dupe_keys\n\nfrom .fixtures import (\n    FILE_CONTENT, FILE_PATH, FILE_PATH_ARG, JSON_FILE_CONTENT,\n    JSON_FILE_PATH_ARG,\n)\nfrom .utils import HTTP_OK, MockEnvironment, StdinBytesIO, http\n\n\nclass TestItemParsing:\n    key_value_arg = KeyValueArgType(*constants.SEPARATOR_GROUP_ALL_ITEMS)\n\n    def test_invalid_items(self):\n        items = ['no-separator']\n        for item in items:\n            pytest.raises(argparse.ArgumentTypeError, self.key_value_arg, item)\n\n    def test_escape_separator(self):\n        items = RequestItems.from_args([\n            # headers\n            self.key_value_arg(r'foo\\:bar:baz'),\n            self.key_value_arg(r'jack\\@jill:hill'),\n\n            # data\n            self.key_value_arg(r'baz\\=bar=foo'),\n\n            # files\n            self.key_value_arg(fr'bar\\@baz@{FILE_PATH_ARG}'),\n        ])\n        # `HTTPHeadersDict` => `dict`\n        headers = dict(items.headers)\n\n        assert headers == {\n            'foo:bar': 'baz',\n            'jack@jill': 'hill',\n        }\n        assert items.data == {\n            'baz=bar': 'foo'\n        }\n        assert 'bar@baz' in items.files\n\n    @pytest.mark.parametrize('string, key, sep, value', [\n        ('path=c:\\\\windows', 'path', '=', 'c:\\\\windows'),\n        ('path=c:\\\\windows\\\\', 'path', '=', 'c:\\\\windows\\\\'),\n        ('path\\\\==c:\\\\windows', 'path=', '=', 'c:\\\\windows'),\n    ])\n    def test_backslash_before_non_special_character_does_not_escape(\n        self, string, key, sep, value\n    ):\n        expected = KeyValueArg(orig=string, key=key, sep=sep, value=value)\n        actual = self.key_value_arg(string)\n        assert actual == expected\n\n    def test_escape_longsep(self):\n        items = RequestItems.from_args([\n            self.key_value_arg(r'bob\\:==foo'),\n        ])\n        assert items.params == {\n            'bob:': 'foo'\n        }\n\n    def test_valid_items(self):\n        items = RequestItems.from_args([\n            self.key_value_arg('string=value'),\n            self.key_value_arg('Header:value'),\n            self.key_value_arg('Unset-Header:'),\n            self.key_value_arg('Empty-Header;'),\n            self.key_value_arg('list:=[\"a\", 1, {}, false]'),\n            self.key_value_arg('obj:={\"a\": \"b\"}'),\n            self.key_value_arg(r'nested\\[2\\][a][]=1'),\n            self.key_value_arg('nested[2][a][]:=1'),\n            self.key_value_arg('ed='),\n            self.key_value_arg('bool:=true'),\n            self.key_value_arg('file@' + FILE_PATH_ARG),\n            self.key_value_arg('query==value'),\n            self.key_value_arg('Embedded-Header:@' + FILE_PATH_ARG),\n            self.key_value_arg('string-embed=@' + FILE_PATH_ARG),\n            self.key_value_arg('param-embed==@' + FILE_PATH_ARG),\n            self.key_value_arg('raw-json-embed:=@' + JSON_FILE_PATH_ARG),\n        ])\n\n        # Parsed headers\n        # `HTTPHeadersDict` => `dict`\n        headers = dict(items.headers)\n        assert headers == {\n            'Header': 'value',\n            'Unset-Header': None,\n            'Empty-Header': '',\n            'Embedded-Header': FILE_CONTENT.rstrip('\\n')\n        }\n\n        # Parsed data\n        raw_json_embed = items.data.pop('raw-json-embed')\n        assert raw_json_embed == load_json_preserve_order_and_dupe_keys(JSON_FILE_CONTENT)\n        items.data['string-embed'] = items.data['string-embed'].strip()\n        assert dict(items.data) == {\n            'ed': '',\n            'string': 'value',\n            'bool': True,\n            'list': ['a', 1, load_json_preserve_order_and_dupe_keys('{}'), False],\n            'nested[2]': {'a': ['1']},\n            'nested': [None, None, {'a': [1]}],\n            'obj': load_json_preserve_order_and_dupe_keys('{\"a\": \"b\"}'),\n            'string-embed': FILE_CONTENT\n        }\n\n        # Parsed query string parameters\n        assert items.params == {\n            'query': 'value',\n            'param-embed': FILE_CONTENT.rstrip('\\n')\n        }\n\n        # Parsed file fields\n        assert 'file' in items.files\n        assert (items.files['file'][1].read().strip().\n                decode() == FILE_CONTENT)\n\n    def test_multiple_file_fields_with_same_field_name(self):\n        items = RequestItems.from_args([\n            self.key_value_arg('file_field@' + FILE_PATH_ARG),\n            self.key_value_arg('file_field@' + FILE_PATH_ARG),\n        ])\n        assert len(items.files['file_field']) == 2\n\n    def test_multiple_text_fields_with_same_field_name(self):\n        items = RequestItems.from_args(\n            request_item_args=[\n                self.key_value_arg('text_field=a'),\n                self.key_value_arg('text_field=b')\n            ],\n            request_type=constants.RequestType.FORM,\n        )\n        assert items.data['text_field'] == ['a', 'b']\n        assert list(items.data.items()) == [\n            ('text_field', 'a'),\n            ('text_field', 'b'),\n        ]\n\n\nclass TestQuerystring:\n    def test_query_string_params_in_url(self, httpbin):\n        r = http('--print=Hhb', 'GET', httpbin + '/get?a=1&b=2')\n        path = '/get?a=1&b=2'\n        url = httpbin + path\n        assert HTTP_OK in r\n        assert f'GET {path} HTTP/1.1' in r\n        assert f'\"url\": \"{url}\"' in r\n\n    def test_query_string_params_items(self, httpbin):\n        r = http('--print=Hhb', 'GET', httpbin + '/get', 'a==1')\n        path = '/get?a=1'\n        url = httpbin + path\n        assert HTTP_OK in r\n        assert f'GET {path} HTTP/1.1' in r\n        assert f'\"url\": \"{url}\"' in r\n\n    def test_query_string_params_in_url_and_items_with_duplicates(self,\n                                                                  httpbin):\n        r = http('--print=Hhb', 'GET',\n                 httpbin + '/get?a=1&a=1', 'a==1', 'a==1')\n        path = '/get?a=1&a=1&a=1&a=1'\n        url = httpbin + path\n        assert HTTP_OK in r\n        assert f'GET {path} HTTP/1.1' in r\n        assert f'\"url\": \"{url}\"' in r\n\n\n@pytest.mark.parametrize(['program_name', 'url_arg', 'parsed_url'], [\n    ('http', '://pie.dev/get', 'http://pie.dev/get'),\n    ('https', '://pie.dev/get', 'https://pie.dev/get'),\n])\ndef test_url_leading_colon_slash_slash(program_name, url_arg, parsed_url):\n    env = MockEnvironment(program_name=program_name)\n    args = parser.parse_args(args=[url_arg], env=env)\n    assert args.url == parsed_url\n\n\ndef test_url_colon_slash_slash_only():\n    r = http('://', tolerate_error_exit_status=True)\n    assert r.stderr.strip() == \"http: error: InvalidURL: Invalid URL 'http://': No host supplied\"\n\n\nclass TestLocalhostShorthand:\n    def test_expand_localhost_shorthand(self):\n        args = parser.parse_args(args=[':'], env=MockEnvironment())\n        assert args.url == 'http://localhost'\n\n    def test_expand_localhost_shorthand_with_slash(self):\n        args = parser.parse_args(args=[':/'], env=MockEnvironment())\n        assert args.url == 'http://localhost/'\n\n    def test_expand_localhost_shorthand_with_port(self):\n        args = parser.parse_args(args=[':3000'], env=MockEnvironment())\n        assert args.url == 'http://localhost:3000'\n\n    def test_expand_localhost_shorthand_with_path(self):\n        args = parser.parse_args(args=[':/path'], env=MockEnvironment())\n        assert args.url == 'http://localhost/path'\n\n    def test_expand_localhost_shorthand_with_port_and_slash(self):\n        args = parser.parse_args(args=[':3000/'], env=MockEnvironment())\n        assert args.url == 'http://localhost:3000/'\n\n    def test_expand_localhost_shorthand_with_port_and_path(self):\n        args = parser.parse_args(args=[':3000/path'], env=MockEnvironment())\n        assert args.url == 'http://localhost:3000/path'\n\n    def test_dont_expand_shorthand_ipv6_as_shorthand(self):\n        args = parser.parse_args(args=['::1'], env=MockEnvironment())\n        assert args.url == 'http://::1'\n\n    def test_dont_expand_longer_ipv6_as_shorthand(self):\n        args = parser.parse_args(\n            args=['::ffff:c000:0280'],\n            env=MockEnvironment()\n        )\n        assert args.url == 'http://::ffff:c000:0280'\n\n    def test_dont_expand_full_ipv6_as_shorthand(self):\n        args = parser.parse_args(\n            args=['0000:0000:0000:0000:0000:0000:0000:0001'],\n            env=MockEnvironment()\n        )\n        assert args.url == 'http://0000:0000:0000:0000:0000:0000:0000:0001'\n\n\nclass TestArgumentParser:\n\n    def setup_method(self, method):\n        self.parser = httpie.cli.argparser.HTTPieArgumentParser()\n\n    def test_guess_when_method_set_and_valid(self):\n        self.parser.args = argparse.Namespace()\n        self.parser.args.method = 'GET'\n        self.parser.args.url = 'http://example.com/'\n        self.parser.args.request_items = []\n        self.parser.args.ignore_stdin = False\n        self.parser.env = MockEnvironment()\n        self.parser._guess_method()\n        assert self.parser.args.method == 'GET'\n        assert self.parser.args.url == 'http://example.com/'\n        assert self.parser.args.request_items == []\n\n    def test_guess_when_method_not_set(self):\n        self.parser.args = argparse.Namespace()\n        self.parser.args.method = None\n        self.parser.args.url = 'http://example.com/'\n        self.parser.args.request_items = []\n        self.parser.args.ignore_stdin = False\n        self.parser.env = MockEnvironment()\n        self.parser._guess_method()\n        assert self.parser.args.method == 'GET'\n        assert self.parser.args.url == 'http://example.com/'\n        assert self.parser.args.request_items == []\n\n    def test_guess_when_method_set_but_invalid_and_data_field(self):\n        self.parser.args = argparse.Namespace()\n        self.parser.args.method = 'http://example.com/'\n        self.parser.args.url = 'data=field'\n        self.parser.args.request_items = []\n        self.parser.args.ignore_stdin = False\n        self.parser.env = MockEnvironment()\n        self.parser._guess_method()\n        assert self.parser.args.method == 'POST'\n        assert self.parser.args.url == 'http://example.com/'\n        assert self.parser.args.request_items == [\n            KeyValueArg(key='data',\n                        value='field',\n                        sep='=',\n                        orig='data=field')\n        ]\n\n    def test_guess_when_method_set_but_invalid_and_header_field(self):\n        self.parser.args = argparse.Namespace()\n        self.parser.args.method = 'http://example.com/'\n        self.parser.args.url = 'test:header'\n        self.parser.args.request_items = []\n        self.parser.args.ignore_stdin = False\n        self.parser.env = MockEnvironment()\n        self.parser._guess_method()\n        assert self.parser.args.method == 'GET'\n        assert self.parser.args.url == 'http://example.com/'\n        assert self.parser.args.request_items, [\n            KeyValueArg(key='test',\n                        value='header',\n                        sep=':',\n                        orig='test:header')\n        ]\n\n    def test_guess_when_method_set_but_invalid_and_item_exists(self):\n        self.parser.args = argparse.Namespace()\n        self.parser.args.method = 'http://example.com/'\n        self.parser.args.url = 'new_item=a'\n        self.parser.args.request_items = [\n            KeyValueArg(\n                key='old_item', value='b', sep='=', orig='old_item=b')\n        ]\n        self.parser.args.ignore_stdin = False\n        self.parser.env = MockEnvironment()\n        self.parser._guess_method()\n        assert self.parser.args.request_items, [\n            KeyValueArg(key='new_item', value='a', sep='=', orig='new_item=a'),\n            KeyValueArg(\n                key='old_item', value='b', sep='=', orig='old_item=b'),\n        ]\n\n\nclass TestNoOptions:\n\n    def test_valid_no_options(self, httpbin):\n        r = http('--verbose', '--no-verbose', 'GET', httpbin + '/get')\n        assert 'GET /get HTTP/1.1' not in r\n\n    def test_invalid_no_options(self, httpbin):\n        r = http('--no-war', 'GET', httpbin + '/get',\n                 tolerate_error_exit_status=True)\n        assert r.exit_status == ExitStatus.ERROR\n        assert 'unrecognized arguments: --no-war' in r.stderr\n        assert 'GET /get HTTP/1.1' not in r\n\n\nclass TestStdin:\n\n    def test_ignore_stdin(self, httpbin):\n        env = MockEnvironment(\n            stdin=StdinBytesIO(FILE_PATH.read_bytes()),\n            stdin_isatty=False,\n        )\n        r = http('--ignore-stdin', '--verbose', httpbin + '/get', env=env)\n        assert HTTP_OK in r\n        assert 'GET /get HTTP' in r, \"Don't default to POST.\"\n        assert FILE_CONTENT not in r, \"Don't send stdin data.\"\n\n    def test_ignore_stdin_cannot_prompt_password(self, httpbin):\n        r = http('--ignore-stdin', '--auth=no-password', httpbin + '/get',\n                 tolerate_error_exit_status=True)\n        assert r.exit_status == ExitStatus.ERROR\n        assert 'because --ignore-stdin' in r.stderr\n\n    def test_stdin_closed(self, httpbin):\n        r = http(httpbin + '/get', env=MockEnvironment(stdin=None))\n        assert HTTP_OK in r\n\n\nclass TestSchemes:\n\n    def test_invalid_custom_scheme(self):\n        # InvalidSchema is expected because HTTPie\n        # shouldn't touch a formally valid scheme.\n        with pytest.raises(InvalidSchema):\n            http('foo+bar-BAZ.123://bah')\n\n    def test_invalid_scheme_via_via_default_scheme(self):\n        # InvalidSchema is expected because HTTPie\n        # shouldn't touch a formally valid scheme.\n        with pytest.raises(InvalidSchema):\n            http('bah', '--default=scheme=foo+bar-BAZ.123')\n\n    def test_default_scheme_option(self, httpbin_secure):\n        url = f'{httpbin_secure.host}:{httpbin_secure.port}'\n        assert HTTP_OK in http(url, '--default-scheme=https')\n\n    def test_scheme_when_invoked_as_https(self, httpbin_secure):\n        url = f'{httpbin_secure.host}:{httpbin_secure.port}'\n        assert HTTP_OK in http(url, program_name='https')\n"
  },
  {
    "path": "tests/test_cli_ui.py",
    "content": "import pytest\nimport shutil\nimport os\nfrom tests.utils import http\n\nNAKED_BASE_TEMPLATE = \"\"\"\\\nusage:\n    http {extra_args}[METHOD] URL [REQUEST_ITEM ...]\n\nerror:\n    {error_msg}\n\nfor more information:\n    run 'http --help' or visit https://httpie.io/docs/cli\n\n\"\"\"\n\nNAKED_HELP_MESSAGE = NAKED_BASE_TEMPLATE.format(\n    extra_args=\"\",\n    error_msg=\"the following arguments are required: URL\"\n)\n\nNAKED_HELP_MESSAGE_PRETTY_WITH_NO_ARG = NAKED_BASE_TEMPLATE.format(\n    extra_args=\"--pretty {all, colors, format, none} \",\n    error_msg=\"argument --pretty: expected one argument\"\n)\n\nNAKED_HELP_MESSAGE_PRETTY_WITH_INVALID_ARG = NAKED_BASE_TEMPLATE.format(\n    extra_args=\"--pretty {all, colors, format, none} \",\n    error_msg=\"argument --pretty: invalid choice: '$invalid' (choose from 'all', 'colors', 'format', 'none')\"\n)\n\n\nPREDEFINED_TERMINAL_SIZE = (200, 100)\n\n\n@pytest.fixture(scope=\"function\")\ndef ignore_terminal_size(monkeypatch):\n    \"\"\"Some tests wrap/crop the output depending on the\n    size of the executed terminal, which might not be consistent\n    through all runs.\n\n    This fixture ensures every run uses the same exact configuration.\n    \"\"\"\n\n    def fake_terminal_size(*args, **kwargs):\n        return os.terminal_size(PREDEFINED_TERMINAL_SIZE)\n\n    # Setting COLUMNS as an env var is required for 3.8<\n    monkeypatch.setitem(os.environ, 'COLUMNS', str(PREDEFINED_TERMINAL_SIZE[0]))\n    monkeypatch.setattr(shutil, 'get_terminal_size', fake_terminal_size)\n    monkeypatch.setattr(os, 'get_terminal_size', fake_terminal_size)\n\n\n@pytest.mark.parametrize(\n    'args, expected_msg', [\n        ([], NAKED_HELP_MESSAGE),\n        (['--pretty'], NAKED_HELP_MESSAGE_PRETTY_WITH_NO_ARG),\n        (['pie.dev', '--pretty'], NAKED_HELP_MESSAGE_PRETTY_WITH_NO_ARG),\n        (['--pretty', '$invalid'], NAKED_HELP_MESSAGE_PRETTY_WITH_INVALID_ARG),\n    ]\n)\ndef test_naked_invocation(ignore_terminal_size, args, expected_msg):\n    result = http(*args, tolerate_error_exit_status=True)\n    assert result.stderr == expected_msg\n"
  },
  {
    "path": "tests/test_cli_utils.py",
    "content": "import pytest\nfrom argparse import ArgumentParser\nfrom unittest.mock import Mock\nfrom httpie.cli.utils import LazyChoices\n\n\ndef test_lazy_choices():\n    mock = Mock()\n    getter = mock.getter\n    getter.return_value = ['a', 'b', 'c']\n\n    parser = ArgumentParser()\n    parser.register('action', 'lazy_choices', LazyChoices)\n    parser.add_argument(\n        '--option',\n        help=\"the regular option\",\n        default='a',\n        metavar='SYMBOL',\n        choices=['a', 'b'],\n    )\n    parser.add_argument(\n        '--lazy-option',\n        help=\"the lazy option\",\n        default='a',\n        metavar='SYMBOL',\n        action='lazy_choices',\n        getter=getter,\n        cache=False  # for test purposes\n    )\n\n    # Parser initialization doesn't call it.\n    getter.assert_not_called()\n\n    # If we don't use --lazy-option, we don't retrieve it.\n    parser.parse_args([])\n    getter.assert_not_called()\n\n    parser.parse_args(['--option', 'b'])\n    getter.assert_not_called()\n\n    # If we pass a value, it will retrieve to verify.\n    parser.parse_args(['--lazy-option', 'c'])\n    getter.assert_called()\n    getter.reset_mock()\n\n    with pytest.raises(SystemExit):\n        parser.parse_args(['--lazy-option', 'z'])\n    getter.assert_called()\n    getter.reset_mock()\n\n\ndef test_lazy_choices_help():\n    mock = Mock()\n    getter = mock.getter\n    getter.return_value = ['a', 'b', 'c']\n\n    help_formatter = mock.help_formatter\n    help_formatter.return_value = '<my help>'\n\n    parser = ArgumentParser()\n    parser.register('action', 'lazy_choices', LazyChoices)\n    parser.add_argument(\n        '--lazy-option',\n        default='a',\n        metavar='SYMBOL',\n        action='lazy_choices',\n        getter=getter,\n        help_formatter=help_formatter,\n        cache=False  # for test purposes\n    )\n\n    # Parser initialization doesn't call it.\n    getter.assert_not_called()\n\n    # If we don't use `--help`, we don't use it.\n    parser.parse_args([])\n    getter.assert_not_called()\n    help_formatter.assert_not_called()\n\n    parser.parse_args(['--lazy-option', 'b'])\n    help_formatter.assert_not_called()\n\n    # If we use --help, then we call it with styles\n    with pytest.raises(SystemExit):\n        parser.parse_args(['--help'])\n    help_formatter.assert_called_once_with(['a', 'b', 'c'], isolation_mode=False)\n"
  },
  {
    "path": "tests/test_compress.py",
    "content": "\"\"\"\nWe test against httpbin which doesn't return the request data in a\nconsistent way:\n\n1. Non-form requests: the `data` field contains base64 encoded version of\nour zlib-encoded request data.\n\n2. Form requests: `form` contains a messed up version of the data.\n\n\"\"\"\nimport base64\nimport zlib\n\nfrom .fixtures import FILE_PATH, FILE_CONTENT\nfrom httpie.status import ExitStatus\nfrom .utils import StdinBytesIO, http, HTTP_OK, MockEnvironment\n\n\ndef assert_decompressed_equal(base64_compressed_data, expected_str):\n    compressed_data = base64.b64decode(\n        base64_compressed_data.split(',', 1)[1])\n    data = zlib.decompress(compressed_data)\n    actual_str = data.decode()\n\n    # FIXME: contains a trailing linebreak with an uploaded file\n    actual_str = actual_str.rstrip()\n\n    assert actual_str == expected_str\n\n\ndef test_cannot_combine_compress_with_chunked(httpbin):\n    r = http('--compress', '--chunked', httpbin + '/get',\n             tolerate_error_exit_status=True)\n    assert r.exit_status == ExitStatus.ERROR\n    assert 'cannot combine --compress and --chunked' in r.stderr\n\n\ndef test_cannot_combine_compress_with_multipart(httpbin):\n    r = http('--compress', '--multipart', httpbin + '/get',\n             tolerate_error_exit_status=True)\n    assert r.exit_status == ExitStatus.ERROR\n    assert 'cannot combine --compress and --multipart' in r.stderr\n\n\ndef test_compress_skip_negative_ratio(httpbin_both):\n    r = http(\n        '--compress',\n        httpbin_both + '/post',\n        'foo=bar',\n    )\n    assert HTTP_OK in r\n    assert 'Content-Encoding' not in r.json['headers']\n    assert r.json['json'] == {'foo': 'bar'}\n\n\ndef test_compress_force_with_negative_ratio(httpbin_both):\n    r = http(\n        '--compress',\n        '--compress',\n        httpbin_both + '/post',\n        'foo=bar',\n    )\n    assert HTTP_OK in r\n    assert r.json['headers']['Content-Encoding'] == 'deflate'\n    assert_decompressed_equal(r.json['data'], '{\"foo\": \"bar\"}')\n\n\ndef test_compress_json(httpbin_both):\n    r = http(\n        '--compress',\n        '--compress',\n        httpbin_both + '/post',\n        'foo=bar',\n    )\n    assert HTTP_OK in r\n    assert r.json['headers']['Content-Encoding'] == 'deflate'\n    assert_decompressed_equal(r.json['data'], '{\"foo\": \"bar\"}')\n    assert r.json['json'] is None\n\n\ndef test_compress_form(httpbin_both):\n    r = http(\n        '--form',\n        '--compress',\n        '--compress',\n        httpbin_both + '/post',\n        'foo=bar',\n    )\n    assert HTTP_OK in r\n    assert r.json['headers']['Content-Encoding'] == 'deflate'\n    assert r.json['data'] == \"\"\n    assert '\"foo\": \"bar\"' not in r\n\n\ndef test_compress_raw(httpbin_both):\n    r = http(\n        '--raw',\n        FILE_CONTENT,\n        '--compress',\n        '--compress',\n        httpbin_both + '/post',\n    )\n    assert HTTP_OK in r\n    assert r.json['headers']['Content-Encoding'] == 'deflate'\n    assert_decompressed_equal(r.json['data'], FILE_CONTENT.strip())\n\n\ndef test_compress_stdin(httpbin_both):\n    env = MockEnvironment(\n        stdin=StdinBytesIO(FILE_PATH.read_bytes()),\n        stdin_isatty=False,\n    )\n    r = http(\n        '--compress',\n        '--compress',\n        'PATCH',\n        httpbin_both + '/patch',\n        env=env,\n    )\n    assert HTTP_OK in r\n    assert r.json['headers']['Content-Encoding'] == 'deflate'\n    assert_decompressed_equal(r.json['data'], FILE_CONTENT.strip())\n    assert not r.json['json']\n\n\ndef test_compress_file(httpbin_both):\n    r = http(\n        '--form',\n        '--compress',\n        '--compress',\n        'PUT',\n        httpbin_both + '/put',\n        f'file@{FILE_PATH}',\n    )\n    assert HTTP_OK in r\n    assert r.json['headers']['Content-Encoding'] == 'deflate'\n    assert r.json['headers']['Content-Type'].startswith(\n        'multipart/form-data; boundary=')\n    assert r.json['files'] == {}\n    assert FILE_CONTENT not in r\n"
  },
  {
    "path": "tests/test_config.py",
    "content": "from pathlib import Path\n\nimport pytest\nfrom _pytest.monkeypatch import MonkeyPatch\n\nfrom httpie.compat import is_windows\nfrom httpie.encoding import UTF8\nfrom httpie.config import (\n    Config, DEFAULT_CONFIG_DIRNAME, DEFAULT_RELATIVE_LEGACY_CONFIG_DIR,\n    DEFAULT_RELATIVE_XDG_CONFIG_HOME, DEFAULT_WINDOWS_CONFIG_DIR,\n    ENV_HTTPIE_CONFIG_DIR, ENV_XDG_CONFIG_HOME, get_default_config_dir,\n)\nfrom .utils import HTTP_OK, MockEnvironment, http\n\n\ndef test_default_options(httpbin):\n    env = MockEnvironment()\n    env.config['default_options'] = ['--form']\n    env.config.save()\n    r = http(httpbin + '/post', 'foo=bar', env=env)\n    assert r.json['form'] == {\n        \"foo\": \"bar\"\n    }\n\n\ndef test_config_file_not_valid(httpbin):\n    env = MockEnvironment()\n    env.create_temp_config_dir()\n    (env.config_dir / Config.FILENAME).write_text('{invalid json}', encoding=UTF8)\n    r = http(httpbin + '/get', env=env)\n    assert HTTP_OK in r\n    assert 'http: warning' in r.stderr\n    assert 'invalid config file' in r.stderr\n\n\n@pytest.mark.skipif(is_windows, reason='cannot chmod 000 on Windows')\ndef test_config_file_inaccessible(httpbin):\n    env = MockEnvironment()\n    env.create_temp_config_dir()\n    config_path = env.config_dir / Config.FILENAME\n    assert not config_path.exists()\n    config_path.touch(0o000)\n    assert config_path.exists()\n    r = http(httpbin + '/get', env=env)\n    assert HTTP_OK in r\n    assert 'http: warning' in r.stderr\n    assert 'cannot read config file' in r.stderr\n\n\ndef test_default_options_overwrite(httpbin):\n    env = MockEnvironment()\n    env.config['default_options'] = ['--form']\n    env.config.save()\n    r = http('--json', httpbin + '/post', 'foo=bar', env=env)\n    assert r.json['json'] == {\n        \"foo\": \"bar\"\n    }\n\n\n@pytest.mark.skipif(is_windows, reason='XDG_CONFIG_HOME needs *nix')\ndef test_explicit_xdg_config_home(monkeypatch: MonkeyPatch, tmp_path: Path):\n    home_dir = tmp_path\n    monkeypatch.delenv(ENV_HTTPIE_CONFIG_DIR, raising=False)\n    monkeypatch.setenv('HOME', str(home_dir))\n    custom_xdg_config_home = home_dir / 'custom_xdg_config_home'\n    monkeypatch.setenv(ENV_XDG_CONFIG_HOME, str(custom_xdg_config_home))\n    expected_config_dir = custom_xdg_config_home / DEFAULT_CONFIG_DIRNAME\n    assert get_default_config_dir() == expected_config_dir\n\n\n@pytest.mark.skipif(is_windows, reason='XDG_CONFIG_HOME needs *nix')\ndef test_default_xdg_config_home(monkeypatch: MonkeyPatch, tmp_path: Path):\n    home_dir = tmp_path\n    monkeypatch.delenv(ENV_HTTPIE_CONFIG_DIR, raising=False)\n    monkeypatch.delenv(ENV_XDG_CONFIG_HOME, raising=False)\n    monkeypatch.setenv('HOME', str(home_dir))\n    expected_config_dir = (\n        home_dir\n        / DEFAULT_RELATIVE_XDG_CONFIG_HOME\n        / DEFAULT_CONFIG_DIRNAME\n    )\n    assert get_default_config_dir() == expected_config_dir\n\n\n@pytest.mark.skipif(is_windows, reason='legacy config dir needs *nix')\ndef test_legacy_config_dir(monkeypatch: MonkeyPatch, tmp_path: Path):\n    home_dir = tmp_path\n    monkeypatch.delenv(ENV_HTTPIE_CONFIG_DIR, raising=False)\n    monkeypatch.setenv('HOME', str(home_dir))\n    legacy_config_dir = home_dir / DEFAULT_RELATIVE_LEGACY_CONFIG_DIR\n    legacy_config_dir.mkdir()\n    assert get_default_config_dir() == legacy_config_dir\n\n\ndef test_custom_config_dir(monkeypatch: MonkeyPatch, tmp_path: Path):\n    httpie_config_dir = tmp_path / 'custom/directory'\n    monkeypatch.setenv(ENV_HTTPIE_CONFIG_DIR, str(httpie_config_dir))\n    assert get_default_config_dir() == httpie_config_dir\n\n\n@pytest.mark.skipif(not is_windows, reason='windows-only')\ndef test_windows_config_dir(monkeypatch: MonkeyPatch):\n    monkeypatch.delenv(ENV_HTTPIE_CONFIG_DIR, raising=False)\n    assert get_default_config_dir() == DEFAULT_WINDOWS_CONFIG_DIR\n"
  },
  {
    "path": "tests/test_cookie.py",
    "content": "from http.cookies import SimpleCookie\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\nfrom threading import Thread\n\nfrom .utils import http\n\n\nclass TestIntegration:\n\n    def setup_mock_server(self, handler):\n        \"\"\"Configure mock server.\"\"\"\n        # Passing 0 as the port will cause a random free port to be chosen.\n        self.mock_server = HTTPServer(('localhost', 0), handler)\n        _, self.mock_server_port = self.mock_server.server_address\n\n        # Start running mock server in a separate thread.\n        # Daemon threads automatically shut down when the main process exits.\n        self.mock_server_thread = Thread(target=self.mock_server.serve_forever)\n        self.mock_server_thread.setDaemon(True)\n        self.mock_server_thread.start()\n\n    def test_cookie_parser(self):\n        \"\"\"Not directly testing HTTPie but `requests` to ensure their cookies handling\n        is still as expected by `get_expired_cookies()`.\n        \"\"\"\n\n        class MockServerRequestHandler(BaseHTTPRequestHandler):\n            \"\"\"\"HTTP request handler.\"\"\"\n\n            def do_GET(self):\n                \"\"\"Handle GET requests.\"\"\"\n                # Craft multiple cookies\n                cookie = SimpleCookie()\n                cookie['hello'] = 'world'\n                cookie['hello']['path'] = self.path\n                cookie['oatmeal_raisin'] = 'is the best'\n                cookie['oatmeal_raisin']['path'] = self.path\n\n                # Send HTTP headers\n                self.send_response(200)\n                self.send_header('Set-Cookie', cookie.output())\n                self.end_headers()\n\n        self.setup_mock_server(MockServerRequestHandler)\n        response = http(f'http://localhost:{self.mock_server_port}/')\n        assert 'Set-Cookie: hello=world; Path=/' in response\n        assert 'Set-Cookie: oatmeal_raisin=\"is the best\"; Path=/' in response\n"
  },
  {
    "path": "tests/test_cookie_on_redirects.py",
    "content": "import pytest\nfrom .utils import http\n\n\n@pytest.mark.parametrize('target_httpbin', [\n    'httpbin',\n    'remote_httpbin',\n])\ndef test_explicit_user_set_cookie(httpbin, target_httpbin, request):\n    \"\"\"User set cookies ARE NOT persisted within redirects when there is no session, even on the same domain.\"\"\"\n    target_httpbin = request.getfixturevalue(target_httpbin)\n    r = http(\n        '--follow',\n        httpbin + '/redirect-to',\n        f'url=={target_httpbin}/cookies',\n        'Cookie:a=b'\n    )\n    assert r.json == {'cookies': {}}\n\n\n@pytest.mark.parametrize('target_httpbin', [\n    'httpbin',\n    'remote_httpbin',\n])\ndef test_explicit_user_set_cookie_in_session(tmp_path, httpbin, target_httpbin, request):\n    \"\"\"User set cookies ARE persisted within redirects when there is A session, even on the same domain.\"\"\"\n    target_httpbin = request.getfixturevalue(target_httpbin)\n    r = http(\n        '--follow',\n        '--session',\n        str(tmp_path / 'session.json'),\n        httpbin + '/redirect-to',\n        f'url=={target_httpbin}/cookies',\n        'Cookie:a=b'\n    )\n    assert r.json == {'cookies': {'a': 'b'}}\n\n\n@pytest.mark.parametrize('target_httpbin', [\n    'httpbin',\n    'remote_httpbin',\n])\ndef test_saved_user_set_cookie_in_session(tmp_path, httpbin, target_httpbin, request):\n    \"\"\"User set cookies ARE persisted within redirects when there is A session, even on the same domain.\"\"\"\n    target_httpbin = request.getfixturevalue(target_httpbin)\n    http(\n        '--follow',\n        '--session',\n        str(tmp_path / 'session.json'),\n        httpbin + '/get',\n        'Cookie:a=b'\n    )\n    r = http(\n        '--follow',\n        '--session',\n        str(tmp_path / 'session.json'),\n        httpbin + '/redirect-to',\n        f'url=={target_httpbin}/cookies',\n    )\n    assert r.json == {'cookies': {'a': 'b'}}\n\n\n@pytest.mark.parametrize('target_httpbin', [\n    'httpbin',\n    'remote_httpbin',\n])\n@pytest.mark.parametrize('session', [True, False])\ndef test_explicit_user_set_headers(httpbin, tmp_path, target_httpbin, session, request):\n    \"\"\"\n    User set headers ARE persisted within redirects even on different domains domain with or without an active session.\n\n    \"\"\"\n    target_httpbin = request.getfixturevalue(target_httpbin)\n    session_args = []\n    if session:\n        session_args.extend([\n            '--session',\n            str(tmp_path / 'session.json')\n        ])\n    r = http(\n        '--follow',\n        *session_args,\n        httpbin + '/redirect-to',\n        f'url=={target_httpbin}/get',\n        'X-Custom-Header:value'\n    )\n    assert 'X-Custom-Header' in r.json['headers']\n\n\n@pytest.mark.parametrize('session', [True, False])\ndef test_server_set_cookie_on_redirect_same_domain(tmp_path, httpbin, session):\n    \"\"\"Server set cookies ARE persisted on the same domain when they are forwarded.\"\"\"\n    session_args = []\n    if session:\n        session_args.extend([\n            '--session',\n            str(tmp_path / 'session.json')\n        ])\n    r = http(\n        '--follow',\n        *session_args,\n        httpbin + '/cookies/set/a/b',\n    )\n    assert r.json['cookies'] == {'a': 'b'}\n\n\n@pytest.mark.parametrize('session', [True, False])\ndef test_server_set_cookie_on_redirect_different_domain(tmp_path, http_server, httpbin, session):\n    # Server set cookies ARE persisted on different domains\n    # when they are forwarded.\n\n    session_args = []\n    if session:\n        session_args.extend([\n            '--session',\n            str(tmp_path / 'session.json')\n        ])\n\n    r = http(\n        '--follow',\n        *session_args,\n        http_server + '/cookies/set-and-redirect',\n        f\"X-Redirect-To:{httpbin + '/cookies'}\",\n        'X-Cookies:a=b'\n    )\n    assert r.json['cookies'] == {'a': 'b'}\n\n\ndef test_saved_session_cookies_on_same_domain(tmp_path, httpbin):\n    \"\"\"Saved session cookies ARE persisted when making a new request to the same domain.\"\"\"\n    http(\n        '--session',\n        str(tmp_path / 'session.json'),\n        httpbin + '/cookies/set/a/b'\n    )\n    r = http(\n        '--session',\n        str(tmp_path / 'session.json'),\n        httpbin + '/cookies'\n    )\n    assert r.json == {'cookies': {'a': 'b'}}\n\n\ndef test_saved_session_cookies_on_different_domain(tmp_path, httpbin, remote_httpbin):\n    \"\"\"Saved session cookies ARE persisted when making a new request to a different domain.\"\"\"\n    http(\n        '--session',\n        str(tmp_path / 'session.json'),\n        httpbin + '/cookies/set/a/b'\n    )\n    r = http(\n        '--session',\n        str(tmp_path / 'session.json'),\n        remote_httpbin + '/cookies'\n    )\n    assert r.json == {'cookies': {}}\n\n\n@pytest.mark.parametrize(['initial_domain', 'first_request_domain', 'second_request_domain', 'expect_cookies'], [\n    (\n        # Cookies are set by    Domain A\n        # Initial domain is     Domain A\n        # Redirected domain is  Domain A\n        'httpbin',\n        'httpbin',\n        'httpbin',\n        True,\n    ),\n    (\n        # Cookies are set by    Domain A\n        # Initial domain is     Domain B\n        # Redirected domain is  Domain B\n        'httpbin',\n        'remote_httpbin',\n        'remote_httpbin',\n        False,\n    ),\n    (\n        # Cookies are set by    Domain A\n        # Initial domain is     Domain A\n        # Redirected domain is  Domain B\n        'httpbin',\n        'httpbin',\n        'remote_httpbin',\n        False,\n    ),\n    (\n        # Cookies are set by    Domain A\n        # Initial domain is     Domain B\n        # Redirected domain is  Domain A\n        'httpbin',\n        'remote_httpbin',\n        'httpbin',\n        True,\n    ),\n])\ndef test_saved_session_cookies_on_redirect(\n        tmp_path, initial_domain, first_request_domain, second_request_domain, expect_cookies, request):\n    initial_domain = request.getfixturevalue(initial_domain)\n    first_request_domain = request.getfixturevalue(first_request_domain)\n    second_request_domain = request.getfixturevalue(second_request_domain)\n    http(\n        '--session',\n        str(tmp_path / 'session.json'),\n        initial_domain + '/cookies/set/a/b'\n    )\n    r = http(\n        '--session',\n        str(tmp_path / 'session.json'),\n        '--follow',\n        first_request_domain + '/redirect-to',\n        f'url=={second_request_domain}/cookies'\n    )\n    if expect_cookies:\n        expected_data = {'cookies': {'a': 'b'}}\n    else:\n        expected_data = {'cookies': {}}\n    assert r.json == expected_data\n\n\ndef test_saved_session_cookie_pool(tmp_path, httpbin, remote_httpbin):\n    http(\n        '--session',\n        str(tmp_path / 'session.json'),\n        httpbin + '/cookies/set/a/b'\n    )\n    http(\n        '--session',\n        str(tmp_path / 'session.json'),\n        remote_httpbin + '/cookies/set/a/c'\n    )\n    http(\n        '--session',\n        str(tmp_path / 'session.json'),\n        remote_httpbin + '/cookies/set/b/d'\n    )\n\n    response = http(\n        '--session',\n        str(tmp_path / 'session.json'),\n        httpbin + '/cookies'\n    )\n    assert response.json['cookies'] == {'a': 'b'}\n\n    response = http(\n        '--session',\n        str(tmp_path / 'session.json'),\n        remote_httpbin + '/cookies'\n    )\n    assert response.json['cookies'] == {'a': 'c', 'b': 'd'}\n"
  },
  {
    "path": "tests/test_defaults.py",
    "content": "\"\"\"\nTests for the provided defaults regarding HTTP method, and --json vs. --form.\n\n\"\"\"\nfrom io import BytesIO\n\nfrom httpie.client import JSON_ACCEPT\nfrom .utils import MockEnvironment, http, HTTP_OK\nfrom .fixtures import FILE_PATH\n\n\ndef test_default_headers_case_insensitive(httpbin):\n    \"\"\"\n    <https://github.com/httpie/cli/issues/644>\n    \"\"\"\n    r = http(\n        '--debug',\n        '--print=H',\n        httpbin + '/post',\n        'CONTENT-TYPE:application/json-patch+json',\n        'a=b',\n    )\n    assert 'CONTENT-TYPE: application/json-patch+json' in r\n    assert 'Content-Type' not in r\n\n\n# noinspection PyPep8Naming\nclass TestImplicitHTTPMethod:\n    def test_implicit_GET(self, httpbin):\n        r = http(httpbin + '/get')\n        assert HTTP_OK in r\n\n    def test_implicit_GET_with_headers(self, httpbin):\n        r = http(httpbin + '/headers', 'Foo:bar')\n        assert HTTP_OK in r\n        assert r.json['headers']['Foo'] == 'bar'\n\n    def test_implicit_POST_json(self, httpbin):\n        r = http(httpbin + '/post', 'hello=world')\n        assert HTTP_OK in r\n        assert r.json['json'] == {'hello': 'world'}\n\n    def test_implicit_POST_form(self, httpbin):\n        r = http('--form', httpbin + '/post', 'foo=bar')\n        assert HTTP_OK in r\n        assert r.json['form'] == {'foo': 'bar'}\n\n    def test_implicit_POST_raw(self, httpbin):\n        r = http('--raw', 'foo bar', httpbin + '/post')\n        assert HTTP_OK in r\n        assert r.json['data'] == 'foo bar'\n\n    def test_implicit_POST_stdin(self, httpbin):\n        env = MockEnvironment(\n            stdin_isatty=False,\n            stdin=BytesIO(FILE_PATH.read_bytes())\n        )\n        r = http('--form', httpbin + '/post', env=env)\n        assert HTTP_OK in r\n\n\nclass TestAutoContentTypeAndAcceptHeaders:\n    \"\"\"\n    Test that `Accept` and `Content-Type` correctly default to JSON,\n    but can still be overridden. The same with Content-Type when `--form`\n    `-f` is used.\n\n    \"\"\"\n\n    def test_GET_no_data_no_auto_headers(self, httpbin):\n        # https://github.com/httpie/cli/issues/62\n        r = http('GET', httpbin + '/headers')\n        assert HTTP_OK in r\n        assert r.json['headers']['Accept'] == '*/*'\n        assert 'Content-Type' not in r.json['headers']\n\n    def test_POST_no_data_no_auto_headers(self, httpbin):\n        # JSON headers shouldn't be automatically set for POST with no data.\n        r = http('POST', httpbin + '/post')\n        assert HTTP_OK in r\n        assert '\"Accept\": \"*/*\"' in r\n        assert '\"Content-Type\": \"application/json' not in r\n\n    def test_POST_with_data_auto_JSON_headers(self, httpbin):\n        r = http('POST', httpbin + '/post', 'a=b')\n        assert HTTP_OK in r\n        assert r.json['headers']['Accept'] == JSON_ACCEPT\n        assert r.json['headers']['Content-Type'] == 'application/json'\n\n    def test_GET_with_data_auto_JSON_headers(self, httpbin):\n        # JSON headers should automatically be set also for GET with data.\n        r = http('POST', httpbin + '/post', 'a=b')\n        assert HTTP_OK in r\n        assert r.json['headers']['Accept'] == JSON_ACCEPT\n        assert r.json['headers']['Content-Type'] == 'application/json'\n\n    def test_POST_explicit_JSON_JSON_ACCEPT(self, httpbin):\n        r = http('--json', 'POST', httpbin + '/post')\n        assert HTTP_OK in r\n        assert r.json['headers']['Accept'] == JSON_ACCEPT\n        # Make sure Content-Type gets set even with no data.\n        # https://github.com/httpie/cli/issues/137\n        assert 'application/json' in r.json['headers']['Content-Type']\n\n    def test_GET_explicit_JSON_explicit_headers(self, httpbin):\n        r = http('--json', 'GET', httpbin + '/headers',\n                 'Accept:application/xml',\n                 'Content-Type:application/xml')\n        assert HTTP_OK in r\n        assert '\"Accept\": \"application/xml\"' in r\n        assert '\"Content-Type\": \"application/xml\"' in r\n\n    def test_POST_form_auto_Content_Type(self, httpbin):\n        r = http('--form', 'POST', httpbin + '/post')\n        assert HTTP_OK in r\n        assert '\"Content-Type\": \"application/x-www-form-urlencoded' in r\n\n    def test_POST_form_Content_Type_override(self, httpbin):\n        r = http('--form', 'POST', httpbin + '/post',\n                 'Content-Type:application/xml')\n        assert HTTP_OK in r\n        assert '\"Content-Type\": \"application/xml\"' in r\n\n    def test_print_only_body_when_stdout_redirected_by_default(self, httpbin):\n        env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)\n        r = http('GET', httpbin + '/get', env=env)\n        assert 'HTTP/' not in r\n\n    def test_print_overridable_when_stdout_redirected(self, httpbin):\n        env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)\n        r = http('--print=h', 'GET', httpbin + '/get', env=env)\n        assert HTTP_OK in r\n"
  },
  {
    "path": "tests/test_downloads.py",
    "content": "import os\nimport tempfile\nimport time\nimport requests\nfrom unittest import mock\nfrom urllib.request import urlopen\n\nimport pytest\nfrom requests.structures import CaseInsensitiveDict\n\nfrom httpie.downloads import (\n    parse_content_range, filename_from_content_disposition, filename_from_url,\n    get_unique_filename, ContentRangeError, Downloader, PARTIAL_CONTENT\n)\nfrom .utils import http, MockEnvironment\n\n\nclass Response(requests.Response):\n    # noinspection PyDefaultArgument\n    def __init__(self, url, headers={}, status_code=200):\n        self.url = url\n        self.headers = CaseInsensitiveDict(headers)\n        self.status_code = status_code\n\n\nclass TestDownloadUtils:\n\n    def test_Content_Range_parsing(self):\n        parse = parse_content_range\n\n        assert parse('bytes 100-199/200', 100) == 200\n        assert parse('bytes 100-199/*', 100) == 200\n\n        # single byte\n        assert parse('bytes 100-100/*', 100) == 101\n\n        # missing\n        pytest.raises(ContentRangeError, parse, None, 100)\n\n        # syntax error\n        pytest.raises(ContentRangeError, parse, 'beers 100-199/*', 100)\n\n        # unexpected range\n        pytest.raises(ContentRangeError, parse, 'bytes 100-199/*', 99)\n\n        # invalid instance-length\n        pytest.raises(ContentRangeError, parse, 'bytes 100-199/199', 100)\n\n        # invalid byte-range-resp-spec\n        pytest.raises(ContentRangeError, parse, 'bytes 100-99/199', 100)\n\n    @pytest.mark.parametrize('header, expected_filename', [\n        ('attachment; filename=hello-WORLD_123.txt', 'hello-WORLD_123.txt'),\n        ('attachment; filename=\".hello-WORLD_123.txt\"', 'hello-WORLD_123.txt'),\n        ('attachment; filename=\"white space.txt\"', 'white space.txt'),\n        (r'attachment; filename=\"\\\"quotes\\\".txt\"', '\"quotes\".txt'),\n        ('attachment; filename=/etc/hosts', 'hosts'),\n        ('attachment; filename=', None)\n    ])\n    def test_Content_Disposition_parsing(self, header, expected_filename):\n        assert filename_from_content_disposition(header) == expected_filename\n\n    def test_filename_from_url(self):\n        assert 'foo.txt' == filename_from_url(\n            url='http://example.org/foo',\n            content_type='text/plain'\n        )\n        assert 'foo.html' == filename_from_url(\n            url='http://example.org/foo',\n            content_type='text/html; charset=UTF-8'\n        )\n        assert 'foo' == filename_from_url(\n            url='http://example.org/foo',\n            content_type=None\n        )\n        assert 'foo' == filename_from_url(\n            url='http://example.org/foo',\n            content_type='x-foo/bar'\n        )\n\n    @pytest.mark.parametrize(\n        'orig_name, unique_on_attempt, expected',\n        [\n            # Simple\n            ('foo.bar', 0, 'foo.bar'),\n            ('foo.bar', 1, 'foo.bar-1'),\n            ('foo.bar', 10, 'foo.bar-10'),\n            # Trim\n            ('A' * 20, 0, 'A' * 10),\n            ('A' * 20, 1, 'A' * 8 + '-1'),\n            ('A' * 20, 10, 'A' * 7 + '-10'),\n            # Trim before ext\n            ('A' * 20 + '.txt', 0, 'A' * 6 + '.txt'),\n            ('A' * 20 + '.txt', 1, 'A' * 4 + '.txt-1'),\n            # Trim at the end\n            ('foo.' + 'A' * 20, 0, 'foo.' + 'A' * 6),\n            ('foo.' + 'A' * 20, 1, 'foo.' + 'A' * 4 + '-1'),\n            ('foo.' + 'A' * 20, 10, 'foo.' + 'A' * 3 + '-10'),\n        ]\n    )\n    @mock.patch('httpie.downloads.get_filename_max_length')\n    def test_unique_filename(self, get_filename_max_length,\n                             orig_name, unique_on_attempt,\n                             expected):\n\n        def attempts(unique_on_attempt=0):\n            # noinspection PyUnresolvedReferences,PyUnusedLocal\n            def exists(filename):\n                if exists.attempt == unique_on_attempt:\n                    return False\n                exists.attempt += 1\n                return True\n\n            exists.attempt = 0\n            return exists\n\n        get_filename_max_length.return_value = 10\n\n        actual = get_unique_filename(orig_name, attempts(unique_on_attempt))\n        assert expected == actual\n\n\nclass TestDownloads:\n\n    def test_actual_download(self, httpbin_both, httpbin):\n        robots_txt = '/robots.txt'\n        body = urlopen(httpbin + robots_txt).read().decode()\n        env = MockEnvironment(stdin_isatty=True, stdout_isatty=False, show_displays=True)\n        r = http('--download', httpbin_both.url + robots_txt, env=env)\n        assert 'Downloading' in r.stderr\n        assert body == r\n\n    def test_download_with_Content_Length(self, mock_env, httpbin_both):\n        with open(os.devnull, 'w') as devnull:\n            downloader = Downloader(mock_env, output_file=devnull)\n            downloader.start(\n                initial_url='/',\n                final_response=Response(\n                    url=httpbin_both.url + '/',\n                    headers={'Content-Length': 10}\n                )\n            )\n            time.sleep(1.1)\n            downloader.chunk_downloaded(b'12345')\n            time.sleep(1.1)\n            downloader.chunk_downloaded(b'12345')\n            downloader.finish()\n            assert not downloader.interrupted\n\n    def test_download_no_Content_Length(self, mock_env, httpbin_both):\n        with open(os.devnull, 'w') as devnull:\n            downloader = Downloader(mock_env, output_file=devnull)\n            downloader.start(\n                final_response=Response(url=httpbin_both.url + '/'),\n                initial_url='/'\n            )\n            time.sleep(1.1)\n            downloader.chunk_downloaded(b'12345')\n            downloader.finish()\n            assert not downloader.interrupted\n\n    def test_download_output_from_content_disposition(self, mock_env, httpbin_both):\n        with tempfile.TemporaryDirectory() as tmp_dirname:\n            orig_cwd = os.getcwd()\n            os.chdir(tmp_dirname)\n            try:\n                assert not os.path.isfile('filename.bin')\n                downloader = Downloader(mock_env)\n                downloader.start(\n                    final_response=Response(\n                        url=httpbin_both.url + '/',\n                        headers={\n                            'Content-Length': 5,\n                            'Content-Disposition': 'attachment; filename=\"filename.bin\"',\n                        }\n                    ),\n                    initial_url='/'\n                )\n                downloader.chunk_downloaded(b'12345')\n                downloader.finish()\n                downloader.failed()  # Stop the reporter\n                assert not downloader.interrupted\n\n                # TODO: Auto-close the file in that case?\n                downloader._output_file.close()\n                assert os.path.isfile('filename.bin')\n            finally:\n                os.chdir(orig_cwd)\n\n    def test_download_interrupted(self, mock_env, httpbin_both):\n        with open(os.devnull, 'w') as devnull:\n            downloader = Downloader(mock_env, output_file=devnull)\n            downloader.start(\n                final_response=Response(\n                    url=httpbin_both.url + '/',\n                    headers={'Content-Length': 5}\n                ),\n                initial_url='/'\n            )\n            downloader.chunk_downloaded(b'1234')\n            downloader.finish()\n            assert downloader.interrupted\n\n    def test_download_resumed(self, mock_env, httpbin_both):\n        with tempfile.TemporaryDirectory() as tmp_dirname:\n            file = os.path.join(tmp_dirname, 'file.bin')\n            with open(file, 'a'):\n                pass\n\n            with open(file, 'a+b') as output_file:\n                # Start and interrupt the transfer after 3 bytes written\n                downloader = Downloader(mock_env, output_file=output_file)\n                downloader.start(\n                    final_response=Response(\n                        url=httpbin_both.url + '/',\n                        headers={'Content-Length': 5}\n                    ),\n                    initial_url='/'\n                )\n                downloader.chunk_downloaded(b'123')\n                downloader.finish()\n                downloader.failed()\n                assert downloader.interrupted\n\n            # Write bytes\n            with open(file, 'wb') as fh:\n                fh.write(b'123')\n\n            with open(file, 'a+b') as output_file:\n                # Resume the transfer\n                downloader = Downloader(mock_env, output_file=output_file, resume=True)\n\n                # Ensure `pre_request()` is working as expected too\n                headers = {}\n                downloader.pre_request(headers)\n                assert headers['Accept-Encoding'] == 'identity'\n                assert headers['Range'] == 'bytes=3-'\n\n                downloader.start(\n                    final_response=Response(\n                        url=httpbin_both.url + '/',\n                        headers={'Content-Length': 5, 'Content-Range': 'bytes 3-4/5'},\n                        status_code=PARTIAL_CONTENT\n                    ),\n                    initial_url='/'\n                )\n                downloader.chunk_downloaded(b'45')\n                downloader.finish()\n\n    def test_download_with_redirect_original_url_used_for_filename(self, httpbin):\n        # Redirect from `/redirect/1` to `/get`.\n        expected_filename = '1.json'\n        orig_cwd = os.getcwd()\n        with tempfile.TemporaryDirectory() as tmp_dirname:\n            os.chdir(tmp_dirname)\n            try:\n                assert os.listdir('.') == []\n                http('--download', httpbin + '/redirect/1')\n                assert os.listdir('.') == [expected_filename]\n            finally:\n                os.chdir(orig_cwd)\n"
  },
  {
    "path": "tests/test_encoding.py",
    "content": "\"\"\"\nVarious encoding handling related tests.\n\n\"\"\"\nimport pytest\nimport responses\nfrom charset_normalizer.constant import TOO_SMALL_SEQUENCE\n\nfrom httpie.cli.constants import PRETTY_MAP\nfrom httpie.encoding import UTF8\n\nfrom .utils import http, HTTP_OK, DUMMY_URL, MockEnvironment\nfrom .fixtures import UNICODE\n\n\nCHARSET_TEXT_PAIRS = [\n    ('big5', '卷首卷首卷首卷首卷卷首卷首卷首卷首卷首卷首卷首卷首卷首卷首卷首卷首卷首'),\n    ('windows-1250', 'Všichni lidé jsou si rovni. Všichni lidé jsou si rovni.'),\n    (UTF8, 'Všichni lidé jsou si rovni. Všichni lidé jsou si rovni.'),\n]\n\n\ndef test_charset_text_pairs():\n    # Verify our test data is legit.\n    for charset, text in CHARSET_TEXT_PAIRS:\n        assert len(text) > TOO_SMALL_SEQUENCE\n        if charset != UTF8:\n            with pytest.raises(UnicodeDecodeError):\n                assert text != text.encode(charset).decode(UTF8)\n\n\ndef test_unicode_headers(httpbin):\n    # httpbin doesn't interpret UFT-8 headers\n    r = http(httpbin + '/headers', f'Test:{UNICODE}')\n    assert HTTP_OK in r\n\n\ndef test_unicode_headers_verbose(httpbin):\n    # httpbin doesn't interpret UTF-8 headers\n    r = http('--verbose', httpbin + '/headers', f'Test:{UNICODE}')\n    assert HTTP_OK in r\n    assert UNICODE in r\n\n\ndef test_unicode_raw(httpbin):\n    r = http('--raw', f'test {UNICODE}', 'POST', httpbin + '/post')\n    assert HTTP_OK in r\n    assert r.json['data'] == f'test {UNICODE}'\n\n\ndef test_unicode_raw_verbose(httpbin):\n    r = http('--verbose', '--raw', f'test {UNICODE}',\n             'POST', httpbin + '/post')\n    assert HTTP_OK in r\n    assert UNICODE in r\n\n\ndef test_unicode_form_item(httpbin):\n    r = http('--form', 'POST', httpbin + '/post', f'test={UNICODE}')\n    assert HTTP_OK in r\n    assert r.json['form'] == {'test': UNICODE}\n\n\ndef test_unicode_form_item_verbose(httpbin):\n    r = http('--verbose', '--form',\n             'POST', httpbin + '/post', f'test={UNICODE}')\n    assert HTTP_OK in r\n    assert UNICODE in r\n\n\ndef test_unicode_json_item(httpbin):\n    r = http('--json', 'POST', httpbin + '/post', f'test={UNICODE}')\n    assert HTTP_OK in r\n    assert r.json['json'] == {'test': UNICODE}\n\n\ndef test_unicode_json_item_verbose(httpbin):\n    r = http('--verbose', '--json',\n             'POST', httpbin + '/post', f'test={UNICODE}')\n    assert HTTP_OK in r\n    assert UNICODE in r\n\n\ndef test_unicode_raw_json_item(httpbin):\n    r = http('--json', 'POST', httpbin + '/post',\n             f'test:={{ \"{UNICODE}\" : [ \"{UNICODE}\" ] }}')\n    assert HTTP_OK in r\n    assert r.json['json'] == {'test': {UNICODE: [UNICODE]}}\n\n\ndef test_unicode_raw_json_item_verbose(httpbin):\n    r = http('--json', 'POST', httpbin + '/post',\n             f'test:={{ \"{UNICODE}\" : [ \"{UNICODE}\" ] }}')\n    assert HTTP_OK in r\n    assert r.json['json'] == {'test': {UNICODE: [UNICODE]}}\n\n\ndef test_unicode_url_query_arg_item(httpbin):\n    r = http(httpbin + '/get', f'test=={UNICODE}')\n    assert HTTP_OK in r\n    assert r.json['args'] == {'test': UNICODE}, r\n\n\ndef test_unicode_url_query_arg_item_verbose(httpbin):\n    r = http('--verbose', httpbin + '/get', f'test=={UNICODE}')\n    assert HTTP_OK in r\n    assert UNICODE in r\n\n\ndef test_unicode_url(httpbin):\n    r = http(f'{httpbin}/get?test={UNICODE}')\n    assert HTTP_OK in r\n    assert r.json['args'] == {'test': UNICODE}\n\n\ndef test_unicode_url_verbose(httpbin):\n    r = http('--verbose', f'{httpbin}/get?test={UNICODE}')\n    assert HTTP_OK in r\n    assert r.json['args'] == {'test': UNICODE}\n\n\ndef test_unicode_basic_auth(httpbin):\n    # it doesn't really authenticate us because httpbin\n    # doesn't interpret the UTF-8-encoded auth\n    http('--verbose', '--auth', f'test:{UNICODE}',\n         f'{httpbin}/basic-auth/test/{UNICODE}')\n\n\ndef test_unicode_digest_auth(httpbin):\n    # it doesn't really authenticate us because httpbin\n    # doesn't interpret the UTF-8-encoded auth\n    http('--auth-type=digest',\n         '--auth', f'test:{UNICODE}',\n         f'{httpbin}/digest-auth/auth/test/{UNICODE}')\n\n\n@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS)\n@responses.activate\ndef test_terminal_output_response_charset_detection(text, charset):\n    responses.add(\n        method=responses.POST,\n        url=DUMMY_URL,\n        body=text.encode(charset),\n        content_type='text/plain',\n    )\n    r = http('--form', 'POST', DUMMY_URL)\n    assert text in r\n\n\n@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS)\n@responses.activate\ndef test_terminal_output_response_content_type_charset(charset, text):\n    responses.add(\n        method=responses.POST,\n        url=DUMMY_URL,\n        body=text.encode(charset),\n        content_type=f'text/plain; charset={charset}',\n    )\n    r = http('--form', 'POST', DUMMY_URL)\n    assert text in r\n\n\n@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS)\n@pytest.mark.parametrize('pretty', PRETTY_MAP.keys())\n@responses.activate\ndef test_terminal_output_response_content_type_charset_with_stream(charset, text, pretty):\n    responses.add(\n        method=responses.GET,\n        url=DUMMY_URL,\n        body=f'<?xml version=\"1.0\"?>\\n<c>{text}</c>'.encode(charset),\n        stream=True,\n        content_type=f'text/xml; charset={charset.upper()}',\n    )\n    r = http('--pretty', pretty, '--stream', DUMMY_URL)\n    assert text in r\n\n\n@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS)\n@pytest.mark.parametrize('pretty', PRETTY_MAP.keys())\n@responses.activate\ndef test_terminal_output_response_charset_override(charset, text, pretty):\n    responses.add(\n        responses.GET,\n        DUMMY_URL,\n        body=text.encode(charset),\n        content_type='text/plain; charset=utf-8',\n    )\n    args = ['--pretty', pretty, DUMMY_URL]\n    if charset != UTF8:\n        # Content-Type charset wrong -> garbled text expected.\n        r = http(*args)\n        assert text not in r\n    r = http('--response-charset', charset, *args)\n    assert text in r\n\n\n@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS)\ndef test_terminal_output_request_content_type_charset(charset, text):\n    r = http(\n        '--offline',\n        DUMMY_URL,\n        f'Content-Type: text/plain; charset={charset.upper()}',\n        env=MockEnvironment(\n            stdin=text.encode(charset),\n            stdin_isatty=False,\n        ),\n    )\n    assert text in r\n\n\n@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS)\ndef test_terminal_output_request_charset_detection(charset, text):\n    r = http(\n        '--offline',\n        DUMMY_URL,\n        'Content-Type: text/plain',\n        env=MockEnvironment(\n            stdin=text.encode(charset),\n            stdin_isatty=False,\n        ),\n    )\n    assert text in r\n"
  },
  {
    "path": "tests/test_errors.py",
    "content": "import pytest\nimport socket\nfrom unittest import mock\nfrom pytest import raises\nfrom requests import Request\nfrom requests.exceptions import ConnectionError\n\nfrom httpie.status import ExitStatus\nfrom .utils import HTTP_OK, http\n\n\n@mock.patch('httpie.core.program')\ndef test_error(program):\n    exc = ConnectionError('Connection aborted')\n    exc.request = Request(method='GET', url='http://www.google.com')\n    program.side_effect = exc\n    r = http('www.google.com', tolerate_error_exit_status=True)\n    assert r.exit_status == ExitStatus.ERROR\n    error_msg = (\n        'ConnectionError: '\n        'Connection aborted while doing a GET request to URL: '\n        'http://www.google.com'\n    )\n    assert error_msg in r.stderr\n\n\n@mock.patch('httpie.core.program')\ndef test_error_traceback(program):\n    exc = ConnectionError('Connection aborted')\n    exc.request = Request(method='GET', url='http://www.google.com')\n    program.side_effect = exc\n    with raises(ConnectionError):\n        http('--traceback', 'www.google.com')\n\n\n@mock.patch('httpie.core.program')\n@pytest.mark.parametrize(\"error_code, expected_message\", [\n    (socket.EAI_AGAIN, \"check your connection\"),\n    (socket.EAI_NONAME, \"check the URL\"),\n])\ndef test_error_custom_dns(program, error_code, expected_message):\n    exc = ConnectionError('Connection aborted')\n    exc.__context__ = socket.gaierror(error_code, \"<test>\")\n    program.side_effect = exc\n\n    r = http('www.google.com', tolerate_error_exit_status=True)\n    assert r.exit_status == ExitStatus.ERROR\n    assert expected_message in r.stderr\n\n\ndef test_max_headers_limit(httpbin_both):\n    with raises(ConnectionError) as e:\n        http('--max-headers=1', httpbin_both + '/get')\n    assert 'got more than 1 headers' in str(e.value)\n\n\ndef test_max_headers_no_limit(httpbin_both):\n    assert HTTP_OK in http('--max-headers=0', httpbin_both + '/get')\n\n\ndef test_response_charset_option_unknown_encoding(httpbin_both):\n    r = http(\n        '--response-charset=foobar',\n        httpbin_both + '/get',\n        tolerate_error_exit_status=True,\n    )\n    assert \"'foobar' is not a supported encoding\" in r.stderr\n\n\ndef test_response_mime_option_invalid_mime_type(httpbin_both):\n    r = http(\n        '--response-mime=foobar',\n        httpbin_both + '/get',\n        tolerate_error_exit_status=True,\n    )\n    assert \"'foobar' doesn’t look like a mime type\" in r.stderr\n"
  },
  {
    "path": "tests/test_exit_status.py",
    "content": "from unittest import mock\n\nfrom httpie.status import ExitStatus\nfrom .utils import MockEnvironment, http, HTTP_OK\n\n\ndef test_keyboard_interrupt_during_arg_parsing_exit_status(httpbin):\n    with mock.patch('httpie.cli.definition.parser.parse_args',\n                    side_effect=KeyboardInterrupt()):\n        r = http('GET', httpbin + '/get', tolerate_error_exit_status=True)\n        assert r.exit_status == ExitStatus.ERROR_CTRL_C\n\n\ndef test_keyboard_interrupt_in_program_exit_status(httpbin):\n    with mock.patch('httpie.core.program',\n                    side_effect=KeyboardInterrupt()):\n        r = http('GET', httpbin + '/get', tolerate_error_exit_status=True)\n        assert r.exit_status == ExitStatus.ERROR_CTRL_C\n\n\ndef test_ok_response_exits_0(httpbin):\n    r = http('GET', httpbin + '/get')\n    assert HTTP_OK in r\n    assert r.exit_status == ExitStatus.SUCCESS\n\n\ndef test_error_response_exits_0_without_check_status(httpbin):\n    r = http('GET', httpbin + '/status/500')\n    assert '500 INTERNAL SERVER ERROR' in r\n    assert r.exit_status == ExitStatus.SUCCESS\n    assert not r.stderr\n\n\ndef test_timeout_exit_status(httpbin):\n\n    r = http('--timeout=0.01', 'GET', httpbin + '/delay/0.5',\n             tolerate_error_exit_status=True)\n    assert r.exit_status == ExitStatus.ERROR_TIMEOUT\n\n\ndef test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected(\n        httpbin):\n    env = MockEnvironment(stdout_isatty=False)\n    r = http('--check-status', '--headers',\n             'GET', httpbin + '/status/301',\n             env=env, tolerate_error_exit_status=True)\n    assert '301 MOVED PERMANENTLY' in r\n    assert r.exit_status == ExitStatus.ERROR_HTTP_3XX\n    assert '301 moved permanently' in r.stderr.lower()\n\n\ndef test_3xx_check_status_redirects_allowed_exits_0(httpbin):\n    r = http('--check-status', '--follow',\n             'GET', httpbin + '/status/301',\n             tolerate_error_exit_status=True)\n    # The redirect will be followed so 200 is expected.\n    assert HTTP_OK in r\n    assert r.exit_status == ExitStatus.SUCCESS\n\n\ndef test_4xx_check_status_exits_4(httpbin):\n    r = http('--check-status', 'GET', httpbin + '/status/401',\n             tolerate_error_exit_status=True)\n    assert '401 UNAUTHORIZED' in r\n    assert r.exit_status == ExitStatus.ERROR_HTTP_4XX\n    # Also stderr should be empty since stdout isn't redirected.\n    assert not r.stderr\n\n\ndef test_5xx_check_status_exits_5(httpbin):\n    r = http('--check-status', 'GET', httpbin + '/status/500',\n             tolerate_error_exit_status=True)\n    assert '500 INTERNAL SERVER ERROR' in r\n    assert r.exit_status == ExitStatus.ERROR_HTTP_5XX\n"
  },
  {
    "path": "tests/test_httpie.py",
    "content": "\"\"\"High-level tests.\"\"\"\nimport io\nfrom unittest import mock\n\nimport pytest\n\nimport httpie\nimport httpie.__main__\nfrom .fixtures import FILE_CONTENT, FILE_PATH\nfrom httpie.cli.exceptions import ParseError\nfrom httpie.context import Environment\nfrom httpie.encoding import UTF8\nfrom httpie.status import ExitStatus\nfrom .utils import HTTP_OK, MockEnvironment, StdinBytesIO, http\n\n\ndef test_main_entry_point():\n    # Patch stdin to bypass pytest capture\n    with mock.patch.object(Environment, 'stdin', io.StringIO()):\n        assert httpie.__main__.main() == ExitStatus.ERROR.value\n\n\n@mock.patch('httpie.core.main')\ndef test_main_entry_point_keyboard_interrupt(main):\n    main.side_effect = KeyboardInterrupt()\n    with mock.patch.object(Environment, 'stdin', io.StringIO()):\n        assert httpie.__main__.main() == ExitStatus.ERROR_CTRL_C.value\n\n\ndef test_debug():\n    r = http('--debug')\n    assert r.exit_status == ExitStatus.SUCCESS\n    assert f'HTTPie {httpie.__version__}' in r.stderr\n\n\ndef test_help():\n    r = http('--help', tolerate_error_exit_status=True)\n    assert r.exit_status == ExitStatus.SUCCESS\n    assert 'https://github.com/httpie/cli/issues' in r\n\n\ndef test_version():\n    r = http('--version', tolerate_error_exit_status=True)\n    assert r.exit_status == ExitStatus.SUCCESS\n    assert httpie.__version__ == r.strip()\n\n\ndef test_GET(httpbin_both):\n    r = http('GET', httpbin_both + '/get')\n    assert HTTP_OK in r\n\n\ndef test_path_dot_normalization():\n    r = http(\n        '--offline',\n        'example.org/../../etc/password',\n        'param==value'\n    )\n    assert 'GET /etc/password?param=value' in r\n\n\ndef test_path_as_is():\n    r = http(\n        '--offline',\n        '--path-as-is',\n        'example.org/../../etc/password',\n        'param==value'\n    )\n    assert 'GET /../../etc/password?param=value' in r\n\n\ndef test_DELETE(httpbin_both):\n    r = http('DELETE', httpbin_both + '/delete')\n    assert HTTP_OK in r\n\n\ndef test_PUT(httpbin_both):\n    r = http('PUT', httpbin_both + '/put', 'foo=bar')\n    assert HTTP_OK in r\n    assert r.json['json']['foo'] == 'bar'\n\n\ndef test_POST_JSON_data(httpbin_both):\n    r = http('POST', httpbin_both + '/post', 'foo=bar')\n    assert HTTP_OK in r\n    assert r.json['json']['foo'] == 'bar'\n\n\ndef test_POST_form(httpbin_both):\n    r = http('--form', 'POST', httpbin_both + '/post', 'foo=bar')\n    assert HTTP_OK in r\n    assert '\"foo\": \"bar\"' in r\n\n\ndef test_POST_form_multiple_values(httpbin_both):\n    r = http('--form', 'POST', httpbin_both + '/post', 'foo=bar', 'foo=baz')\n    assert HTTP_OK in r\n    assert r.json['form'] == {\n        'foo': ['bar', 'baz']\n    }\n\n\ndef test_POST_raw(httpbin_both):\n    r = http('--raw', 'foo bar', 'POST', httpbin_both + '/post')\n    assert HTTP_OK in r\n    assert '\"foo bar\"' in r\n\n\ndef test_POST_stdin(httpbin_both):\n    env = MockEnvironment(\n        stdin=StdinBytesIO(FILE_PATH.read_bytes()),\n        stdin_isatty=False,\n    )\n    r = http('--form', 'POST', httpbin_both + '/post', env=env)\n    assert HTTP_OK in r\n    assert FILE_CONTENT in r\n\n\ndef test_POST_file(httpbin_both):\n    r = http('--form', 'POST', httpbin_both + '/post', f'file@{FILE_PATH}')\n    assert HTTP_OK in r\n    assert FILE_CONTENT in r\n\n\ndef test_form_POST_file_redirected_stdin(httpbin):\n    \"\"\"\n    <https://github.com/httpie/cli/issues/840>\n\n    \"\"\"\n    with open(FILE_PATH, encoding=UTF8):\n        r = http(\n            '--form',\n            'POST',\n            httpbin + '/post',\n            f'file@{FILE_PATH}',\n            tolerate_error_exit_status=True,\n            env=MockEnvironment(\n                stdin=StdinBytesIO(FILE_PATH.read_bytes()),\n                stdin_isatty=False,\n            ),\n        )\n    assert r.exit_status == ExitStatus.ERROR\n    assert 'cannot be mixed' in r.stderr\n\n\ndef test_raw_POST_key_values_supplied(httpbin):\n    r = http(\n        '--raw',\n        'foo bar',\n        'POST',\n        httpbin + '/post',\n        'foo=bar',\n        tolerate_error_exit_status=True,\n    )\n    assert r.exit_status == ExitStatus.ERROR\n    assert 'cannot be mixed' in r.stderr\n\n\ndef test_raw_POST_redirected_stdin(httpbin):\n    r = http(\n        '--raw',\n        'foo bar',\n        'POST',\n        httpbin + '/post',\n        tolerate_error_exit_status=True,\n        env=MockEnvironment(\n            stdin='some=value',\n            stdin_isatty=False,\n        ),\n    )\n    assert r.exit_status == ExitStatus.ERROR\n    assert 'cannot be mixed' in r.stderr\n\n\ndef test_headers(httpbin_both):\n    r = http('GET', httpbin_both + '/headers', 'Foo:bar')\n    assert HTTP_OK in r\n    assert '\"User-Agent\": \"HTTPie' in r, r\n    assert '\"Foo\": \"bar\"' in r\n\n\ndef test_headers_unset(httpbin_both):\n    r = http('GET', httpbin_both + '/headers')\n    assert 'Accept' in r.json['headers']  # default Accept present\n\n    r = http('GET', httpbin_both + '/headers', 'Accept:')\n    assert 'Accept' not in r.json['headers']  # default Accept unset\n\n\n@pytest.mark.skip('unimplemented')\ndef test_unset_host_header(httpbin_both):\n    r = http('GET', httpbin_both + '/headers')\n    assert 'Host' in r.json['headers']  # default Host present\n\n    r = http('GET', httpbin_both + '/headers', 'Host:')\n    assert 'Host' not in r.json['headers']  # default Host unset\n\n\ndef test_unset_useragent_header(httpbin_both):\n    r = http('GET', httpbin_both + '/headers')\n    assert 'User-Agent' in r.json['headers']  # default User-Agent present\n\n    r = http('GET', httpbin_both + '/headers', 'User-Agent:')\n    assert 'User-Agent' not in r.json['headers']  # default User-Agent unset\n\n\ndef test_headers_empty_value(httpbin_both):\n    r = http('GET', httpbin_both + '/headers')\n    assert r.json['headers']['Accept']  # default Accept has value\n\n    r = http('GET', httpbin_both + '/headers', 'Accept;')\n    assert r.json['headers']['Accept'] == ''  # Accept has no value\n\n\ndef test_headers_empty_value_with_value_gives_error(httpbin):\n    with pytest.raises(ParseError):\n        http('GET', httpbin + '/headers', 'Accept;SYNTAX_ERROR')\n\n\ndef test_headers_omit(httpbin_both):\n    r = http('GET', httpbin_both + '/headers', 'Accept:')\n    assert 'Accept' not in r.json['headers']\n\n\ndef test_headers_multiple_omit(httpbin_both):\n    r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Bar:baz',\n             'Foo:', 'Baz:quux')\n    assert 'Foo' not in r.json['headers']\n    assert r.json['headers']['Bar'] == 'baz'\n    assert r.json['headers']['Baz'] == 'quux'\n\n\ndef test_headers_same_after_omit(httpbin_both):\n    r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Foo:',\n             'Foo:quux')\n    assert r.json['headers']['Foo'] == 'quux'\n\n\ndef test_headers_fully_omit(httpbin_both):\n    r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Foo:baz',\n             'Foo:')\n    assert 'Foo' not in r.json['headers']\n\n\ndef test_headers_multiple_values(httpbin_both):\n    r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Foo:baz')\n    assert r.json['headers']['Foo'] == 'bar,baz'\n\n\ndef test_headers_multiple_values_repeated(httpbin_both):\n    r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Foo:baz',\n             'Foo:bar')\n    assert r.json['headers']['Foo'] == 'bar,baz,bar'\n\n\n@pytest.mark.parametrize(\"headers, expected\", [\n    (\n        [\"Foo;\", \"Foo:bar\"],\n        \",bar\"\n    ),\n    (\n        [\"Foo:bar\", \"Foo;\"],\n        \"bar,\"\n    ),\n    (\n        [\"Foo:bar\", \"Foo;\", \"Foo:baz\"],\n        \"bar,,baz\"\n    ),\n])\ndef test_headers_multiple_values_with_empty(httpbin_both, headers, expected):\n    r = http('GET', httpbin_both + '/headers', *headers)\n    assert r.json['headers']['Foo'] == expected\n\n\ndef test_headers_multiple_values_mixed(httpbin_both):\n    r = http('GET', httpbin_both + '/headers', 'Foo:bar', 'Vary:XXX',\n             'Foo:baz', 'Vary:YYY', 'Foo:quux')\n    assert r.json['headers']['Vary'] == 'XXX,YYY'\n    assert r.json['headers']['Foo'] == 'bar,baz,quux'\n\n\ndef test_headers_preserve_prepared_headers(httpbin_both):\n    r = http('POST', httpbin_both + '/post', 'Content-Length:0',\n             '--raw', 'foo')\n    assert r.json['headers']['Content-Length'] == '3'\n\n\n@pytest.mark.parametrize('pretty', ['format', 'none'])\ndef test_headers_multiple_headers_representation(httpbin_both, pretty):\n    r = http('--offline', '--pretty', pretty, 'example.org',\n             'A:A', 'A:B', 'A:C', 'B:A', 'B:B', 'C:C', 'c:c')\n\n    assert 'A: A' in r\n    assert 'A: B' in r\n    assert 'A: C' in r\n    assert 'B: A' in r\n    assert 'B: B' in r\n    assert 'C: C' in r\n    assert 'c: c' in r\n\n\ndef test_response_headers_multiple(http_server):\n    r = http('GET', http_server + '/headers', 'Foo:bar', 'Foo:baz')\n    assert 'Foo: bar' in r\n    assert 'Foo: baz' in r\n\n\ndef test_response_headers_multiple_repeated(http_server):\n    r = http('GET', http_server + '/headers', 'Foo:bar', 'Foo:baz',\n             'Foo:bar')\n    assert r.count('Foo: bar') == 2\n    assert 'Foo: baz' in r\n\n\n@pytest.mark.parametrize('pretty', ['format', 'none'])\ndef test_response_headers_multiple_representation(http_server, pretty):\n    r = http('--pretty', pretty, http_server + '/headers',\n             'A:A', 'A:B', 'A:C', 'B:A', 'B:B', 'C:C', 'C:c')\n\n    assert 'A: A' in r\n    assert 'A: B' in r\n    assert 'A: C' in r\n    assert 'B: A' in r\n    assert 'B: B' in r\n    assert 'C: C' in r\n    assert 'C: c' in r\n\n\ndef test_json_input_preserve_order(httpbin_both):\n    r = http('PATCH', httpbin_both + '/patch',\n             'order:={\"map\":{\"1\":\"first\",\"2\":\"second\"}}')\n    assert HTTP_OK in r\n    assert r.json['data'] == \\\n        '{\"order\": {\"map\": {\"1\": \"first\", \"2\": \"second\"}}}'\n\n\n@pytest.mark.parametrize('extra_args, expected_content_length', [\n    (\n        ['Content-Length:0'],\n        '0'\n    ),\n    (\n        ['Content-Length:xxx'],\n        'xxx',\n    ),\n    (\n        ['--raw=data'],\n        '4'\n    ),\n    (\n        ['query[param]=something'],\n        '33'\n    )\n])\ndef test_options_content_length_preservation(httpbin, extra_args, expected_content_length):\n    r = http(\n        '--offline',\n        'OPTIONS',\n        httpbin + '/anything',\n        *extra_args\n    )\n    assert f'Content-Length: {expected_content_length}' in r\n\n\n@pytest.mark.parametrize('method', ['options', 'Options', 'OPTIONS'])\ndef test_options_dropping_redundant_content_length(httpbin, method):\n    r = http(\n        '--offline',\n        method,\n        httpbin + '/anything'\n    )\n    assert 'Content-Length' not in r\n"
  },
  {
    "path": "tests/test_httpie_cli.py",
    "content": "import pytest\nimport shutil\nimport json\nfrom httpie.sessions import SESSIONS_DIR_NAME\nfrom httpie.status import ExitStatus\nfrom httpie.cli.options import PARSER_SPEC_VERSION\nfrom tests.utils import DUMMY_HOST, httpie\nfrom tests.fixtures import SESSION_FILES_PATH, SESSION_FILES_NEW, SESSION_FILES_OLD, read_session_file\n\n\nOLD_SESSION_FILES_PATH = SESSION_FILES_PATH / 'old'\n\n\n@pytest.mark.requires_installation\ndef test_plugins_cli_error_message_without_args():\n    # No arguments\n    result = httpie(no_debug=True)\n    assert result.exit_status == ExitStatus.ERROR\n    assert 'usage: ' in result.stderr\n    assert 'specify one of these' in result.stderr\n    assert 'please use the http/https commands:' in result.stderr\n\n\n@pytest.mark.parametrize(\n    'example',\n    [\n        'pie.dev/get',\n        'DELETE localhost:8000/delete',\n        'POST pie.dev/post header:value a=b header_2:value x:=1',\n    ],\n)\n@pytest.mark.requires_installation\ndef test_plugins_cli_error_messages_with_example(example):\n    result = httpie(*example.split(), no_debug=True)\n    assert result.exit_status == ExitStatus.ERROR\n    assert 'usage: ' in result.stderr\n    assert f'http {example}' in result.stderr\n    assert f'https {example}' in result.stderr\n\n\n@pytest.mark.parametrize(\n    'example',\n    [\n        'cli',\n        'plugins',\n        'cli foo',\n        'plugins unknown',\n        'plugins unknown.com A:B c=d',\n        'unknown.com UNPARSABLE????SYNTAX',\n    ],\n)\n@pytest.mark.requires_installation\ndef test_plugins_cli_error_messages_invalid_example(example):\n    result = httpie(*example.split(), no_debug=True)\n    assert result.exit_status == ExitStatus.ERROR\n    assert 'usage: ' in result.stderr\n    assert f'http {example}' not in result.stderr\n    assert f'https {example}' not in result.stderr\n\n\nHTTPIE_CLI_SESSIONS_UPGRADE_OPTIONS = [\n    (\n        # Default settings\n        [],\n        {'__host__': json.dumps(None)},\n    ),\n    (\n        # When --bind-cookies is applied, the __host__ becomes DUMMY_URL.\n        ['--bind-cookies'],\n        {'__host__': json.dumps(DUMMY_HOST)},\n    ),\n]\n\n\n@pytest.mark.parametrize(\n    'old_session_file, new_session_file', zip(SESSION_FILES_OLD, SESSION_FILES_NEW)\n)\n@pytest.mark.parametrize(\n    'extra_args, extra_variables',\n    HTTPIE_CLI_SESSIONS_UPGRADE_OPTIONS,\n)\ndef test_httpie_sessions_upgrade(tmp_path, old_session_file, new_session_file, extra_args, extra_variables):\n    session_path = tmp_path / 'session.json'\n    shutil.copyfile(old_session_file, session_path)\n\n    result = httpie(\n        'cli', 'sessions', 'upgrade', *extra_args, DUMMY_HOST, str(session_path)\n    )\n    assert result.exit_status == ExitStatus.SUCCESS\n    assert read_session_file(session_path) == read_session_file(\n        new_session_file, extra_variables=extra_variables\n    )\n\n\ndef test_httpie_sessions_upgrade_on_non_existent_file(tmp_path):\n    session_path = tmp_path / 'session.json'\n    result = httpie('cli', 'sessions', 'upgrade', DUMMY_HOST, str(session_path))\n    assert result.exit_status == ExitStatus.ERROR\n    assert 'does not exist' in result.stderr\n\n\n@pytest.mark.parametrize(\n    'extra_args, extra_variables',\n    HTTPIE_CLI_SESSIONS_UPGRADE_OPTIONS,\n)\ndef test_httpie_sessions_upgrade_all(tmp_path, mock_env, extra_args, extra_variables):\n    mock_env._create_temp_config_dir = False\n    mock_env.config_dir = tmp_path / \"config\"\n\n    session_dir = mock_env.config_dir / SESSIONS_DIR_NAME / DUMMY_HOST\n    session_dir.mkdir(parents=True)\n    for original_session_file in SESSION_FILES_OLD:\n        shutil.copy(original_session_file, session_dir)\n\n    result = httpie(\n        'cli', 'sessions', 'upgrade-all', *extra_args, env=mock_env\n    )\n    assert result.exit_status == ExitStatus.SUCCESS\n\n    for refactored_session_file, expected_session_file in zip(\n        sorted(session_dir.glob(\"*.json\")),\n        SESSION_FILES_NEW\n    ):\n        assert read_session_file(refactored_session_file) == read_session_file(\n            expected_session_file, extra_variables=extra_variables\n        )\n\n\n@pytest.mark.parametrize(\n    'load_func, extra_options', [\n        (json.loads, []),\n        (json.loads, ['--format=json'])\n    ]\n)\ndef test_cli_export(load_func, extra_options):\n    response = httpie('cli', 'export-args', *extra_options)\n    assert response.exit_status == ExitStatus.SUCCESS\n    assert load_func(response)['version'] == PARSER_SPEC_VERSION\n"
  },
  {
    "path": "tests/test_json.py",
    "content": "import json\n\nimport pytest\nimport responses\n\nfrom httpie.cli.constants import PRETTY_MAP\nfrom httpie.cli.exceptions import ParseError\nfrom httpie.cli.nested_json import NestedJSONSyntaxError\nfrom httpie.output.formatters.colors import ColorFormatter\nfrom httpie.utils import JsonDictPreservingDuplicateKeys\n\nfrom .fixtures import (\n    FILE_CONTENT,\n    FILE_PATH,\n    JSON_FILE_CONTENT,\n    JSON_FILE_PATH,\n    JSON_WITH_DUPE_KEYS_FILE_PATH,\n)\nfrom .utils import DUMMY_URL, MockEnvironment, http\n\nTEST_JSON_XXSI_PREFIXES = [\n    r\")]}',\\n\",\n    \")]}',\",\n    'while(1);',\n    'for(;;)',\n    ')',\n    ']',\n    '}',\n]\nTEST_JSON_VALUES = [\n    # FIXME: missing int & float\n    {},\n    {'a': 0, 'b': 0},\n    [],\n    ['a', 'b'],\n    'foo',\n    True,\n    False,\n    None,\n]\nTEST_PREFIX_TOKEN_COLOR = '\\x1b[04m\\x1b[91m'\n\nJSON_WITH_DUPES_RAW = '{\"key\": 15, \"key\": 15, \"key\": 3, \"key\": 7}'\nJSON_WITH_DUPES_FORMATTED_SORTED = \"\"\"{\n    \"key\": 3,\n    \"key\": 7,\n    \"key\": 15,\n    \"key\": 15\n}\"\"\"\nJSON_WITH_DUPES_FORMATTED_UNSORTED = \"\"\"{\n    \"key\": 15,\n    \"key\": 15,\n    \"key\": 3,\n    \"key\": 7\n}\"\"\"\n\n\n@pytest.mark.parametrize('data_prefix', TEST_JSON_XXSI_PREFIXES)\n@pytest.mark.parametrize('json_data', TEST_JSON_VALUES)\n@pytest.mark.parametrize('pretty', PRETTY_MAP.keys())\n@responses.activate\ndef test_json_formatter_with_body_preceded_by_non_json_data(\n    data_prefix, json_data, pretty\n):\n    \"\"\"Test JSON bodies preceded by non-JSON data.\"\"\"\n    body = data_prefix + json.dumps(json_data)\n    content_type = 'application/json;charset=utf8'\n    responses.add(\n        responses.GET,\n        DUMMY_URL,\n        body=body,\n        content_type=content_type,\n    )\n\n    colored_output = pretty in {'all', 'colors'}\n    env = MockEnvironment(colors=256) if colored_output else None\n    r = http('--pretty', pretty, DUMMY_URL, env=env)\n\n    indent = None if pretty in {'none', 'colors'} else 4\n    expected_body = data_prefix + json.dumps(json_data, indent=indent)\n    if colored_output:\n        fmt = ColorFormatter(\n            env, format_options={'json': {'format': True, 'indent': 4}}\n        )\n        expected_body = fmt.format_body(expected_body, content_type)\n        # Check to ensure the non-JSON data prefix is colored only one time,\n        # meaning it was correctly handled as a whole.\n        assert (\n            TEST_PREFIX_TOKEN_COLOR + data_prefix in expected_body\n        ), expected_body\n    assert expected_body in r\n\n\n@responses.activate\ndef test_duplicate_keys_support_from_response():\n    \"\"\"JSON with duplicate keys should be handled correctly.\"\"\"\n    responses.add(\n        responses.GET,\n        DUMMY_URL,\n        body=JSON_WITH_DUPES_RAW,\n        content_type='application/json',\n    )\n    args = ('--pretty', 'format', DUMMY_URL)\n\n    # Check implicit --sorted\n    if JsonDictPreservingDuplicateKeys.SUPPORTS_SORTING:\n        r = http(*args)\n        assert JSON_WITH_DUPES_FORMATTED_SORTED in r\n\n    # Check --unsorted\n    r = http(*args, '--unsorted')\n    assert JSON_WITH_DUPES_FORMATTED_UNSORTED in r\n\n\ndef test_duplicate_keys_support_from_input_file():\n    \"\"\"JSON file with duplicate keys should be handled correctly.\"\"\"\n    args = (\n        '--verbose',\n        '--offline',\n        DUMMY_URL,\n        f'@{JSON_WITH_DUPE_KEYS_FILE_PATH}',\n    )\n\n    # Check implicit --sorted\n    if JsonDictPreservingDuplicateKeys.SUPPORTS_SORTING:\n        r = http(*args)\n        assert JSON_WITH_DUPES_FORMATTED_SORTED in r\n\n    # Check --unsorted\n    r = http(*args, '--unsorted')\n    assert JSON_WITH_DUPES_FORMATTED_UNSORTED in r\n\n\n@pytest.mark.parametrize('value', [1, 1.1, True, 'some_value'])\ndef test_simple_json_arguments_with_non_json(httpbin, value):\n    r = http(\n        '--form',\n        httpbin + '/post',\n        f'option:={json.dumps(value)}',\n    )\n    assert r.json['form'] == {'option': str(value)}\n\n\n@pytest.mark.parametrize(\n    'request_type',\n    [\n        '--form',\n        '--multipart',\n    ],\n)\n@pytest.mark.parametrize('value', [[1, 2, 3], {'a': 'b'}, None])\ndef test_complex_json_arguments_with_non_json(httpbin, request_type, value):\n    with pytest.raises(ParseError) as cm:\n        http(\n            request_type,\n            httpbin + '/post',\n            f'option:={json.dumps(value)}',\n        )\n\n    cm.match('Cannot use complex JSON value types')\n\n\n@pytest.mark.parametrize(\n    'input_json, expected_json',\n    [\n        # Examples taken from https://www.w3.org/TR/html-json-forms/\n        (\n            [\n                'bottle-on-wall[]:=1',\n                'bottle-on-wall[]:=2',\n                'bottle-on-wall[]:=3',\n            ],\n            {'bottle-on-wall': [1, 2, 3]},\n        ),\n        (\n            [\n                'pet[species]=Dahut',\n                'pet[name]:=\"Hypatia\"',\n                'kids[1]=Thelma',\n                'kids[0]:=\"Ashley\"',\n            ],\n            {\n                'pet': {'species': 'Dahut', 'name': 'Hypatia'},\n                'kids': ['Ashley', 'Thelma'],\n            },\n        ),\n        (\n            [\n                'pet[0][species]=Dahut',\n                'pet[0][name]=Hypatia',\n                'pet[1][species]=Felis Stultus',\n                'pet[1][name]:=\"Billie\"',\n            ],\n            {\n                'pet': [\n                    {'species': 'Dahut', 'name': 'Hypatia'},\n                    {'species': 'Felis Stultus', 'name': 'Billie'},\n                ]\n            },\n        ),\n        (\n            ['wow[such][deep][3][much][power][!]=Amaze'],\n            {\n                'wow': {\n                    'such': {\n                        'deep': [\n                            None,\n                            None,\n                            None,\n                            {'much': {'power': {'!': 'Amaze'}}},\n                        ]\n                    }\n                }\n            },\n        ),\n        (\n            ['mix[]=scalar', 'mix[2]=something', 'mix[4]:=\"something 2\"'],\n            {'mix': ['scalar', None, 'something', None, 'something 2']},\n        ),\n        (\n            ['highlander[]=one'],\n            {'highlander': ['one']},\n        ),\n        (\n            ['error[good]=BOOM!', r'error\\[bad:=\"BOOM BOOM!\"'],\n            {'error': {'good': 'BOOM!'}, 'error[bad': 'BOOM BOOM!'},\n        ),\n        (\n            [\n                'special[]:=true',\n                'special[]:=false',\n                'special[]:=\"true\"',\n                'special[]:=null',\n            ],\n            {'special': [True, False, 'true', None]},\n        ),\n        (\n            [\n                r'\\[\\]:=1',\n                r'escape\\[d\\]:=1',\n                r'escaped\\[\\]:=1',\n                r'e\\[s\\][c][a][p][\\[ed\\]][]:=1',\n            ],\n            {\n                '[]': 1,\n                'escape[d]': 1,\n                'escaped[]': 1,\n                'e[s]': {'c': {'a': {'p': {'[ed]': [1]}}}},\n            },\n        ),\n        (\n            ['[]:=1', '[]=foo'],\n            [1, 'foo'],\n        ),\n        (\n            [r'\\]:=1', r'\\[\\]1:=1', r'\\[1\\]\\]:=1'],\n            {']': 1, '[]1': 1, '[1]]': 1},\n        ),\n        (\n            [\n                r'foo\\[bar\\][baz]:=1',\n                r'foo\\[bar\\]\\[baz\\]:=3',\n                r'foo[bar][\\[baz\\]]:=4',\n            ],\n            {\n                'foo[bar]': {'baz': 1},\n                'foo[bar][baz]': 3,\n                'foo': {'bar': {'[baz]': 4}},\n            },\n        ),\n        (\n            ['key[]:=1', 'key[][]:=2', 'key[][][]:=3', 'key[][][]:=4'],\n            {'key': [1, [2], [[3]], [[4]]]},\n        ),\n        (\n            ['x[0]:=1', 'x[]:=2', 'x[]:=3', 'x[][]:=4', 'x[][]:=5'],\n            {'x': [1, 2, 3, [4], [5]]},\n        ),\n        (\n            [\n                f'x=@{FILE_PATH}',\n                f'y[z]=@{FILE_PATH}',\n                f'q[u][]:=@{JSON_FILE_PATH}',\n            ],\n            {\n                'x': FILE_CONTENT,\n                'y': {'z': FILE_CONTENT},\n                'q': {'u': [json.loads(JSON_FILE_CONTENT)]},\n            },\n        ),\n        (\n            [\n                'foo[bar][5][]:=5',\n                'foo[bar][]:=6',\n                'foo[bar][][]:=7',\n                'foo[bar][][x]=dfasfdas',\n                'foo[baz]:=[1, 2, 3]',\n                'foo[baz][]:=4',\n            ],\n            {\n                'foo': {\n                    'bar': [\n                        None,\n                        None,\n                        None,\n                        None,\n                        None,\n                        [5],\n                        6,\n                        [7],\n                        {'x': 'dfasfdas'},\n                    ],\n                    'baz': [1, 2, 3, 4],\n                }\n            },\n        ),\n        (\n            [\n                'foo[]:=1',\n                'foo[]:=2',\n                'foo[][key]=value',\n                'foo[2][key 2]=value 2',\n                r'foo[2][key \\[]=value 3',\n                r'bar[nesting][under][!][empty][?][\\\\key]:=4',\n            ],\n            {\n                'foo': [\n                    1,\n                    2,\n                    {'key': 'value', 'key 2': 'value 2', 'key [': 'value 3'},\n                ],\n                'bar': {\n                    'nesting': {'under': {'!': {'empty': {'?': {'\\\\key': 4}}}}}\n                },\n            },\n        ),\n        (\n            [\n                r'foo\\[key\\]:=1',\n                r'bar\\[1\\]:=2',\n                r'baz\\[\\]:3',\n                r'quux[key\\[escape\\]]:=4',\n                r'quux[key 2][\\\\][\\\\\\\\][\\\\\\[\\]\\\\\\]\\\\\\[\\n\\\\]:=5',\n            ],\n            {\n                'foo[key]': 1,\n                'bar[1]': 2,\n                'quux': {\n                    'key[escape]': 4,\n                    'key 2': {'\\\\': {'\\\\\\\\': {'\\\\[]\\\\]\\\\[\\\\n\\\\': 5}}},\n                },\n            },\n        ),\n        (\n            [r'A[B\\\\]=C', r'A[B\\\\\\\\]=C', r'A[\\B\\\\]=C'],\n            {'A': {'B\\\\': 'C', 'B\\\\\\\\': 'C', '\\\\B\\\\': 'C'}},\n        ),\n        (\n            [\n                'name=python',\n                'version:=3',\n                'date[year]:=2021',\n                'date[month]=December',\n                'systems[]=Linux',\n                'systems[]=Mac',\n                'systems[]=Windows',\n                'people[known_ids][1]:=1000',\n                'people[known_ids][5]:=5000',\n            ],\n            {\n                'name': 'python',\n                'version': 3,\n                'date': {'year': 2021, 'month': 'December'},\n                'systems': ['Linux', 'Mac', 'Windows'],\n                'people': {'known_ids': [None, 1000, None, None, None, 5000]},\n            },\n        ),\n        (\n            [\n                r'foo[\\1][type]=migration',\n                r'foo[\\2][type]=migration',\n                r'foo[\\dates]:=[2012, 2013]',\n                r'foo[\\dates][0]:=2014',\n                r'foo[\\2012 bleh]:=2013',\n                r'foo[bleh \\2012]:=2014',\n                r'\\2012[x]:=2',\n                r'\\2012[\\[3\\]]:=4',\n            ],\n            {\n                'foo': {\n                    '1': {'type': 'migration'},\n                    '2': {'type': 'migration'},\n                    '\\\\dates': [2014, 2013],\n                    '\\\\2012 bleh': 2013,\n                    'bleh \\\\2012': 2014,\n                },\n                '2012': {'x': 2, '[3]': 4},\n            },\n        ),\n        (\n            [\n                r'a[\\0]:=0',\n                r'a[\\\\1]:=1',\n                r'a[\\\\\\2]:=2',\n                r'a[\\\\\\\\\\3]:=3',\n                r'a[-1\\\\]:=-1',\n                r'a[-2\\\\\\\\]:=-2',\n                r'a[\\\\-3\\\\\\\\]:=-3',\n            ],\n            {\n                'a': {\n                    '0': 0,\n                    r'\\1': 1,\n                    r'\\\\2': 2,\n                    r'\\\\\\3': 3,\n                    '-1\\\\': -1,\n                    '-2\\\\\\\\': -2,\n                    '\\\\-3\\\\\\\\': -3,\n                }\n            },\n        ),\n        (\n            ['[]:=0', '[]:=1', '[5]:=5', '[]:=6', '[9]:=9'],\n            [0, 1, None, None, None, 5, 6, None, None, 9],\n        ),\n        (\n            ['=empty', 'foo=bar', 'bar[baz][quux]=tuut'],\n            {'': 'empty', 'foo': 'bar', 'bar': {'baz': {'quux': 'tuut'}}},\n        ),\n        (\n            [\n                r'\\1=top level int',\n                r'\\\\1=escaped top level int',\n                r'\\2[\\3][\\4]:=5',\n            ],\n            {\n                '1': 'top level int',\n                '\\\\1': 'escaped top level int',\n                '2': {'3': {'4': 5}},\n            },\n        ),\n        (\n            [':={\"foo\": {\"bar\": \"baz\"}}', 'top=val'],\n            {'': {'foo': {'bar': 'baz'}}, 'top': 'val'},\n        ),\n        (\n            ['[][a][b][]:=1', '[0][a][b][]:=2', '[][]:=2'],\n            [{'a': {'b': [1, 2]}}, [2]],\n        ),\n        ([':=[1,2,3]'], {'': [1, 2, 3]}),\n        ([':=[1,2,3]', 'foo=bar'], {'': [1, 2, 3], 'foo': 'bar'}),\n    ],\n)\ndef test_nested_json_syntax(input_json, expected_json, httpbin):\n    r = http(httpbin + '/post', *input_json)\n    assert r.json['json'] == expected_json\n\n\n@pytest.mark.parametrize(\n    'input_json, expected_error',\n    [\n        (\n            ['A[:=1'],\n            \"HTTPie Syntax Error: Expecting a text, a number or ']'\\nA[\\n  ^\",\n        ),\n        (['A[1:=1'], \"HTTPie Syntax Error: Expecting ']'\\nA[1\\n   ^\"),\n        (['A[text:=1'], \"HTTPie Syntax Error: Expecting ']'\\nA[text\\n      ^\"),\n        (\n            ['A[text][:=1'],\n            \"HTTPie Syntax Error: Expecting a text, a number or ']'\\nA[text][\\n        ^\",\n        ),\n        (\n            ['A[key]=value', 'B[something]=u', 'A[text][:=1', 'C[key]=value'],\n            \"HTTPie Syntax Error: Expecting a text, a number or ']'\\nA[text][\\n        ^\",\n        ),\n        (\n            ['A[text]1:=1'],\n            \"HTTPie Syntax Error: Expecting '['\\nA[text]1\\n       ^\",\n        ),\n        (['A\\\\[]:=1'], \"HTTPie Syntax Error: Expecting '['\\nA\\\\[]\\n   ^\"),\n        (\n            ['A[something\\\\]:=1'],\n            \"HTTPie Syntax Error: Expecting ']'\\nA[something\\\\]\\n             ^\",\n        ),\n        (\n            ['foo\\\\[bar\\\\]\\\\\\\\[   bleh:=1'],\n            \"HTTPie Syntax Error: Expecting ']'\\nfoo\\\\[bar\\\\]\\\\\\\\[   bleh\\n                    ^\",\n        ),\n        (\n            ['foo\\\\[bar\\\\]\\\\\\\\[   bleh   :=1'],\n            \"HTTPie Syntax Error: Expecting ']'\\nfoo\\\\[bar\\\\]\\\\\\\\[   bleh   \\n                       ^\",\n        ),\n        (\n            ['foo[bar][1]][]:=2'],\n            \"HTTPie Syntax Error: Expecting '['\\nfoo[bar][1]][]\\n           ^\",\n        ),\n        (\n            ['foo[bar][1]something[]:=2'],\n            \"HTTPie Syntax Error: Expecting '['\\nfoo[bar][1]something[]\\n           ^^^^^^^^^\",\n        ),\n        (\n            ['foo[bar][1][142241[]:=2'],\n            \"HTTPie Syntax Error: Expecting ']'\\nfoo[bar][1][142241[]\\n                  ^\",\n        ),\n        (\n            ['foo[bar][1]\\\\[142241[]:=2'],\n            \"HTTPie Syntax Error: Expecting '['\\nfoo[bar][1]\\\\[142241[]\\n           ^^^^^^^^\",\n        ),\n        (\n            ['foo=1', 'foo[key]:=2'],\n            \"HTTPie Type Error: Cannot perform 'key' based access on 'foo' which has a type of 'string' but this operation requires a type of 'object'.\\nfoo[key]\\n   ^^^^^\",\n        ),\n        (\n            ['foo=1', 'foo[0]:=2'],\n            \"HTTPie Type Error: Cannot perform 'index' based access on 'foo' which has a type of 'string' but this operation requires a type of 'array'.\\nfoo[0]\\n   ^^^\",\n        ),\n        (\n            ['foo=1', 'foo[]:=2'],\n            \"HTTPie Type Error: Cannot perform 'append' based access on 'foo' which has a type of 'string' but this operation requires a type of 'array'.\\nfoo[]\\n   ^^\",\n        ),\n        (\n            ['data[key]=value', 'data[key 2]=value 2', 'data[0]=value'],\n            \"HTTPie Type Error: Cannot perform 'index' based access on 'data' which has a type of 'object' but this operation requires a type of 'array'.\\ndata[0]\\n    ^^^\",\n        ),\n        (\n            ['data[key]=value', 'data[key 2]=value 2', 'data[]=value'],\n            \"HTTPie Type Error: Cannot perform 'append' based access on 'data' which has a type of 'object' but this operation requires a type of 'array'.\\ndata[]\\n    ^^\",\n        ),\n        (\n            [\n                'foo[bar][baz][5]:=[1,2,3]',\n                'foo[bar][baz][5][]:=4',\n                'foo[bar][baz][key][]:=5',\n            ],\n            \"HTTPie Type Error: Cannot perform 'key' based access on 'foo[bar][baz]' which has a type of 'array' but this operation requires a type of 'object'.\\nfoo[bar][baz][key][]\\n             ^^^^^\",\n        ),\n        (\n            ['foo[-10]:=[1,2]'],\n            'HTTPie Value Error: Negative indexes are not supported.\\nfoo[-10]\\n    ^^^',\n        ),\n        (\n            ['foo[0]:=1', 'foo[]:=2', 'foo[\\\\2]:=3'],\n            \"HTTPie Type Error: Cannot perform 'key' based access on 'foo' which has a type of 'array' but this operation requires a type of 'object'.\\nfoo[\\\\2]\\n   ^^^^\",\n        ),\n        (\n            ['foo[\\\\1]:=2', 'foo[5]:=3'],\n            \"HTTPie Type Error: Cannot perform 'index' based access on 'foo' which has a type of 'object' but this operation requires a type of 'array'.\\nfoo[5]\\n   ^^^\",\n        ),\n        (\n            ['x=y', '[]:=2'],\n            \"HTTPie Type Error: Cannot perform 'append' based access on '' which has a type of 'object' but this operation requires a type of 'array'.\",\n        ),\n        (\n            ['[]:=2', 'x=y'],\n            \"HTTPie Type Error: Cannot perform 'key' based access on '' which has a type of 'array' but this operation requires a type of 'object'.\",\n        ),\n        (\n            [':=[1,2,3]', '[]:=4'],\n            \"HTTPie Type Error: Cannot perform 'append' based access on '' which has a type of 'object' but this operation requires a type of 'array'.\",\n        ),\n        (\n            ['[]:=4', ':=[1,2,3]'],\n            \"HTTPie Type Error: Cannot perform 'key' based access on '' which has a type of 'array' but this operation requires a type of 'object'.\",\n        ),\n    ],\n)\ndef test_nested_json_errors(input_json, expected_error, httpbin):\n    with pytest.raises(NestedJSONSyntaxError) as exc:\n        http(httpbin + '/post', *input_json)\n\n    exc_lines = str(exc.value).splitlines()\n    expected_lines = expected_error.splitlines()\n    if len(expected_lines) == 1:\n        # When the error offsets are not important, we'll just compare the actual\n        # error message.\n        exc_lines = exc_lines[:1]\n\n    assert expected_lines == exc_lines\n\n\ndef test_nested_json_sparse_array(httpbin_both):\n    r = http(httpbin_both + '/post', 'test[0]:=1', 'test[100]:=1')\n    assert len(r.json['json']['test']) == 101\n"
  },
  {
    "path": "tests/test_meta.py",
    "content": "import pytest\n\nfrom httpie.models import ELAPSED_TIME_LABEL\nfrom httpie.output.formatters.colors import PIE_STYLE_NAMES\nfrom .utils import http, MockEnvironment, COLOR\n\n\ndef test_meta_elapsed_time(httpbin):\n    r = http('--meta', httpbin + '/delay/1')\n    assert f'{ELAPSED_TIME_LABEL}: 1.' in r\n\n\n@pytest.mark.parametrize('style', ['auto', 'fruity', *PIE_STYLE_NAMES])\ndef test_meta_elapsed_time_colors(httpbin, style):\n    r = http('--style', style, '--meta', httpbin + '/get', env=MockEnvironment(colors=256))\n    assert COLOR in r\n    assert ELAPSED_TIME_LABEL in r\n"
  },
  {
    "path": "tests/test_offline.py",
    "content": "from .fixtures import FILE_CONTENT, FILE_PATH_ARG\nfrom .utils import http\n\n\ndef test_offline():\n    r = http(\n        '--offline',\n        'https://this-should.never-resolve/foo',\n    )\n    assert 'GET /foo' in r\n\n\ndef test_offline_raw():\n    r = http(\n        '--offline',\n        '--raw',\n        'foo bar',\n        'https://this-should.never-resolve/foo',\n    )\n    assert 'POST /foo' in r\n    assert 'foo bar' in r\n\n\ndef test_offline_raw_empty_should_use_POST():\n    r = http(\n        '--offline',\n        '--raw',\n        '',\n        'https://this-should.never-resolve/foo',\n    )\n    assert 'POST /foo' in r\n\n\ndef test_offline_form():\n    r = http(\n        '--offline',\n        '--form',\n        'https://this-should.never-resolve/foo',\n        'foo=bar'\n    )\n    assert 'POST /foo' in r\n    assert 'foo=bar' in r\n\n\ndef test_offline_json():\n    r = http(\n        '--offline',\n        'https://this-should.never-resolve/foo',\n        'foo=bar'\n    )\n    assert 'POST /foo' in r\n    assert r.json == {'foo': 'bar'}\n\n\ndef test_offline_multipart():\n    r = http(\n        '--offline',\n        '--multipart',\n        'https://this-should.never-resolve/foo',\n        'foo=bar'\n    )\n    assert 'POST /foo' in r\n    assert 'name=\"foo\"' in r\n\n\ndef test_offline_from_file():\n    r = http(\n        '--offline',\n        'https://this-should.never-resolve/foo',\n        f'@{FILE_PATH_ARG}'\n    )\n    assert 'POST /foo' in r\n    assert FILE_CONTENT in r\n\n\ndef test_offline_chunked():\n    r = http(\n        '--offline',\n        '--chunked',\n        '--form',\n        'https://this-should.never-resolve/foo',\n        'hello=world'\n    )\n    assert 'POST /foo' in r\n    assert 'Transfer-Encoding: chunked' in r, r\n    assert 'hello=world' in r\n\n\ndef test_offline_download():\n    \"\"\"Absence of response should be handled gracefully with --download\"\"\"\n    r = http(\n        '--offline',\n        '--download',\n        'https://this-should.never-resolve/foo',\n    )\n    assert 'GET /foo' in r\n"
  },
  {
    "path": "tests/test_output.py",
    "content": "import argparse\nfrom pathlib import Path\nfrom unittest import mock\n\nimport json\nimport os\nimport io\nimport warnings\nfrom urllib.request import urlopen\n\nimport pytest\nimport requests\nimport responses\n\nfrom httpie.cli.argtypes import (\n    PARSED_DEFAULT_FORMAT_OPTIONS,\n    parse_format_options,\n)\nfrom httpie.cli.definition import parser\nfrom httpie.encoding import UTF8\nfrom httpie.output.formatters.colors import get_lexer, PIE_STYLE_NAMES, BUNDLED_STYLES\nfrom httpie.status import ExitStatus\nfrom .fixtures import XML_DATA_RAW, XML_DATA_FORMATTED\nfrom .utils import COLOR, CRLF, HTTP_OK, MockEnvironment, http, DUMMY_URL, strip_colors\n\n\n# For ensuring test reproducibility, avoid using the unsorted\n# BUNDLED_STYLES set.\nSORTED_BUNDLED_STYLES = sorted(BUNDLED_STYLES)\n\n\n@pytest.mark.parametrize('stdout_isatty', [True, False])\ndef test_output_option(tmp_path, httpbin, stdout_isatty):\n    output_filename = tmp_path / 'test_output_option'\n    url = httpbin + '/robots.txt'\n\n    r = http('--output', str(output_filename), url,\n             env=MockEnvironment(stdout_isatty=stdout_isatty))\n    assert r == ''\n\n    expected_body = urlopen(url).read().decode()\n    actual_body = output_filename.read_text(encoding=UTF8)\n\n    assert actual_body == expected_body\n\n\nclass TestQuietFlag:\n    QUIET_SCENARIOS = [('--quiet',), ('-q',), ('--quiet', '--quiet'), ('-qq',)]\n\n    @pytest.mark.parametrize('quiet_flags', QUIET_SCENARIOS)\n    def test_quiet(self, httpbin, quiet_flags):\n        env = MockEnvironment(\n            stdin_isatty=True,\n            stdout_isatty=True,\n            devnull=io.BytesIO()\n        )\n        r = http(*quiet_flags, 'GET', httpbin + '/get', env=env)\n        assert env.stdout is env.devnull\n        assert env.stderr is env.devnull\n        assert HTTP_OK in r.devnull\n        assert r == ''\n        assert r.stderr == ''\n\n    def test_quiet_with_check_status_non_zero(self, httpbin):\n        r = http(\n            '--quiet', '--check-status', httpbin + '/status/500',\n            tolerate_error_exit_status=True,\n        )\n        assert 'http: warning: HTTP 500' in r.stderr\n\n    def test_quiet_with_check_status_non_zero_pipe(self, httpbin):\n        r = http(\n            '--quiet', '--check-status', httpbin + '/status/500',\n            tolerate_error_exit_status=True,\n            env=MockEnvironment(stdout_isatty=False)\n        )\n        assert 'http: warning: HTTP 500' in r.stderr\n\n    def test_quiet_quiet_with_check_status_non_zero(self, httpbin):\n        r = http(\n            '--quiet', '--quiet', '--check-status', httpbin + '/status/500',\n            tolerate_error_exit_status=True,\n        )\n        assert not r.stderr\n\n    def test_quiet_quiet_with_check_status_non_zero_pipe(self, httpbin):\n        r = http(\n            '--quiet', '--quiet', '--check-status', httpbin + '/status/500',\n            tolerate_error_exit_status=True,\n            env=MockEnvironment(stdout_isatty=False)\n        )\n        assert 'http: warning: HTTP 500' in r.stderr\n\n    @mock.patch('httpie.core.program')\n    @pytest.mark.parametrize('flags, expected_warnings', [\n        ([], 1),\n        (['-q'], 1),\n        (['-qq'], 0),\n    ])\n    # Might fail on Windows due to interference from other warnings.\n    @pytest.mark.xfail\n    def test_quiet_on_python_warnings(self, test_patch, httpbin, flags, expected_warnings):\n        def warn_and_run(*args, **kwargs):\n            warnings.warn('warning!!')\n            return ExitStatus.SUCCESS\n\n        test_patch.side_effect = warn_and_run\n        with pytest.warns(None) as record:\n            http(*flags, httpbin + '/get')\n\n        assert len(record) == expected_warnings\n\n    def test_double_quiet_on_error(self, httpbin):\n        r = http(\n            '-qq', '--check-status', '$$$this.does.not.exist$$$',\n            tolerate_error_exit_status=True,\n        )\n        assert not r\n        assert 'Couldn’t resolve the given hostname' in r.stderr\n\n    @pytest.mark.parametrize('quiet_flags', QUIET_SCENARIOS)\n    @mock.patch('httpie.cli.argtypes.AuthCredentials._getpass',\n                new=lambda self, prompt: 'password')\n    def test_quiet_with_password_prompt(self, httpbin, quiet_flags):\n        \"\"\"\n        Tests whether httpie still prompts for a password when request\n        requires authentication and only username is provided\n\n        \"\"\"\n        env = MockEnvironment(\n            stdin_isatty=True,\n            stdout_isatty=True,\n            devnull=io.BytesIO()\n        )\n        r = http(\n            *quiet_flags, '--auth', 'user', 'GET',\n            httpbin + '/basic-auth/user/password',\n            env=env\n        )\n        assert env.stdout is env.devnull\n        assert env.stderr is env.devnull\n        assert HTTP_OK in r.devnull\n        assert r == ''\n        assert r.stderr == ''\n\n    @pytest.mark.parametrize('quiet_flags', QUIET_SCENARIOS)\n    @pytest.mark.parametrize('output_options', ['-h', '-b', '-v', '-p=hH'])\n    def test_quiet_with_explicit_output_options(self, httpbin, quiet_flags, output_options):\n        env = MockEnvironment(stdin_isatty=True, stdout_isatty=True)\n        r = http(*quiet_flags, output_options, httpbin + '/get', env=env)\n        assert env.stdout is env.devnull\n        assert env.stderr is env.devnull\n        assert r == ''\n        assert r.stderr == ''\n\n    @pytest.mark.parametrize('quiet_flags', QUIET_SCENARIOS)\n    @pytest.mark.parametrize('with_download', [True, False])\n    def test_quiet_with_output_redirection(self, tmp_path, httpbin, quiet_flags, with_download):\n        url = httpbin + '/robots.txt'\n        output_path = Path('output.txt')\n        env = MockEnvironment()\n        orig_cwd = os.getcwd()\n        output = requests.get(url).text\n        extra_args = ['--download'] if with_download else []\n        os.chdir(tmp_path)\n        try:\n            assert os.listdir('.') == []\n            r = http(\n                *quiet_flags,\n                '--output', str(output_path),\n                *extra_args,\n                url,\n                env=env\n            )\n            assert os.listdir('.') == [str(output_path)]\n            assert r == ''\n            assert r.stderr == ''\n            assert env.stderr is env.devnull\n            if with_download:\n                assert env.stdout is env.devnull\n            else:\n                assert env.stdout is not env.devnull  # --output swaps stdout.\n            assert output_path.read_text(encoding=UTF8) == output\n        finally:\n            os.chdir(orig_cwd)\n\n\nclass TestVerboseFlag:\n    def test_verbose(self, httpbin):\n        r = http('--verbose',\n                 'GET', httpbin + '/get', 'test-header:__test__')\n        assert HTTP_OK in r\n        assert r.count('__test__') == 2\n\n    def test_verbose_raw(self, httpbin):\n        r = http('--verbose', '--raw', 'foo bar',\n                 'POST', httpbin + '/post')\n        assert HTTP_OK in r\n        assert 'foo bar' in r\n\n    def test_verbose_form(self, httpbin):\n        # https://github.com/httpie/cli/issues/53\n        r = http('--verbose', '--form', 'POST', httpbin + '/post',\n                 'A=B', 'C=D')\n        assert HTTP_OK in r\n        assert 'A=B&C=D' in r\n\n    def test_verbose_json(self, httpbin):\n        r = http('--verbose',\n                 'POST', httpbin + '/post', 'foo=bar', 'baz=bar')\n        assert HTTP_OK in r\n        assert '\"baz\": \"bar\"' in r\n\n    def test_verbose_implies_all(self, httpbin):\n        r = http('--verbose', '--follow', httpbin + '/redirect/1')\n        assert 'GET /redirect/1 HTTP/1.1' in r\n        assert 'HTTP/1.1 302 FOUND' in r\n        assert 'GET /get HTTP/1.1' in r\n        assert HTTP_OK in r\n\n\nclass TestColors:\n\n    @pytest.mark.parametrize(\n        'mime, explicit_json, body, expected_lexer_name',\n        [\n            ('application/json', False, None, 'JSON'),\n            ('application/json+foo', False, None, 'JSON'),\n            ('application/foo+json', False, None, 'JSON'),\n            ('application/json-foo', False, None, 'JSON'),\n            ('application/x-json', False, None, 'JSON'),\n            ('foo/json', False, None, 'JSON'),\n            ('foo/json+bar', False, None, 'JSON'),\n            ('foo/bar+json', False, None, 'JSON'),\n            ('foo/json-foo', False, None, 'JSON'),\n            ('foo/x-json', False, None, 'JSON'),\n            ('application/vnd.comverge.grid+hal+json', False, None, 'JSON'),\n            ('text/plain', True, '{}', 'JSON'),\n            ('text/plain', True, 'foo', 'Text only'),\n        ]\n    )\n    def test_get_lexer(self, mime, explicit_json, body, expected_lexer_name):\n        lexer = get_lexer(mime, body=body, explicit_json=explicit_json)\n        assert lexer is not None\n        assert lexer.name == expected_lexer_name\n\n    def test_get_lexer_not_found(self):\n        assert get_lexer('xxx/yyy') is None\n\n\n@pytest.mark.parametrize(\"endpoint\", [\n    \"/encoding/utf8\",\n    \"/html\",\n    \"/json\",\n    \"/xml\",\n])\ndef test_ensure_contents_colored(httpbin, endpoint):\n    env = MockEnvironment(colors=256)\n    r = http('--body', 'GET', httpbin + endpoint, env=env)\n    assert COLOR in r\n\n\n@pytest.mark.parametrize('style', PIE_STYLE_NAMES)\ndef test_ensure_meta_is_colored(httpbin, style):\n    env = MockEnvironment(colors=256)\n    r = http('--meta', '--style', style, 'GET', httpbin + '/get', env=env)\n    assert COLOR in r\n\n\n@pytest.mark.parametrize('style', SORTED_BUNDLED_STYLES)\n@pytest.mark.parametrize('msg', [\n    '',\n    ' ',\n    ' OK',\n    ' OK ',\n    ' CUSTOM ',\n])\ndef test_ensure_status_code_is_shown_on_all_themes(http_server, style, msg):\n    env = MockEnvironment(colors=256)\n    r = http('--style', style,\n             http_server + '/status/msg',\n             '--raw', msg, env=env)\n\n    # Trailing space is stripped away.\n    assert 'HTTP/1.0 200' + msg.rstrip() in strip_colors(r)\n\n\nclass TestPrettyOptions:\n    \"\"\"Test the --pretty handling.\"\"\"\n\n    def test_pretty_enabled_by_default(self, httpbin):\n        env = MockEnvironment(colors=256)\n        r = http('GET', httpbin + '/get', env=env)\n        assert COLOR in r\n\n    def test_pretty_enabled_by_default_unless_stdout_redirected(self, httpbin):\n        r = http('GET', httpbin + '/get')\n        assert COLOR not in r\n\n    def test_force_pretty(self, httpbin):\n        env = MockEnvironment(stdout_isatty=False, colors=256)\n        r = http('--pretty=all', 'GET', httpbin + '/get', env=env)\n        assert COLOR in r\n\n    def test_force_ugly(self, httpbin):\n        r = http('--pretty=none', 'GET', httpbin + '/get')\n        assert COLOR not in r\n\n    def test_subtype_based_pygments_lexer_match(self, httpbin):\n        \"\"\"Test that media subtype is used if type/subtype doesn't\n        match any lexer.\n\n        \"\"\"\n        env = MockEnvironment(colors=256)\n        r = http('--print=B', '--pretty=all', httpbin + '/post',\n                 'Content-Type:text/foo+json', 'a=b', env=env)\n        assert COLOR in r\n\n    def test_colors_option(self, httpbin):\n        env = MockEnvironment(colors=256)\n        r = http('--print=B', '--pretty=colors',\n                 'GET', httpbin + '/get', 'a=b',\n                 env=env)\n        # Tests that the JSON data isn't formatted.\n        assert not r.strip().count('\\n')\n        assert COLOR in r\n\n    def test_format_option(self, httpbin):\n        env = MockEnvironment(colors=256)\n        r = http('--print=B', '--pretty=format',\n                 'GET', httpbin + '/get', 'a=b',\n                 env=env)\n        # Tests that the JSON data is formatted.\n        assert r.strip().count('\\n') == 2\n        assert COLOR not in r\n\n\nclass TestLineEndings:\n    \"\"\"\n    Test that CRLF is properly used in headers\n    and as the headers/body separator.\n\n    \"\"\"\n\n    def _validate_crlf(self, msg):\n        lines = iter(msg.splitlines(True))\n        for header in lines:\n            if header == CRLF:\n                break\n            assert header.endswith(CRLF), repr(header)\n        else:\n            assert 0, f'CRLF between headers and body not found in {msg!r}'\n        body = ''.join(lines)\n        assert CRLF not in body\n        return body\n\n    def test_CRLF_headers_only(self, httpbin):\n        r = http('--headers', 'GET', httpbin + '/get')\n        body = self._validate_crlf(r)\n        assert not body, f'Garbage after headers: {r!r}'\n\n    def test_CRLF_ugly_response(self, httpbin):\n        r = http('--pretty=none', 'GET', httpbin + '/get')\n        self._validate_crlf(r)\n\n    def test_CRLF_formatted_response(self, httpbin):\n        r = http('--pretty=format', 'GET', httpbin + '/get')\n        assert r.exit_status == ExitStatus.SUCCESS\n        self._validate_crlf(r)\n\n    def test_CRLF_ugly_request(self, httpbin):\n        r = http('--pretty=none', '--print=HB', 'GET', httpbin + '/get')\n        self._validate_crlf(r)\n\n    def test_CRLF_formatted_request(self, httpbin):\n        r = http('--pretty=format', '--print=HB', 'GET', httpbin + '/get')\n        self._validate_crlf(r)\n\n\nclass TestFormatOptions:\n    def test_header_formatting_options(self):\n        def get_headers(sort):\n            return http(\n                '--offline', '--print=H',\n                '--format-options', 'headers.sort:' + sort,\n                'example.org', 'ZZZ:foo', 'XXX:foo',\n            )\n\n        r_sorted = get_headers('true')\n        r_unsorted = get_headers('false')\n        assert r_sorted != r_unsorted\n        assert f'XXX: foo{CRLF}ZZZ: foo' in r_sorted\n        assert f'ZZZ: foo{CRLF}XXX: foo' in r_unsorted\n\n    @pytest.mark.parametrize(\n        'options, expected_json',\n        [\n            # @formatter:off\n            (\n                'json.sort_keys:true,json.indent:4',\n                json.dumps({'a': 0, 'b': 0}, indent=4),\n            ),\n            (\n                'json.sort_keys:false,json.indent:2',\n                json.dumps({'b': 0, 'a': 0}, indent=2),\n            ),\n            (\n                'json.format:false',\n                json.dumps({'b': 0, 'a': 0}),\n            ),\n            # @formatter:on\n        ]\n    )\n    def test_json_formatting_options(self, options: str, expected_json: str):\n        r = http(\n            '--offline', '--print=B',\n            '--format-options', options,\n            'example.org', 'b:=0', 'a:=0',\n        )\n        assert expected_json in r\n\n    @pytest.mark.parametrize(\n        'defaults, options_string, expected',\n        [\n            # @formatter:off\n            ({'foo': {'bar': 1}}, 'foo.bar:2', {'foo': {'bar': 2}}),\n            ({'foo': {'bar': True}}, 'foo.bar:false', {'foo': {'bar': False}}),\n            ({'foo': {'bar': 'a'}}, 'foo.bar:b', {'foo': {'bar': 'b'}}),\n            # @formatter:on\n        ]\n    )\n    def test_parse_format_options(self, defaults, options_string, expected):\n        actual = parse_format_options(s=options_string, defaults=defaults)\n        assert expected == actual\n\n    @pytest.mark.parametrize(\n        'options_string, expected_error',\n        [\n            ('foo:2', 'invalid option'),\n            ('foo.baz:2', 'invalid key'),\n            ('foo.bar:false', 'expected int got bool'),\n        ]\n    )\n    def test_parse_format_options_errors(self, options_string, expected_error):\n        defaults = {\n            'foo': {\n                'bar': 1\n            }\n        }\n        with pytest.raises(argparse.ArgumentTypeError, match=expected_error):\n            parse_format_options(s=options_string, defaults=defaults)\n\n    @pytest.mark.parametrize(\n        'args, expected_format_options',\n        [\n            (\n                [\n                    '--format-options',\n                    'headers.sort:false,json.sort_keys:false',\n                    '--format-options=json.indent:10'\n                ],\n                {\n                    'headers': {\n                        'sort': False\n                    },\n                    'json': {\n                        'sort_keys': False,\n                        'indent': 10,\n                        'format': True\n                    },\n                    'xml': {\n                        'format': True,\n                        'indent': 2,\n                    },\n                }\n            ),\n            (\n                [\n                    '--unsorted'\n                ],\n                {\n                    'headers': {\n                        'sort': False\n                    },\n                    'json': {\n                        'sort_keys': False,\n                        'indent': 4,\n                        'format': True\n                    },\n                    'xml': {\n                        'format': True,\n                        'indent': 2,\n                    },\n                }\n            ),\n            (\n                [\n                    '--format-options=headers.sort:true',\n                    '--unsorted',\n                    '--format-options=headers.sort:true',\n                ],\n                {\n                    'headers': {\n                        'sort': True\n                    },\n                    'json': {\n                        'sort_keys': False,\n                        'indent': 4,\n                        'format': True\n                    },\n                    'xml': {\n                        'format': True,\n                        'indent': 2,\n                    },\n                }\n            ),\n            (\n                [\n                    '--no-format-options',  # --no-<option> anywhere resets\n                    '--format-options=headers.sort:true',\n                    '--unsorted',\n                    '--format-options=headers.sort:true',\n                ],\n                PARSED_DEFAULT_FORMAT_OPTIONS,\n            ),\n            (\n                [\n                    '--format-options=json.indent:2',\n                    '--format-options=xml.format:false',\n                    '--format-options=xml.indent:4',\n                    '--unsorted',\n                    '--no-unsorted',\n                ],\n                {\n                    'headers': {\n                        'sort': True\n                    },\n                    'json': {\n                        'sort_keys': True,\n                        'indent': 2,\n                        'format': True\n                    },\n                    'xml': {\n                        'format': False,\n                        'indent': 4,\n                    },\n                }\n            ),\n            (\n                [\n                    '--format-options=json.indent:2',\n                    '--unsorted',\n                    '--sorted',\n                ],\n                {\n                    'headers': {\n                        'sort': True\n                    },\n                    'json': {\n                        'sort_keys': True,\n                        'indent': 2,\n                        'format': True\n                    },\n                    'xml': {\n                        'format': True,\n                        'indent': 2,\n                    },\n                }\n            ),\n            (\n                [\n                    '--format-options=json.indent:2',\n                    '--sorted',\n                    '--no-sorted',\n                    '--no-unsorted',\n                ],\n                {\n                    'headers': {\n                        'sort': True\n                    },\n                    'json': {\n                        'sort_keys': True,\n                        'indent': 2,\n                        'format': True\n                    },\n                    'xml': {\n                        'format': True,\n                        'indent': 2,\n                    },\n                }\n            ),\n        ],\n    )\n    def test_format_options_accumulation(self, args, expected_format_options):\n        parsed_args = parser.parse_args(\n            args=[*args, 'example.org'],\n            env=MockEnvironment(),\n        )\n        assert parsed_args.format_options == expected_format_options\n\n\n@responses.activate\ndef test_response_mime_overwrite():\n    responses.add(\n        method=responses.GET,\n        url=DUMMY_URL,\n        body=XML_DATA_RAW,\n        content_type='text/plain',\n    )\n    r = http(\n        '--offline',\n        '--raw', XML_DATA_RAW,\n        '--response-mime=application/xml', DUMMY_URL\n    )\n    assert XML_DATA_RAW in r  # not affecting request bodies\n\n    r = http('--response-mime=application/xml', DUMMY_URL)\n    assert XML_DATA_FORMATTED in r\n\n\n@responses.activate\ndef test_response_mime_overwrite_incorrect():\n    responses.add(\n        method=responses.GET,\n        url=DUMMY_URL,\n        body=XML_DATA_RAW,\n        content_type='text/xml',\n    )\n    # The provided Content-Type is simply ignored, and so no formatting is done.\n    r = http('--response-mime=incorrect/type', DUMMY_URL)\n    assert XML_DATA_RAW in r\n"
  },
  {
    "path": "tests/test_parser_schema.py",
    "content": "from httpie.cli.options import ParserSpec, Qualifiers\n\n\ndef test_parser_serialization():\n    small_parser = ParserSpec(\"test_parser\")\n\n    group_1 = small_parser.add_group(\"group_1\")\n    group_1.add_argument(\"regular_arg\", help=\"regular arg\", short_help=\"short\")\n    group_1.add_argument(\n        \"variadic_arg\",\n        metavar=\"META\",\n        help=Qualifiers.SUPPRESS,\n        nargs=Qualifiers.ZERO_OR_MORE\n    )\n    group_1.add_argument(\n        \"-O\",\n        \"--opt-arg\",\n        action=\"lazy_choices\",\n        getter=lambda: [\"opt_1\", \"opt_2\"],\n        help_formatter=lambda state, *, isolation_mode: \", \".join(state),\n        short_help=\"short_help\",\n    )\n\n    group_2 = small_parser.add_group(\"group_2\")\n    group_2.add_argument(\"--typed\", action=\"store_true\", type=int)\n\n    definition = small_parser.finalize()\n    assert definition.serialize() == {\n        \"name\": \"test_parser\",\n        \"description\": None,\n        \"groups\": [\n            {\n                \"name\": \"group_1\",\n                \"description\": None,\n                \"is_mutually_exclusive\": False,\n                \"args\": [\n                    {\n                        \"options\": [\"regular_arg\"],\n                        \"description\": \"regular arg\",\n                        \"short_description\": \"short\",\n                    },\n                    {\n                        \"options\": [\"variadic_arg\"],\n                        \"is_optional\": True,\n                        \"is_variadic\": True,\n                        \"metavar\": \"META\",\n                    },\n                    {\n                        \"options\": [\"-O\", \"--opt-arg\"],\n                        \"description\": \"opt_1, opt_2\",\n                        \"short_description\": \"short_help\",\n                        \"choices\": [\"opt_1\", \"opt_2\"],\n                    },\n                ],\n            },\n            {\n                \"name\": \"group_2\",\n                \"description\": None,\n                \"is_mutually_exclusive\": False,\n                \"args\": [{\"options\": [\"--typed\"], \"python_type_name\": \"int\"}],\n            },\n        ],\n    }\n"
  },
  {
    "path": "tests/test_plugins_cli.py",
    "content": "import pytest\n\nfrom httpie.status import ExitStatus\nfrom tests.utils.plugins_cli import parse_listing\n\n\n@pytest.mark.requires_installation\n@pytest.mark.parametrize('cli_mode', [True, False])\ndef test_plugins_installation(httpie_plugins_success, interface, dummy_plugin, cli_mode):\n    lines = httpie_plugins_success('install', dummy_plugin.path, cli_mode=cli_mode)\n    assert lines[0].startswith(\n        f'Installing {dummy_plugin.path}'\n    )\n    assert f'Successfully installed {dummy_plugin.name}-{dummy_plugin.version}' in lines\n    assert interface.is_installed(dummy_plugin.name)\n\n\n@pytest.mark.requires_installation\ndef test_plugin_installation_with_custom_config(httpie_plugins_success, interface, dummy_plugin):\n    interface.environment.config['default_options'] = ['--session-read-only', 'some-path.json', 'other', 'args']\n    interface.environment.config.save()\n\n    lines = httpie_plugins_success('install', dummy_plugin.path)\n    assert lines[0].startswith(\n        f'Installing {dummy_plugin.path}'\n    )\n    assert f'Successfully installed {dummy_plugin.name}-{dummy_plugin.version}' in lines\n    assert interface.is_installed(dummy_plugin.name)\n\n\n@pytest.mark.requires_installation\n@pytest.mark.parametrize('cli_mode', [True, False])\ndef test_plugins_listing(httpie_plugins_success, interface, dummy_plugin, cli_mode):\n    httpie_plugins_success('install', dummy_plugin.path, cli_mode=cli_mode)\n    data = parse_listing(httpie_plugins_success('list'))\n\n    assert data == {\n        dummy_plugin.name: dummy_plugin.dump()\n    }\n\n\n@pytest.mark.requires_installation\ndef test_plugins_listing_multiple(interface, httpie_plugins_success, dummy_plugins):\n    paths = [plugin.path for plugin in dummy_plugins]\n    httpie_plugins_success('install', *paths)\n    data = parse_listing(httpie_plugins_success('list'))\n\n    assert data == {\n        plugin.name: plugin.dump()\n        for plugin in dummy_plugins\n    }\n\n\n@pytest.mark.requires_installation\n@pytest.mark.parametrize('cli_mode', [True, False])\ndef test_plugins_uninstall(interface, httpie_plugins_success, dummy_plugin, cli_mode):\n    httpie_plugins_success('install', dummy_plugin.path, cli_mode=cli_mode)\n    httpie_plugins_success('uninstall', dummy_plugin.name, cli_mode=cli_mode)\n    assert not interface.is_installed(dummy_plugin.name)\n\n\n@pytest.mark.requires_installation\ndef test_plugins_listing_after_uninstall(interface, httpie_plugins_success, dummy_plugin):\n    httpie_plugins_success('install', dummy_plugin.path)\n    httpie_plugins_success('uninstall', dummy_plugin.name)\n\n    data = parse_listing(httpie_plugins_success('list'))\n    assert len(data) == 0\n\n\n@pytest.mark.requires_installation\ndef test_plugins_uninstall_specific(interface, httpie_plugins_success):\n    new_plugin_1 = interface.make_dummy_plugin()\n    new_plugin_2 = interface.make_dummy_plugin()\n    target_plugin = interface.make_dummy_plugin()\n\n    httpie_plugins_success('install', new_plugin_1.path, new_plugin_2.path, target_plugin.path)\n    httpie_plugins_success('uninstall', target_plugin.name)\n\n    assert interface.is_installed(new_plugin_1.name)\n    assert interface.is_installed(new_plugin_2.name)\n    assert not interface.is_installed(target_plugin.name)\n\n\n@pytest.mark.requires_installation\ndef test_plugins_installation_failed(httpie_plugins, interface):\n    plugin = interface.make_dummy_plugin(build=False)\n    result = httpie_plugins('install', plugin.path)\n\n    assert result.exit_status == ExitStatus.ERROR\n    assert result.stderr.splitlines()[-1].strip().startswith(\"Can't install\")\n\n\n@pytest.mark.requires_installation\ndef test_plugins_uninstall_non_existent(httpie_plugins, interface):\n    plugin = interface.make_dummy_plugin(build=False)\n    result = httpie_plugins('uninstall', plugin.name)\n\n    assert result.exit_status == ExitStatus.ERROR\n    assert (\n        result.stderr.splitlines()[-1].strip()\n        == f\"Can't uninstall '{plugin.name}': package is not installed\"\n    )\n\n\n@pytest.mark.requires_installation\ndef test_plugins_double_uninstall(httpie_plugins, httpie_plugins_success, dummy_plugin):\n    httpie_plugins_success(\"install\", dummy_plugin.path)\n    httpie_plugins_success(\"uninstall\", dummy_plugin.name)\n\n    result = httpie_plugins(\"uninstall\", dummy_plugin.name)\n\n    assert result.exit_status == ExitStatus.ERROR\n    assert (\n        result.stderr.splitlines()[-1].strip()\n        == f\"Can't uninstall '{dummy_plugin.name}': package is not installed\"\n    )\n\n\n# TODO: Make this work on CI (stopped working at some point)\n@pytest.mark.skip(reason='Doesn’t work in CI')\n@pytest.mark.requires_installation\ndef test_plugins_upgrade(httpie_plugins, httpie_plugins_success, dummy_plugin):\n    httpie_plugins_success(\"install\", dummy_plugin.path)\n\n    # Make a new version of the plugin\n    dummy_plugin.version = '2.0.0'\n    dummy_plugin.build()\n\n    httpie_plugins_success(\"upgrade\", dummy_plugin.path)\n    data = parse_listing(httpie_plugins_success('list'))\n    assert data[dummy_plugin.name]['version'] == '2.0.0'\n\n\n@pytest.mark.requires_installation\ndef test_broken_plugins(httpie_plugins, httpie_plugins_success, dummy_plugin, broken_plugin):\n    httpie_plugins_success(\"install\", dummy_plugin.path, broken_plugin.path)\n\n    with pytest.warns(\n        UserWarning,\n        match=(\n            f'While loading \"{broken_plugin.name}\", an error'\n            ' occurred: broken plugin'\n        )\n    ):\n        data = parse_listing(httpie_plugins_success('list'))\n        assert len(data) == 2\n\n    # We load before the uninstallation, so it will warn again.\n    with pytest.warns(UserWarning):\n        httpie_plugins_success(\"uninstall\", broken_plugin.name)\n\n    # No warning now, since it is uninstalled.\n    data = parse_listing(httpie_plugins_success('list'))\n    assert len(data) == 1\n"
  },
  {
    "path": "tests/test_redirects.py",
    "content": "\"\"\"High-level tests.\"\"\"\nimport pytest\n\nfrom httpie.compat import is_windows\nfrom httpie.status import ExitStatus\nfrom .fixtures import FILE_PATH_ARG, FILE_CONTENT\nfrom .utils import http, HTTP_OK\nfrom .utils.matching import assert_output_matches, Expect, ExpectSequence\n\n\n# <https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections>\nREDIRECTS_WITH_METHOD_BODY_PRESERVED = [307, 308]\n\n\ndef test_follow_all_redirects_shown(httpbin):\n    r = http('--follow', '--all', httpbin + '/redirect/2')\n    assert r.count('HTTP/1.1') == 3\n    assert r.count('HTTP/1.1 302 FOUND', 2)\n    assert HTTP_OK in r\n\n\n@pytest.mark.parametrize('follow_flag', ['--follow', '-F'])\ndef test_follow_without_all_redirects_hidden(httpbin, follow_flag):\n    r = http(follow_flag, httpbin + '/redirect/2')\n    assert r.count('HTTP/1.1') == 1\n    assert HTTP_OK in r\n\n\n@pytest.mark.xfail(True, reason=\"https://github.com/httpie/cli/issues/1082\")\ndef test_follow_output_options_used_for_redirects(httpbin):\n    r = http('--follow', '--print=H', httpbin + '/redirect/2')\n    assert r.count('GET /') == 1\n    assert HTTP_OK not in r\n\n\ndef test_follow_all_output_options_used_for_redirects(httpbin):\n    r = http('--check-status',\n             '--follow',\n             '--all',\n             '--print=H',\n             httpbin + '/redirect/2')\n    assert r.count('GET /') == 3\n    assert HTTP_OK not in r\n\n\n#\n# def test_follow_redirect_output_options(httpbin):\n#     r = http('--check-status',\n#              '--follow',\n#              '--all',\n#              '--print=h',\n#              '--history-print=H',\n#              httpbin + '/redirect/2')\n#     assert r.count('GET /') == 2\n#     assert 'HTTP/1.1 302 FOUND' not in r\n#     assert HTTP_OK in r\n#\n\n\ndef test_max_redirects(httpbin):\n    r = http(\n        '--max-redirects=1',\n        '--follow',\n        httpbin + '/redirect/3',\n        tolerate_error_exit_status=True,\n    )\n    assert r.exit_status == ExitStatus.ERROR_TOO_MANY_REDIRECTS\n\n\n@pytest.mark.skipif(is_windows, reason='occasionally fails w/ ConnectionError for no apparent reason')\n@pytest.mark.parametrize('status_code', REDIRECTS_WITH_METHOD_BODY_PRESERVED)\ndef test_follow_redirect_with_repost(httpbin, status_code):\n    r = http(\n        '--follow',\n        httpbin + '/redirect-to',\n        'A:A',\n        'A:B',\n        'B:B',\n        f'url=={httpbin}/post',\n        f'status_code=={status_code}',\n        '@' + FILE_PATH_ARG,\n    )\n    assert HTTP_OK in r\n    assert FILE_CONTENT in r\n    assert r.json['headers']['A'] == 'A,B'\n    assert r.json['headers']['B'] == 'B'\n\n\n@pytest.mark.skipif(is_windows, reason='occasionally fails w/ ConnectionError for no apparent reason')\n@pytest.mark.parametrize('status_code', REDIRECTS_WITH_METHOD_BODY_PRESERVED)\ndef test_verbose_follow_redirect_with_repost(httpbin, status_code):\n    r = http(\n        '--follow',\n        '--verbose',\n        httpbin + '/redirect-to',\n        'A:A',\n        'A:B',\n        'B:B',\n        f'url=={httpbin}/post',\n        f'status_code=={status_code}',\n        '@' + FILE_PATH_ARG,\n    )\n    assert f'HTTP/1.1 {status_code}' in r\n    assert 'A: A' in r\n    assert 'A: B' in r\n    assert 'B: B' in r\n    assert r.count('POST /redirect-to') == 1\n    assert r.count('POST /post') == 1\n    assert r.count(FILE_CONTENT) == 3  # two requests + final response contain it\n    assert HTTP_OK in r\n    assert_output_matches(r, [\n        *ExpectSequence.TERMINAL_REQUEST,\n        Expect.RESPONSE_HEADERS,\n        Expect.SEPARATOR,\n        *ExpectSequence.TERMINAL_EXCHANGE,\n    ])\n"
  },
  {
    "path": "tests/test_regressions.py",
    "content": "\"\"\"Miscellaneous regression tests\"\"\"\nimport pytest\n\nfrom httpie.compat import is_windows\nfrom .utils.matching import assert_output_matches, Expect\nfrom .utils import HTTP_OK, MockEnvironment, http\n\n\ndef test_Host_header_overwrite(httpbin):\n    \"\"\"\n    https://github.com/httpie/cli/issues/235\n\n    \"\"\"\n    host = 'pie.dev'\n    url = httpbin + '/get'\n    r = http('--print=hH', url, f'host:{host}')\n    assert HTTP_OK in r\n    assert r.lower().count('host:') == 1\n    assert f'host: {host}' in r\n\n\n@pytest.mark.skipif(is_windows, reason='Unix-only')\ndef test_output_devnull(httpbin):\n    \"\"\"\n    https://github.com/httpie/cli/issues/252\n\n    \"\"\"\n    http('--output=/dev/null', httpbin + '/get')\n\n\ndef test_verbose_redirected_stdout_separator(httpbin):\n    \"\"\"\n\n    <https://github.com/httpie/cli/issues/1006>\n    \"\"\"\n    r = http(\n        '-v',\n        httpbin + '/post',\n        'a=b',\n        env=MockEnvironment(stdout_isatty=False),\n    )\n    assert '}HTTP/' not in r\n    assert_output_matches(r, [\n        Expect.REQUEST_HEADERS,\n        Expect.BODY,\n        Expect.SEPARATOR,\n        Expect.RESPONSE_HEADERS,\n        Expect.BODY,\n    ])\n"
  },
  {
    "path": "tests/test_sessions.py",
    "content": "import json\nimport os\nimport shutil\nfrom contextlib import contextmanager\nfrom datetime import datetime\nfrom unittest import mock\nfrom pathlib import Path\nfrom typing import Iterator\n\nimport pytest\n\nfrom .fixtures import FILE_PATH_ARG, UNICODE\nfrom httpie.context import Environment\nfrom httpie.encoding import UTF8\nfrom httpie.plugins import AuthPlugin\nfrom httpie.plugins.builtin import HTTPBasicAuth\nfrom httpie.plugins.registry import plugin_manager\nfrom httpie.sessions import Session\nfrom httpie.utils import get_expired_cookies\nfrom .test_auth_plugins import basic_auth\nfrom .utils import DUMMY_HOST, HTTP_OK, MockEnvironment, http, mk_config_dir\nfrom base64 import b64encode\n\n\nclass SessionTestBase:\n\n    def start_session(self, httpbin):\n        \"\"\"Create and reuse a unique config dir for each test.\"\"\"\n        self.config_dir = mk_config_dir()\n\n    def teardown_method(self, method):\n        shutil.rmtree(self.config_dir)\n\n    def env(self):\n        \"\"\"\n        Return an environment.\n\n        Each environment created within a test method\n        will share the same config_dir. It is necessary\n        for session files being reused.\n\n        \"\"\"\n        return MockEnvironment(config_dir=self.config_dir)\n\n\nclass CookieTestBase:\n    def setup_method(self, method):\n        self.config_dir = mk_config_dir()\n\n        orig_session = {\n            'cookies': {\n                'cookie1': {\n                    'value': 'foo',\n                },\n                'cookie2': {\n                    'value': 'foo',\n                }\n            }\n        }\n        self.session_path = self.config_dir / 'test-session.json'\n        self.session_path.write_text(json.dumps(orig_session), encoding=UTF8)\n\n    def teardown_method(self, method):\n        shutil.rmtree(self.config_dir)\n\n\nclass TestSessionFlow(SessionTestBase):\n    \"\"\"\n    These tests start with an existing session created in `setup_method()`.\n\n    \"\"\"\n\n    def start_session(self, httpbin):\n        \"\"\"\n        Start a full-blown session with a custom request header,\n        authorization, and response cookies.\n\n        \"\"\"\n        super().start_session(httpbin)\n        r1 = http(\n            '--follow',\n            '--session=test',\n            '--auth=username:password',\n            'GET',\n            httpbin + '/cookies/set?hello=world',\n            'Hello:World',\n            env=self.env()\n        )\n        assert HTTP_OK in r1\n\n    def test_session_created_and_reused(self, httpbin):\n        self.start_session(httpbin)\n        # Verify that the session created in setup_method() has been used.\n        r2 = http('--session=test',\n                  'GET', httpbin + '/get', env=self.env())\n        assert HTTP_OK in r2\n        assert r2.json['headers']['Hello'] == 'World'\n        assert r2.json['headers']['Cookie'] == 'hello=world'\n        assert 'Basic ' in r2.json['headers']['Authorization']\n\n    def test_session_update(self, httpbin):\n        self.start_session(httpbin)\n        # Get a response to a request from the original session.\n        r2 = http('--session=test', 'GET', httpbin + '/get',\n                  env=self.env())\n        assert HTTP_OK in r2\n\n        # Make a request modifying the session data.\n        r3 = http('--follow', '--session=test', '--auth=username:password2',\n                  'GET', httpbin + '/cookies/set?hello=world2',\n                  'Hello:World2',\n                  env=self.env())\n        assert HTTP_OK in r3\n\n        # Get a response to a request from the updated session.\n        r4 = http('--session=test', 'GET', httpbin + '/get',\n                  env=self.env())\n        assert HTTP_OK in r4\n        assert r4.json['headers']['Hello'] == 'World2'\n        assert r4.json['headers']['Cookie'] == 'hello=world2'\n        assert (r2.json['headers']['Authorization']\n                != r4.json['headers']['Authorization'])\n\n    def test_session_read_only(self, httpbin):\n        self.start_session(httpbin)\n        # Get a response from the original session.\n        r2 = http('--session=test', 'GET', httpbin + '/get',\n                  env=self.env())\n        assert HTTP_OK in r2\n\n        # Make a request modifying the session data but\n        # with --session-read-only.\n        r3 = http('--follow', '--session-read-only=test',\n                  '--auth=username:password2', 'GET',\n                  httpbin + '/cookies/set?hello=world2', 'Hello:World2',\n                  env=self.env())\n        assert HTTP_OK in r3\n\n        # Get a response from the updated session.\n        r4 = http('--session=test', 'GET', httpbin + '/get',\n                  env=self.env())\n        assert HTTP_OK in r4\n\n        # Origin can differ on Travis.\n        del r2.json['origin'], r4.json['origin']\n        # Different for each request.\n\n        # Should be the same as before r3.\n        assert r2.json == r4.json\n\n    def test_session_overwrite_header(self, httpbin):\n        self.start_session(httpbin)\n\n        r2 = http('--session=test', 'GET', httpbin + '/get',\n                  'Hello:World2', env=self.env())\n        assert HTTP_OK in r2\n        assert r2.json['headers']['Hello'] == 'World2'\n\n        r3 = http('--session=test', 'GET', httpbin + '/get',\n                  'Hello:World2', 'Hello:World3', env=self.env())\n        assert HTTP_OK in r3\n        assert r3.json['headers']['Hello'] == 'World2,World3'\n\n        r3 = http('--session=test', 'GET', httpbin + '/get',\n                  'Hello:', 'Hello:World3', env=self.env())\n        assert HTTP_OK in r3\n        assert 'Hello' not in r3.json['headers']['Hello']\n\n\nclass TestSession(SessionTestBase):\n    \"\"\"Stand-alone session tests.\"\"\"\n\n    def test_session_ignored_header_prefixes(self, httpbin):\n        self.start_session(httpbin)\n        r1 = http('--session=test', 'GET', httpbin + '/get',\n                  'Content-Type: text/plain',\n                  'If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT',\n                  env=self.env())\n        assert HTTP_OK in r1\n        r2 = http('--session=test', 'GET', httpbin + '/get',\n                  env=self.env())\n        assert HTTP_OK in r2\n        assert 'Content-Type' not in r2.json['headers']\n        assert 'If-Unmodified-Since' not in r2.json['headers']\n\n    def test_session_with_upload(self, httpbin):\n        self.start_session(httpbin)\n        r = http('--session=test', '--form', '--verbose', 'POST', httpbin + '/post',\n                 f'test-file@{FILE_PATH_ARG}', 'foo=bar', env=self.env())\n        assert HTTP_OK in r\n\n    def test_session_by_path(self, httpbin):\n        self.start_session(httpbin)\n        session_path = self.config_dir / 'session-by-path.json'\n        r1 = http('--session', str(session_path), 'GET', httpbin + '/get',\n                  'Foo:Bar', env=self.env())\n        assert HTTP_OK in r1\n\n        r2 = http('--session', str(session_path), 'GET', httpbin + '/get',\n                  env=self.env())\n        assert HTTP_OK in r2\n        assert r2.json['headers']['Foo'] == 'Bar'\n\n    def test_session_with_cookie_followed_by_another_header(self, httpbin):\n        \"\"\"\n        Make sure headers don’t get mutated — <https://github.com/httpie/cli/issues/1126>\n        \"\"\"\n        self.start_session(httpbin)\n        session_data = {\n            'headers': {\n                'cookie': '...',\n                'zzz': '...'\n            }\n        }\n        session_path = self.config_dir / 'session-data.json'\n        session_path.write_text(json.dumps(session_data))\n        r = http('--session', str(session_path), 'GET', httpbin + '/get',\n                 env=self.env())\n        assert HTTP_OK in r\n        assert 'Zzz' in r\n\n    def test_session_unicode(self, httpbin):\n        self.start_session(httpbin)\n\n        r1 = http('--session=test', f'--auth=test:{UNICODE}',\n                  'GET', httpbin + '/get', f'Test:{UNICODE}',\n                  env=self.env())\n        assert HTTP_OK in r1\n\n        r2 = http('--session=test', '--verbose', 'GET',\n                  httpbin + '/get', env=self.env())\n        assert HTTP_OK in r2\n\n        # FIXME: Authorization *sometimes* is not present\n        assert (r2.json['headers']['Authorization']\n                == HTTPBasicAuth.make_header('test', UNICODE))\n        # httpbin doesn't interpret UTF-8 headers\n        assert UNICODE in r2\n\n    def test_session_default_header_value_overwritten(self, httpbin):\n        self.start_session(httpbin)\n        # https://github.com/httpie/cli/issues/180\n        r1 = http('--session=test',\n                  httpbin + '/headers', 'User-Agent:custom',\n                  env=self.env())\n        assert HTTP_OK in r1\n        assert r1.json['headers']['User-Agent'] == 'custom'\n\n        r2 = http('--session=test', httpbin + '/headers', env=self.env())\n        assert HTTP_OK in r2\n        assert r2.json['headers']['User-Agent'] == 'custom'\n\n    def test_download_in_session(self, tmp_path, httpbin):\n        # https://github.com/httpie/cli/issues/412\n        self.start_session(httpbin)\n        cwd = os.getcwd()\n        os.chdir(tmp_path)\n        try:\n            http('--session=test', '--download',\n                 httpbin + '/get', env=self.env())\n        finally:\n            os.chdir(cwd)\n\n    @pytest.mark.parametrize(\n        'auth_require_param, auth_parse_param',\n        [\n            (False, False),\n            (False, True),\n            (True, False)\n        ]\n    )\n    def test_auth_type_reused_in_session(self, auth_require_param, auth_parse_param, httpbin):\n        self.start_session(httpbin)\n        session_path = self.config_dir / 'test-session.json'\n\n        header = 'Custom dXNlcjpwYXNzd29yZA'\n\n        class Plugin(AuthPlugin):\n            auth_type = 'test-reused'\n            auth_require = auth_require_param\n            auth_parse = auth_parse_param\n\n            def get_auth(self, username=None, password=None):\n                return basic_auth(header=f'{header}==')\n\n        plugin_manager.register(Plugin)\n\n        r1 = http(\n            '--session', str(session_path),\n            httpbin + '/basic-auth/user/password',\n            '--auth-type',\n            Plugin.auth_type,\n            '--auth', 'user:password',\n            '--print=H',\n        )\n\n        r2 = http(\n            '--session', str(session_path),\n            httpbin + '/basic-auth/user/password',\n            '--print=H',\n        )\n        assert f'Authorization: {header}' in r1\n        assert f'Authorization: {header}' in r2\n        plugin_manager.unregister(Plugin)\n\n    def test_auth_plugin_prompt_password_in_session(self, httpbin):\n        self.start_session(httpbin)\n        session_path = self.config_dir / 'test-session.json'\n\n        class Plugin(AuthPlugin):\n            auth_type = 'test-prompted'\n\n            def get_auth(self, username=None, password=None):\n                basic_auth_header = 'Basic ' + b64encode(self.raw_auth.encode()).strip().decode('latin1')\n                return basic_auth(basic_auth_header)\n\n        plugin_manager.register(Plugin)\n\n        with mock.patch(\n            'httpie.cli.argtypes.AuthCredentials._getpass',\n            new=lambda self, prompt: 'password'\n        ):\n            r1 = http(\n                '--session', str(session_path),\n                httpbin + '/basic-auth/user/password',\n                '--auth-type',\n                Plugin.auth_type,\n                '--auth', 'user',\n            )\n\n        r2 = http(\n            '--session', str(session_path),\n            httpbin + '/basic-auth/user/password',\n        )\n        assert HTTP_OK in r1\n        assert HTTP_OK in r2\n\n        # additional test for issue: https://github.com/httpie/cli/issues/1098\n        with open(session_path) as session_file:\n            session_file_lines = ''.join(session_file.readlines())\n            assert \"\\\"type\\\": \\\"test-prompted\\\"\" in session_file_lines\n            assert \"\\\"raw_auth\\\": \\\"user:password\\\"\" in session_file_lines\n\n        plugin_manager.unregister(Plugin)\n\n    def test_auth_type_stored_in_session_file(self, httpbin):\n        self.config_dir = mk_config_dir()\n        self.session_path = self.config_dir / 'test-session.json'\n\n        class Plugin(AuthPlugin):\n            auth_type = 'test-saved'\n            auth_require = True\n\n            def get_auth(self, username=None, password=None):\n                return basic_auth()\n\n        plugin_manager.register(Plugin)\n        http('--session', str(self.session_path),\n             httpbin + '/basic-auth/user/password',\n             '--auth-type',\n             Plugin.auth_type,\n             '--auth', 'user:password',\n             )\n        updated_session = json.loads(self.session_path.read_text(encoding=UTF8))\n        assert updated_session['auth']['type'] == 'test-saved'\n        assert updated_session['auth']['raw_auth'] == 'user:password'\n        plugin_manager.unregister(Plugin)\n\n\nclass TestExpiredCookies(CookieTestBase):\n\n    @pytest.mark.parametrize(\n        'initial_cookie, expired_cookie',\n        [\n            ({'id': {'value': 123}}, {'name': 'id'}),\n            ({'id': {'value': 123}}, {'name': 'token'})\n        ]\n    )\n    def test_removes_expired_cookies_from_session_obj(self, initial_cookie, expired_cookie, httpbin, mock_env):\n        session = Session(self.config_dir, env=mock_env, session_id=None, bound_host=None)\n        session['cookies'] = initial_cookie\n        session.remove_cookies([expired_cookie])\n        assert expired_cookie not in session.cookies\n\n    def test_expired_cookies(self, httpbin):\n        r = http(\n            '--session', str(self.session_path),\n            '--print=H',\n            httpbin + '/cookies/delete?cookie2',\n        )\n        assert 'Cookie: cookie1=foo; cookie2=foo' in r\n\n        updated_session = json.loads(self.session_path.read_text(encoding=UTF8))\n        assert 'cookie1' in updated_session['cookies']\n        assert 'cookie2' not in updated_session['cookies']\n\n    def test_get_expired_cookies_using_max_age(self):\n        cookies = 'one=two; Max-Age=0; path=/; domain=.tumblr.com; HttpOnly'\n        expected_expired = [\n            {'name': 'one', 'path': '/'}\n        ]\n        assert get_expired_cookies(cookies, now=None) == expected_expired\n\n    @pytest.mark.parametrize(\n        'cookies, now, expected_expired',\n        [\n            (\n                'hello=world; Path=/; Expires=Thu, 01-Jan-1970 00:00:00 GMT; HttpOnly',\n                None,\n                [\n                    {\n                        'name': 'hello',\n                        'path': '/'\n                    }\n                ]\n            ),\n            (\n                (\n                    'hello=world; Path=/; Expires=Thu, 01-Jan-1970 00:00:00 GMT; HttpOnly, '\n                    'pea=pod; Path=/ab; Expires=Thu, 01-Jan-1970 00:00:00 GMT; HttpOnly'\n                ),\n                None,\n                [\n                    {'name': 'hello', 'path': '/'},\n                    {'name': 'pea', 'path': '/ab'}\n                ]\n            ),\n            (\n                # Checks we gracefully ignore expires date in invalid format.\n                # <https://github.com/httpie/cli/issues/963>\n                'pfg=; Expires=Sat, 19-Sep-2020 06:58:14 GMT+0000; Max-Age=0; path=/; domain=.tumblr.com; secure; HttpOnly',\n                None,\n                []\n            ),\n            (\n                'hello=world; Path=/; Expires=Fri, 12 Jun 2020 12:28:55 GMT; HttpOnly',\n                datetime(2020, 6, 11).timestamp(),\n                []\n            ),\n        ]\n    )\n    def test_get_expired_cookies_manages_multiple_cookie_headers(self, cookies, now, expected_expired):\n        assert get_expired_cookies(cookies, now=now) == expected_expired\n\n\nclass TestCookieStorage(CookieTestBase):\n\n    @pytest.mark.parametrize(\n        ['specified_cookie_header', 'new_cookies_dict', 'expected_effective_cookie_header'],\n        [(\n            'new=bar',\n            {'new': 'bar'},\n            'cookie1=foo; cookie2=foo; new=bar'\n        ),\n            (\n            'new=bar;chocolate=milk',\n            {'new': 'bar', 'chocolate': 'milk'},\n            'chocolate=milk; cookie1=foo; cookie2=foo; new=bar'\n        ),\n            (\n            'new=bar; chocolate=milk',\n            {'new': 'bar', 'chocolate': 'milk'},\n            'chocolate=milk; cookie1=foo; cookie2=foo; new=bar'\n        ),\n            (\n            'new=bar; chocolate=milk',\n            {'new': 'bar', 'chocolate': 'milk'},\n            'cookie1=foo; cookie2=foo; new=bar; chocolate=milk'\n        ),\n            (\n            'new=bar; chocolate=milk;;;',\n            {'new': 'bar', 'chocolate': 'milk'},\n            'chocolate=milk; cookie1=foo; cookie2=foo; new=bar'\n        )\n        ]\n    )\n    def test_existing_and_new_cookies_sent_in_request(\n        self,\n        specified_cookie_header,\n        new_cookies_dict,\n        expected_effective_cookie_header,\n        httpbin,\n    ):\n        r = http(\n            '--session', str(self.session_path),\n            '--print=H',\n            httpbin + '/get',\n            'Cookie:' + specified_cookie_header,\n        )\n        parsed_request_headers = {  # noqa\n            name: value for name, value in [\n                line.split(': ', 1)\n                for line in r.splitlines()\n                if line and ':' in line\n            ]\n        }\n        # Note: cookies in the request are in an undefined order.\n        expected_request_cookie_set = set(expected_effective_cookie_header.split('; '))\n        actual_request_cookie_set = set(parsed_request_headers['Cookie'].split('; '))\n        assert actual_request_cookie_set == expected_request_cookie_set\n\n        updated_session = json.loads(self.session_path.read_text(encoding=UTF8))\n        assert 'Cookie' not in updated_session['headers']\n        for name, value in new_cookies_dict.items():\n            assert updated_session['cookies'][name]['value'] == value\n\n    @pytest.mark.parametrize(\n        'cli_cookie, set_cookie, expected',\n        [(\n            '',\n            '/cookies/set/cookie1/bar',\n            'bar'\n        ),\n            (\n            'cookie1=not_foo',\n            '/cookies/set/cookie1/bar',\n            'bar'\n        ),\n            (\n            'cookie1=not_foo',\n            '',\n            'not_foo'\n        ),\n            (\n            '',\n            '',\n            'foo'\n        )\n        ]\n    )\n    def test_cookie_storage_priority(self, cli_cookie, set_cookie, expected, httpbin):\n        \"\"\"\n        Expected order of priority for cookie storage in session file:\n        1. set-cookie (from server)\n        2. command line arg\n        3. cookie already stored in session file\n        \"\"\"\n        http(\n            '--session', str(self.session_path),\n            httpbin + set_cookie,\n            'Cookie:' + cli_cookie,\n        )\n        updated_session = json.loads(self.session_path.read_text(encoding=UTF8))\n\n        assert updated_session['cookies']['cookie1']['value'] == expected\n\n\n@pytest.fixture\ndef basic_session(httpbin, tmp_path):\n    session_path = tmp_path / 'session.json'\n    http(\n        '--session', str(session_path),\n        httpbin + '/get'\n    )\n    return session_path\n\n\n@contextmanager\ndef open_session(path: Path, env: Environment, read_only: bool = False) -> Iterator[Session]:\n    session = Session(path, env, session_id='test', bound_host=DUMMY_HOST)\n    session.load()\n    yield session\n    if not read_only:\n        session.save()\n\n\n@contextmanager\ndef open_raw_session(path: Path, read_only: bool = False) -> None:\n    with open(path) as stream:\n        raw_session = json.load(stream)\n\n    yield raw_session\n\n    if not read_only:\n        with open(path, 'w') as stream:\n            json.dump(raw_session, stream)\n\n\ndef read_stderr(env: Environment) -> bytes:\n    env.stderr.seek(0)\n    stderr_data = env.stderr.read()\n    if isinstance(stderr_data, str):\n        return stderr_data.encode()\n    else:\n        return stderr_data\n\n\ndef test_old_session_version_saved_as_is(basic_session, mock_env):\n    with open_session(basic_session, mock_env) as session:\n        session['__meta__'] = {'httpie': '0.0.1'}\n\n    with open_session(basic_session, mock_env, read_only=True) as session:\n        assert session['__meta__']['httpie'] == '0.0.1'\n\n\ndef test_old_session_cookie_layout_warning(basic_session, mock_env):\n    with open_session(basic_session, mock_env) as session:\n        # Use the old layout & set a cookie\n        session['cookies'] = {}\n        session.cookies.set('foo', 'bar')\n\n    assert read_stderr(mock_env) == b''\n\n    with open_session(basic_session, mock_env, read_only=True) as session:\n        assert b'Outdated layout detected' in read_stderr(mock_env)\n\n\n@pytest.mark.parametrize('cookies, expect_warning', [\n    # Old-style cookie format\n    (\n        # Without 'domain' set\n        {'foo': {'value': 'bar'}},\n        True\n    ),\n    (\n        # With 'domain' set to empty string\n        {'foo': {'value': 'bar', 'domain': ''}},\n        True\n    ),\n    (\n        # With 'domain' set to null\n        {'foo': {'value': 'bar', 'domain': None}},\n        False,\n    ),\n    (\n        # With 'domain' set to a URL\n        {'foo': {'value': 'bar', 'domain': DUMMY_HOST}},\n        False,\n    ),\n    # New style cookie format\n    (\n        # Without 'domain' set\n        [{'name': 'foo', 'value': 'bar'}],\n        False\n    ),\n    (\n        # With 'domain' set to empty string\n        [{'name': 'foo', 'value': 'bar', 'domain': ''}],\n        False\n    ),\n    (\n        # With 'domain' set to null\n        [{'name': 'foo', 'value': 'bar', 'domain': None}],\n        False,\n    ),\n    (\n        # With 'domain' set to a URL\n        [{'name': 'foo', 'value': 'bar', 'domain': DUMMY_HOST}],\n        False,\n    ),\n])\ndef test_cookie_security_warnings_on_raw_cookies(basic_session, mock_env, cookies, expect_warning):\n    with open_raw_session(basic_session) as raw_session:\n        raw_session['cookies'] = cookies\n\n    with open_session(basic_session, mock_env, read_only=True):\n        warning = b'Outdated layout detected'\n        stderr = read_stderr(mock_env)\n\n        if expect_warning:\n            assert warning in stderr\n        else:\n            assert warning not in stderr\n\n\ndef test_old_session_cookie_layout_loading(basic_session, httpbin, mock_env):\n    with open_session(basic_session, mock_env) as session:\n        # Use the old layout & set a cookie\n        session['cookies'] = {}\n        session.cookies.set('foo', 'bar')\n\n    response = http(\n        '--session', str(basic_session),\n        httpbin + '/cookies'\n    )\n    assert response.json['cookies'] == {'foo': 'bar'}\n\n\n@pytest.mark.parametrize('layout_type', [\n    dict, list\n])\ndef test_session_cookie_layout_preservation(basic_session, mock_env, layout_type):\n    with open_session(basic_session, mock_env) as session:\n        session['cookies'] = layout_type()\n        session.cookies.set('foo', 'bar')\n        session.save()\n\n    with open_session(basic_session, mock_env, read_only=True) as session:\n        assert isinstance(session['cookies'], layout_type)\n\n\n@pytest.mark.parametrize('layout_type', [\n    dict, list\n])\ndef test_session_cookie_layout_preservation_on_new_cookies(basic_session, httpbin, mock_env, layout_type):\n    with open_session(basic_session, mock_env) as session:\n        session['cookies'] = layout_type()\n        session.cookies.set('foo', 'bar')\n        session.save()\n\n    http(\n        '--session', str(basic_session),\n        httpbin + '/cookies/set/baz/quux'\n    )\n\n    with open_session(basic_session, mock_env, read_only=True) as session:\n        assert isinstance(session['cookies'], layout_type)\n\n\n@pytest.mark.parametrize('headers, expect_warning', [\n    # Old-style header format\n    (\n        {},\n        False\n    ),\n    (\n        {'Foo': 'bar'},\n        True\n    ),\n    # New style header format\n    (\n        [],\n        False\n    ),\n    (\n        [{'name': 'Foo', 'value': 'Bar'}],\n        False\n    ),\n])\ndef test_headers_old_layout_warning(basic_session, mock_env, headers, expect_warning):\n    with open_raw_session(basic_session) as raw_session:\n        raw_session['headers'] = headers\n\n    with open_session(basic_session, mock_env, read_only=True):\n        warning = b'Outdated layout detected'\n        stderr = read_stderr(mock_env)\n\n        if expect_warning:\n            assert warning in stderr\n        else:\n            assert warning not in stderr\n\n\ndef test_outdated_layout_mixed(basic_session, mock_env):\n    with open_raw_session(basic_session) as raw_session:\n        raw_session['headers'] = {'Foo': 'Bar'}\n        raw_session['cookies'] = {\n            'cookie': {\n                'value': 'value'\n            }\n        }\n\n    with open_session(basic_session, mock_env, read_only=True):\n        stderr = read_stderr(mock_env)\n        # We should only see 1 warning.\n        assert stderr.count(b'Outdated layout') == 1\n\n\ndef test_old_session_header_layout_loading(basic_session, httpbin, mock_env):\n    with open_session(basic_session, mock_env) as session:\n        # Use the old layout & set a header\n        session['headers'] = {}\n        session._headers.add('Foo', 'Bar')\n\n    response = http(\n        '--session', str(basic_session),\n        httpbin + '/get'\n    )\n    assert response.json['headers']['Foo'] == 'Bar'\n\n\n@pytest.mark.parametrize('layout_type', [\n    dict, list\n])\ndef test_session_header_layout_preservation(basic_session, mock_env, layout_type):\n    with open_session(basic_session, mock_env) as session:\n        session['headers'] = layout_type()\n        session._headers.add('Foo', 'Bar')\n\n    with open_session(basic_session, mock_env, read_only=True) as session:\n        assert isinstance(session['headers'], layout_type)\n\n\n@pytest.mark.parametrize('layout_type', [\n    dict, list\n])\ndef test_session_header_layout_preservation_on_new_headers(basic_session, httpbin, mock_env, layout_type):\n    with open_session(basic_session, mock_env) as session:\n        session['headers'] = layout_type()\n        session._headers.add('Foo', 'Bar')\n\n    http(\n        '--session', str(basic_session),\n        httpbin + '/get',\n        'Baz:Quux'\n    )\n\n    with open_session(basic_session, mock_env, read_only=True) as session:\n        assert isinstance(session['headers'], layout_type)\n\n\ndef test_session_multiple_headers_with_same_name(basic_session, httpbin):\n    http(\n        '--session', str(basic_session),\n        httpbin + '/get',\n        'Foo:bar',\n        'Foo:baz',\n        'Foo:bar'\n    )\n\n    r = http(\n        '--offline',\n        '--session', str(basic_session),\n        httpbin + '/get',\n    )\n    assert r.count('Foo: bar') == 2\n    assert 'Foo: baz' in r\n\n\n@pytest.mark.parametrize(\n    'server, expected_cookies',\n    [\n        (\n            'localhost_http_server',\n            {'secure_cookie': 'foo', 'insecure_cookie': 'bar'}\n        ),\n        (\n            'remote_httpbin',\n            {'insecure_cookie': 'bar'}\n        )\n    ]\n)\ndef test_secure_cookies_on_localhost(mock_env, tmp_path, server, expected_cookies, request):\n    server = request.getfixturevalue(server)\n    session_path = tmp_path / 'session.json'\n    http(\n        '--session', str(session_path),\n        server + '/cookies/set',\n        'secure_cookie==foo',\n        'insecure_cookie==bar'\n    )\n\n    with open_session(session_path, mock_env) as session:\n        for cookie in session.cookies:\n            if cookie.name == 'secure_cookie':\n                cookie.secure = True\n\n    r = http(\n        '--session', str(session_path),\n        server + '/cookies'\n    )\n    assert r.json == {'cookies': expected_cookies}\n"
  },
  {
    "path": "tests/test_ssl.py",
    "content": "import ssl\n\nimport pytest\nimport pytest_httpbin.certs\nimport requests.exceptions\nimport urllib3\n\nfrom unittest import mock\n\nfrom httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS_STRING\nfrom httpie.status import ExitStatus\n\nfrom .utils import HTTP_OK, TESTS_ROOT, IS_PYOPENSSL, http\n\n\ntry:\n    # Handle OpenSSL errors, if installed.\n    # See <https://github.com/httpie/cli/issues/729>\n    # noinspection PyUnresolvedReferences\n    import OpenSSL.SSL\n    ssl_errors = (\n        requests.exceptions.SSLError,\n        OpenSSL.SSL.Error,\n        ValueError,  # TODO: Remove with OSS-65\n    )\nexcept ImportError:\n    ssl_errors = (\n        requests.exceptions.SSLError,\n    )\n\nCERTS_ROOT = TESTS_ROOT / 'client_certs'\nCLIENT_CERT = str(CERTS_ROOT / 'client.crt')\nCLIENT_KEY = str(CERTS_ROOT / 'client.key')\nCLIENT_PEM = str(CERTS_ROOT / 'client.pem')\n\n# In case of a regeneration, use the following commands\n# in the PWD_TESTS_ROOT:\n#   $ openssl genrsa -aes128 -passout pass:password 3072 > client.pem\n#   $ openssl req -new -x509 -nodes -days 10000 -key client.pem > client.pem\nPWD_TESTS_ROOT = CERTS_ROOT / 'password_protected'\nPWD_CLIENT_PEM = str(PWD_TESTS_ROOT / 'client.pem')\nPWD_CLIENT_KEY = str(PWD_TESTS_ROOT / 'client.key')\nPWD_CLIENT_PASS = 'password'\nPWD_CLIENT_INVALID_PASS = PWD_CLIENT_PASS + 'invalid'\n\n# We test against a local httpbin instance which uses a self-signed cert.\n# Requests without --verify=<CA_BUNDLE> will fail with a verification error.\n# See: https://github.com/kevin1024/pytest-httpbin#https-support\nCA_BUNDLE = pytest_httpbin.certs.where()\n\n\n@pytest.mark.parametrize('ssl_version',\n                         AVAILABLE_SSL_VERSION_ARG_MAPPING.keys())\ndef test_ssl_version(httpbin_secure, ssl_version):\n    try:\n        r = http(\n            '--ssl', ssl_version,\n            httpbin_secure + '/get'\n        )\n        assert HTTP_OK in r\n    except ssl_errors as e:\n        if ssl_version == 'ssl3':\n            # pytest-httpbin doesn't support ssl3\n            pass\n        elif e.__context__ is not None:  # Check if root cause was an unsupported TLS version\n            root = e.__context__\n            while root.__context__ is not None:\n                root = root.__context__\n            if isinstance(root, ssl.SSLError) and root.reason == \"TLSV1_ALERT_PROTOCOL_VERSION\":\n                pytest.skip(f'Unsupported TLS version: {ssl_version}')\n        elif 'No such protocol' in str(e):  # TODO: Remove with OSS-65\n            pytest.skip(f'Unsupported TLS version: {ssl_version}')\n        else:\n            raise\n\n\nclass TestClientCert:\n\n    def test_cert_and_key(self, httpbin_secure):\n        r = http(httpbin_secure + '/get',\n                 '--cert', CLIENT_CERT,\n                 '--cert-key', CLIENT_KEY)\n        assert HTTP_OK in r\n\n    def test_cert_pem(self, httpbin_secure):\n        r = http(httpbin_secure + '/get',\n                 '--cert', CLIENT_PEM)\n        assert HTTP_OK in r\n\n    def test_cert_file_not_found(self, httpbin_secure):\n        r = http(httpbin_secure + '/get',\n                 '--cert', '/__not_found__',\n                 tolerate_error_exit_status=True)\n        assert r.exit_status == ExitStatus.ERROR\n        assert '/__not_found__: No such file or directory' in r.stderr\n\n    def test_cert_file_invalid(self, httpbin_secure):\n        with pytest.raises(ssl_errors):\n            http(httpbin_secure + '/get',\n                 '--cert', __file__)\n\n    def test_cert_ok_but_missing_key(self, httpbin_secure):\n        with pytest.raises(ssl_errors):\n            http(httpbin_secure + '/get',\n                 '--cert', CLIENT_CERT)\n\n\nclass TestServerCert:\n\n    def test_verify_no_OK(self, httpbin_secure):\n        # Avoid warnings when explicitly testing insecure requests\n        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)\n        r = http(httpbin_secure.url + '/get', '--verify=no')\n        assert HTTP_OK in r\n\n    @pytest.mark.parametrize('verify_value', ['false', 'fALse'])\n    def test_verify_false_OK(self, httpbin_secure, verify_value):\n        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)\n        r = http(httpbin_secure.url + '/get', '--verify', verify_value)\n        assert HTTP_OK in r\n\n    def test_verify_custom_ca_bundle_path(\n        self, httpbin_secure_untrusted\n    ):\n        r = http(httpbin_secure_untrusted + '/get', '--verify', CA_BUNDLE)\n        assert HTTP_OK in r\n\n    def test_self_signed_server_cert_by_default_raises_ssl_error(\n        self,\n        httpbin_secure_untrusted\n    ):\n        with pytest.raises(ssl_errors):\n            http(httpbin_secure_untrusted.url + '/get')\n\n    def test_verify_custom_ca_bundle_invalid_path(self, httpbin_secure):\n        # since 2.14.0 requests raises IOError (an OSError subclass)\n        with pytest.raises(ssl_errors + (OSError,)):\n            http(httpbin_secure.url + '/get', '--verify', '/__not_found__')\n\n    def test_verify_custom_ca_bundle_invalid_bundle(self, httpbin_secure):\n        with pytest.raises(ssl_errors):\n            http(httpbin_secure.url + '/get', '--verify', __file__)\n\n\ndef test_ciphers(httpbin_secure):\n    r = http(\n        httpbin_secure.url + '/get',\n        '--ciphers',\n        DEFAULT_SSL_CIPHERS_STRING,\n    )\n    assert HTTP_OK in r\n\n\n@pytest.mark.skipif(IS_PYOPENSSL, reason='pyOpenSSL uses a different message format.')\ndef test_ciphers_none_can_be_selected(httpbin_secure):\n    r = http(\n        httpbin_secure.url + '/get',\n        '--ciphers',\n        '__FOO__',\n        tolerate_error_exit_status=True,\n    )\n    assert r.exit_status == ExitStatus.ERROR\n    # Linux/macOS:\n    #   http: error: SSLError: ('No cipher can be selected.',)\n    # OpenBSD:\n    #   <https://marc.info/?l=openbsd-ports&m=159251948515635&w=2>\n    #   http: error: Error: [('SSL routines', '(UNKNOWN)SSL_internal', 'no cipher match')]\n    assert 'cipher' in r.stderr\n\n\ndef test_pyopenssl_presence():\n    if not IS_PYOPENSSL:\n        assert not urllib3.util.ssl_.IS_PYOPENSSL\n        assert not urllib3.util.IS_PYOPENSSL\n    else:\n        assert urllib3.util.ssl_.IS_PYOPENSSL\n        assert urllib3.util.IS_PYOPENSSL\n\n\n@mock.patch('httpie.cli.argtypes.SSLCredentials._prompt_password',\n            new=lambda self, prompt: PWD_CLIENT_PASS)\ndef test_password_protected_cert_prompt(httpbin_secure):\n    r = http(httpbin_secure + '/get',\n             '--cert', PWD_CLIENT_PEM,\n             '--cert-key', PWD_CLIENT_KEY)\n    assert HTTP_OK in r\n\n\n@mock.patch('httpie.cli.argtypes.SSLCredentials._prompt_password',\n            new=lambda self, prompt: PWD_CLIENT_INVALID_PASS)\ndef test_password_protected_cert_prompt_invalid(httpbin_secure):\n    with pytest.raises(ssl_errors):\n        http(httpbin_secure + '/get',\n             '--cert', PWD_CLIENT_PEM,\n             '--cert-key', PWD_CLIENT_KEY)\n\n\ndef test_password_protected_cert_cli_arg(httpbin_secure):\n    r = http(httpbin_secure + '/get',\n             '--cert', PWD_CLIENT_PEM,\n             '--cert-key', PWD_CLIENT_KEY,\n             '--cert-key-pass', PWD_CLIENT_PASS)\n    assert HTTP_OK in r\n\n\ndef test_password_protected_cert_cli_arg_invalid(httpbin_secure):\n    with pytest.raises(ssl_errors):\n        http(httpbin_secure + '/get',\n             '--cert', PWD_CLIENT_PEM,\n             '--cert-key', PWD_CLIENT_KEY,\n             '--cert-key-pass', PWD_CLIENT_INVALID_PASS)\n"
  },
  {
    "path": "tests/test_stream.py",
    "content": "import json\n\nimport pytest\nimport responses\nfrom unittest.mock import Mock\n\nfrom httpie.compat import is_windows\nfrom httpie.cli.constants import PRETTY_MAP\nfrom httpie.output.streams import BINARY_SUPPRESSED_NOTICE\nfrom httpie.plugins import ConverterPlugin\nfrom httpie.plugins.registry import plugin_manager\n\nfrom .utils import StdinBytesIO, http, MockEnvironment, DUMMY_URL\nfrom .fixtures import (\n    ASCII_FILE_CONTENT,\n    BIN_FILE_CONTENT,\n    BIN_FILE_PATH,\n    FILE_CONTENT as UNICODE_FILE_CONTENT\n)\n\nPRETTY_OPTIONS = list(PRETTY_MAP.keys())\n\n\nclass SortJSONConverterPlugin(ConverterPlugin):\n    @classmethod\n    def supports(cls, mime):\n        return mime == 'json/bytes'\n\n    def convert(self, body):\n        body = body.lstrip(b'\\x00')\n        data = json.loads(body)\n        return 'application/json', json.dumps(data, sort_keys=True)\n\n\n# GET because httpbin 500s with binary POST body.\n\n\n@pytest.mark.skipif(is_windows,\n                    reason='Pretty redirect not supported under Windows')\ndef test_pretty_redirected_stream(httpbin):\n    \"\"\"Test that --stream works with prettified redirected output.\"\"\"\n    env = MockEnvironment(\n        colors=256,\n        stdin=StdinBytesIO(BIN_FILE_PATH.read_bytes()),\n        stdin_isatty=False,\n        stdout_isatty=False,\n    )\n    r = http('--verbose', '--pretty=all', '--stream', 'GET',\n             httpbin + '/get', env=env)\n    assert BINARY_SUPPRESSED_NOTICE.decode() in r\n\n\ndef test_pretty_stream_ensure_full_stream_is_retrieved(httpbin):\n    env = MockEnvironment(\n        stdin=StdinBytesIO(),\n        stdin_isatty=False,\n        stdout_isatty=False,\n    )\n    r = http('--pretty=format', '--stream', 'GET',\n             httpbin + '/stream/3', env=env)\n    assert r.count('/stream/3') == 3\n\n\n@pytest.mark.parametrize('pretty', PRETTY_OPTIONS)\n@pytest.mark.parametrize('stream', [True, False])\n@responses.activate\ndef test_pretty_options_with_and_without_stream_with_converter(pretty, stream):\n    plugin_manager.register(SortJSONConverterPlugin)\n    try:\n        # Cover PluginManager.__repr__()\n        assert 'SortJSONConverterPlugin' in str(plugin_manager)\n\n        body = b'\\x00{\"foo\":42,\\n\"bar\":\"baz\"}'\n        responses.add(responses.GET, DUMMY_URL, body=body,\n                      stream=True, content_type='json/bytes')\n\n        args = ['--pretty=' + pretty, 'GET', DUMMY_URL]\n        if stream:\n            args.insert(0, '--stream')\n        r = http(*args)\n\n        assert 'json/bytes' in r\n        if pretty == 'none':\n            assert BINARY_SUPPRESSED_NOTICE.decode() in r\n        else:\n            # Ensure the plugin was effectively used and the resulting JSON is sorted\n            assert '\"bar\": \"baz\",' in r\n            assert '\"foo\": 42' in r\n    finally:\n        plugin_manager.unregister(SortJSONConverterPlugin)\n\n\ndef test_encoded_stream(httpbin):\n    \"\"\"Test that --stream works with non-prettified\n    redirected terminal output.\"\"\"\n    env = MockEnvironment(\n        stdin=StdinBytesIO(BIN_FILE_PATH.read_bytes()),\n        stdin_isatty=False,\n    )\n    r = http('--pretty=none', '--stream', '--verbose', 'GET',\n             httpbin + '/get', env=env)\n    assert BINARY_SUPPRESSED_NOTICE.decode() in r\n\n\ndef test_redirected_stream(httpbin):\n    \"\"\"Test that --stream works with non-prettified\n    redirected terminal output.\"\"\"\n    env = MockEnvironment(\n        stdout_isatty=False,\n        stdin_isatty=False,\n        stdin=StdinBytesIO(BIN_FILE_PATH.read_bytes()),\n    )\n    r = http('--pretty=none', '--stream', '--verbose', 'GET',\n             httpbin + '/get', env=env)\n    assert BIN_FILE_CONTENT in r\n\n\n# /drip endpoint produces 3 individual lines,\n# if we set text/event-stream HTTPie should stream\n# it by default. Otherwise, it will buffer and then\n# print.\n@pytest.mark.parametrize('extras, expected', [\n    (\n        ['Accept:text/event-stream'],\n        3\n    ),\n    (\n        ['Accept:text/event-stream; charset=utf-8'],\n        3\n    ),\n    (\n        ['Accept:text/plain'],\n        1\n    )\n])\ndef test_auto_streaming(http_server, extras, expected):\n    env = MockEnvironment()\n    env.stdout.write = Mock()\n    http(http_server + '/drip', *extras, env=env)\n    assert len([\n        call_arg\n        for call_arg in env.stdout.write.call_args_list\n        if 'test' in call_arg[0][0]\n    ]) == expected\n\n\ndef test_streaming_encoding_detection(http_server):\n    r = http('--stream', http_server + '/stream/encoding/random')\n    assert ASCII_FILE_CONTENT in r\n    assert UNICODE_FILE_CONTENT in r\n"
  },
  {
    "path": "tests/test_tokens.py",
    "content": "\"\"\"\nThe ideas behind these test and the named templates is to ensure consistent output\nacross all supported different scenarios:\n\nTODO: cover more scenarios\n * terminal vs. redirect stdout\n * different combinations of `--print=HBhb` (request/response headers/body)\n * multipart requests\n * streamed uploads\n\n\"\"\"\nfrom .utils.matching import assert_output_matches, Expect, ExpectSequence\nfrom .utils import http, HTTP_OK, MockEnvironment\n\n\ndef test_headers():\n    r = http('--print=H', '--offline', 'pie.dev')\n    assert_output_matches(r, [Expect.REQUEST_HEADERS])\n\n\ndef test_redirected_headers():\n    r = http('--print=H', '--offline', 'pie.dev', env=MockEnvironment(stdout_isatty=False))\n    assert_output_matches(r, [Expect.REQUEST_HEADERS])\n\n\ndef test_terminal_headers_and_body():\n    r = http('--print=HB', '--offline', 'pie.dev', 'AAA=BBB')\n    assert_output_matches(r, ExpectSequence.TERMINAL_REQUEST)\n\n\ndef test_terminal_request_headers_response_body(httpbin):\n    r = http('--print=Hb', httpbin + '/get')\n    assert_output_matches(r, ExpectSequence.TERMINAL_REQUEST)\n\n\ndef test_raw_request_headers_response_body(httpbin):\n    r = http('--print=Hb', httpbin + '/get', env=MockEnvironment(stdout_isatty=False))\n    assert_output_matches(r, ExpectSequence.RAW_REQUEST)\n\n\ndef test_terminal_request_headers_response_headers(httpbin):\n    r = http('--print=Hh', httpbin + '/get')\n    assert_output_matches(r, [Expect.REQUEST_HEADERS, Expect.RESPONSE_HEADERS])\n\n\ndef test_raw_request_headers_response_headers(httpbin):\n    r = http('--print=Hh', httpbin + '/get', env=MockEnvironment(stdout_isatty=False))\n    assert_output_matches(r, [Expect.REQUEST_HEADERS, Expect.RESPONSE_HEADERS])\n\n\ndef test_terminal_request_body_response_body(httpbin):\n    r = http('--print=Hh', httpbin + '/get')\n    assert_output_matches(r, [Expect.REQUEST_HEADERS, Expect.RESPONSE_HEADERS])\n\n\ndef test_raw_headers_and_body():\n    r = http(\n        '--print=HB', '--offline', 'pie.dev', 'AAA=BBB',\n        env=MockEnvironment(stdout_isatty=False),\n    )\n    assert_output_matches(r, ExpectSequence.RAW_REQUEST)\n\n\ndef test_raw_body():\n    r = http('--print=B', '--offline', 'pie.dev', 'AAA=BBB', env=MockEnvironment(stdout_isatty=False))\n    assert_output_matches(r, ExpectSequence.RAW_BODY)\n\n\ndef test_raw_exchange(httpbin):\n    r = http('--verbose', httpbin + '/post', 'a=b', env=MockEnvironment(stdout_isatty=False))\n    assert HTTP_OK in r\n    assert_output_matches(r, ExpectSequence.RAW_EXCHANGE)\n\n\ndef test_terminal_exchange(httpbin):\n    r = http('--verbose', httpbin + '/post', 'a=b')\n    assert HTTP_OK in r\n    assert_output_matches(r, ExpectSequence.TERMINAL_EXCHANGE)\n\n\ndef test_headers_multipart_body_separator():\n    r = http('--print=HB', '--multipart', '--offline', 'pie.dev', 'AAA=BBB')\n    assert_output_matches(r, ExpectSequence.TERMINAL_REQUEST)\n\n\ndef test_redirected_headers_multipart_no_separator():\n    r = http(\n        '--print=HB', '--multipart', '--offline', 'pie.dev', 'AAA=BBB',\n        env=MockEnvironment(stdout_isatty=False),\n    )\n    assert_output_matches(r, ExpectSequence.RAW_REQUEST)\n\n\ndef test_verbose_chunked(httpbin_with_chunked_support):\n    r = http('--verbose', '--chunked', httpbin_with_chunked_support + '/post', 'hello=world')\n    assert HTTP_OK in r\n    assert 'Transfer-Encoding: chunked' in r\n    assert_output_matches(r, ExpectSequence.TERMINAL_EXCHANGE)\n\n\ndef test_request_headers_response_body(httpbin):\n    r = http('--print=Hb', httpbin + '/get')\n    assert_output_matches(r, ExpectSequence.TERMINAL_REQUEST)\n\n\ndef test_request_single_verbose(httpbin):\n    r = http('-v', httpbin + '/post', 'hello=world')\n    assert_output_matches(r, ExpectSequence.TERMINAL_EXCHANGE)\n\n\ndef test_request_double_verbose(httpbin):\n    r = http('-vv', httpbin + '/post', 'hello=world')\n    assert_output_matches(r, ExpectSequence.TERMINAL_EXCHANGE_META)\n\n\ndef test_request_meta(httpbin):\n    r = http('--meta', httpbin + '/get')\n    assert_output_matches(r, [Expect.RESPONSE_META])\n"
  },
  {
    "path": "tests/test_transport_plugin.py",
    "content": "from io import BytesIO\n\nfrom requests.adapters import BaseAdapter\nfrom requests.models import Response\nfrom requests.utils import get_encoding_from_headers\n\nfrom httpie.plugins import TransportPlugin\nfrom httpie.plugins.registry import plugin_manager\n\nfrom .utils import HTTP_OK, http\n\nSCHEME = 'http+fake'\n\n\nclass FakeAdapter(BaseAdapter):\n    def send(self, request, **kwargs):\n        response = Response()\n        response.status_code = 200\n        response.reason = 'OK'\n        response.headers = {\n            'Content-Type': 'text/html; charset=UTF-8',\n        }\n        response.encoding = get_encoding_from_headers(response.headers)\n        response.raw = BytesIO(b'<!doctype html><html>Hello</html>')\n        return response\n\n\nclass FakeTransportPlugin(TransportPlugin):\n    name = 'Fake Transport'\n\n    prefix = SCHEME\n\n    def get_adapter(self):\n        return FakeAdapter()\n\n\ndef test_transport_from_requests_response(httpbin):\n    plugin_manager.register(FakeTransportPlugin)\n    try:\n        r = http(f'{SCHEME}://example.com')\n        assert HTTP_OK in r\n        assert 'Hello' in r\n        assert 'Content-Type: text/html; charset=UTF-8' in r\n    finally:\n        plugin_manager.unregister(FakeTransportPlugin)\n"
  },
  {
    "path": "tests/test_update_warnings.py",
    "content": "import json\nimport tempfile\nimport time\nfrom contextlib import suppress\nfrom datetime import datetime\nfrom pathlib import Path\n\nimport pytest\n\nfrom httpie.internal.daemon_runner import STATUS_FILE\nfrom httpie.internal.daemons import spawn_daemon\nfrom httpie.status import ExitStatus\n\nfrom .utils import PersistentMockEnvironment, http, httpie\n\nBUILD_CHANNEL = 'test'\nBUILD_CHANNEL_2 = 'test2'\nUNKNOWN_BUILD_CHANNEL = 'test3'\n\nHIGHEST_VERSION = '999.999.999'\nLOWEST_VERSION = '1.1.1'\n\nFIXED_DATE = datetime(1970, 1, 1).isoformat()\n\nMAX_ATTEMPT = 40\nMAX_TIMEOUT = 2.0\n\n\ndef check_update_warnings(text):\n    return 'A new HTTPie release' in text\n\n\n@pytest.mark.requires_external_processes\ndef test_daemon_runner():\n    # We have a pseudo daemon task called 'check_status'\n    # which creates a temp file called STATUS_FILE under\n    # user's temp directory. This test simply ensures that\n    # we create a daemon that successfully performs the\n    # external task.\n\n    status_file = Path(tempfile.gettempdir()) / STATUS_FILE\n    with suppress(FileNotFoundError):\n        status_file.unlink()\n\n    spawn_daemon('check_status')\n\n    for attempt in range(MAX_ATTEMPT):\n        time.sleep(MAX_TIMEOUT / MAX_ATTEMPT)\n        if status_file.exists():\n            break\n    else:\n        pytest.fail(\n            'Maximum number of attempts failed for daemon status check.'\n        )\n\n    assert status_file.exists()\n\n\ndef test_fetch(static_fetch_data, without_warnings):\n    http('fetch_updates', '--daemon', env=without_warnings)\n\n    with open(without_warnings.config.version_info_file) as stream:\n        version_data = json.load(stream)\n\n    assert version_data['last_warned_date'] is None\n    assert version_data['last_fetched_date'] is not None\n    assert (\n        version_data['last_released_versions'][BUILD_CHANNEL]\n        == HIGHEST_VERSION\n    )\n    assert (\n        version_data['last_released_versions'][BUILD_CHANNEL_2]\n        == LOWEST_VERSION\n    )\n\n\ndef test_fetch_dont_override_existing_layout(\n    static_fetch_data, without_warnings\n):\n    with open(without_warnings.config.version_info_file, 'w') as stream:\n        existing_layout = {\n            'last_warned_date': FIXED_DATE,\n            'last_fetched_date': FIXED_DATE,\n            'last_released_versions': {BUILD_CHANNEL: LOWEST_VERSION},\n        }\n        json.dump(existing_layout, stream)\n\n    http('fetch_updates', '--daemon', env=without_warnings)\n\n    with open(without_warnings.config.version_info_file) as stream:\n        version_data = json.load(stream)\n\n    # The \"last updated at\" field should not be modified, but the\n    # rest need to be updated.\n    assert version_data['last_warned_date'] == FIXED_DATE\n    assert version_data['last_fetched_date'] != FIXED_DATE\n    assert (\n        version_data['last_released_versions'][BUILD_CHANNEL]\n        == HIGHEST_VERSION\n    )\n\n\ndef test_fetch_broken_json(static_fetch_data, without_warnings):\n    with open(without_warnings.config.version_info_file, 'w') as stream:\n        stream.write('$$broken$$')\n\n    http('fetch_updates', '--daemon', env=without_warnings)\n\n    with open(without_warnings.config.version_info_file) as stream:\n        version_data = json.load(stream)\n\n    assert (\n        version_data['last_released_versions'][BUILD_CHANNEL]\n        == HIGHEST_VERSION\n    )\n\n\ndef test_check_updates_disable_warnings(\n    without_warnings, httpbin, fetch_update_mock\n):\n    r = http(httpbin + '/get', env=without_warnings)\n    assert not fetch_update_mock.called\n    assert not check_update_warnings(r.stderr)\n\n\ndef test_check_updates_first_invocation(\n    with_warnings, httpbin, fetch_update_mock\n):\n    r = http(httpbin + '/get', env=with_warnings)\n    assert fetch_update_mock.called\n    assert not check_update_warnings(r.stderr)\n\n\n@pytest.mark.parametrize(\n    ['should_issue_warning', 'build_channel'],\n    [\n        (False, 'lower_build_channel'),\n        (True, 'higher_build_channel'),\n    ],\n)\ndef test_check_updates_first_time_after_data_fetch(\n    with_warnings,\n    httpbin,\n    fetch_update_mock,\n    static_fetch_data,\n    should_issue_warning,\n    build_channel,\n    request,\n):\n    request.getfixturevalue(build_channel)\n    http('fetch_updates', '--daemon', env=with_warnings)\n    r = http(httpbin + '/get', env=with_warnings)\n\n    assert not fetch_update_mock.called\n    assert (not should_issue_warning) or check_update_warnings(r.stderr)\n\n\ndef test_check_updates_first_time_after_data_fetch_unknown_build_channel(\n    with_warnings,\n    httpbin,\n    fetch_update_mock,\n    static_fetch_data,\n    unknown_build_channel,\n):\n    http('fetch_updates', '--daemon', env=with_warnings)\n    r = http(httpbin + '/get', env=with_warnings)\n\n    assert not fetch_update_mock.called\n    assert not check_update_warnings(r.stderr)\n\n\ndef test_cli_check_updates(\n    static_fetch_data, higher_build_channel\n):\n    r = httpie('cli', 'check-updates')\n    assert r.exit_status == ExitStatus.SUCCESS\n    assert check_update_warnings(r)\n\n\n@pytest.mark.parametrize(\n    'build_channel', [\n        'lower_build_channel',\n        'unknown_build_channel',\n    ]\n)\ndef test_cli_check_updates_not_shown(\n    static_fetch_data, build_channel, request\n):\n    request.getfixturevalue(build_channel)\n    r = httpie('cli', 'check-updates')\n    assert r.exit_status == ExitStatus.SUCCESS\n    assert not check_update_warnings(r)\n\n\n@pytest.fixture\ndef with_warnings(tmp_path):\n    env = PersistentMockEnvironment()\n    env.config['version_info_file'] = tmp_path / 'version.json'\n    env.config['disable_update_warnings'] = False\n    return env\n\n\n@pytest.fixture\ndef without_warnings(tmp_path):\n    env = PersistentMockEnvironment()\n    env.config['version_info_file'] = tmp_path / 'version.json'\n    env.config['disable_update_warnings'] = True\n    return env\n\n\n@pytest.fixture\ndef fetch_update_mock(mocker):\n    mock_fetch = mocker.patch('httpie.internal.update_warnings.fetch_updates')\n    return mock_fetch\n\n\n@pytest.fixture\ndef static_fetch_data(mocker):\n    mock_get = mocker.patch('requests.get')\n    mock_get.return_value.status_code = 200\n    mock_get.return_value.json.return_value = {\n        BUILD_CHANNEL: HIGHEST_VERSION,\n        BUILD_CHANNEL_2: LOWEST_VERSION,\n    }\n    return mock_get\n\n\n@pytest.fixture\ndef unknown_build_channel(mocker):\n    mocker.patch('httpie.internal.update_warnings.BUILD_CHANNEL', UNKNOWN_BUILD_CHANNEL)\n\n\n@pytest.fixture\ndef higher_build_channel(mocker):\n    mocker.patch('httpie.internal.update_warnings.BUILD_CHANNEL', BUILD_CHANNEL)\n\n\n@pytest.fixture\ndef lower_build_channel(mocker):\n    mocker.patch('httpie.internal.update_warnings.BUILD_CHANNEL', BUILD_CHANNEL_2)\n"
  },
  {
    "path": "tests/test_uploads.py",
    "content": "import os\nimport json\nimport sys\nimport subprocess\nimport time\nimport contextlib\nimport httpie.__main__ as main\n\nimport pytest\n\nfrom httpie.cli.exceptions import ParseError\nfrom httpie.client import FORM_CONTENT_TYPE\nfrom httpie.compat import is_windows\nfrom httpie.status import ExitStatus\nfrom .utils import (\n    MockEnvironment, StdinBytesIO, http,\n    HTTP_OK,\n)\nfrom .fixtures import FILE_PATH_ARG, FILE_PATH, FILE_CONTENT\n\nMAX_RESPONSE_WAIT_TIME = 5\n\n\ndef test_chunked_json(httpbin_with_chunked_support):\n    r = http(\n        '--verbose',\n        '--chunked',\n        httpbin_with_chunked_support + '/post',\n        'hello=world',\n    )\n    assert HTTP_OK in r\n    assert 'Transfer-Encoding: chunked' in r\n    assert r.count('hello') == 3\n\n\ndef test_chunked_form(httpbin_with_chunked_support):\n    r = http(\n        '--verbose',\n        '--chunked',\n        '--form',\n        httpbin_with_chunked_support + '/post',\n        'hello=world',\n    )\n    assert HTTP_OK in r\n    assert 'Transfer-Encoding: chunked' in r\n    assert r.count('hello') == 2\n\n\ndef test_chunked_stdin(httpbin_with_chunked_support):\n    r = http(\n        '--verbose',\n        '--chunked',\n        httpbin_with_chunked_support + '/post',\n        env=MockEnvironment(\n            stdin=StdinBytesIO(FILE_PATH.read_bytes()),\n            stdin_isatty=False,\n        )\n    )\n    assert HTTP_OK in r\n    assert 'Transfer-Encoding: chunked' in r\n    assert r.count(FILE_CONTENT) == 2\n\n\ndef test_chunked_stdin_multiple_chunks(httpbin_with_chunked_support):\n    data = FILE_PATH.read_bytes()\n    stdin_bytes = data + b'\\n' + data\n    r = http(\n        '--verbose',\n        '--chunked',\n        httpbin_with_chunked_support + '/post',\n        env=MockEnvironment(\n            stdin=StdinBytesIO(stdin_bytes),\n            stdin_isatty=False,\n            stdout_isatty=True,\n        )\n    )\n    assert HTTP_OK in r\n    assert 'Transfer-Encoding: chunked' in r\n    assert r.count(FILE_CONTENT) == 4\n\n\ndef test_chunked_raw(httpbin_with_chunked_support):\n    r = http(\n        '--verbose',\n        '--chunked',\n        httpbin_with_chunked_support + '/post',\n        '--raw',\n        json.dumps({'a': 1, 'b': '2fafds', 'c': '🥰'}),\n    )\n    assert HTTP_OK in r\n    assert 'Transfer-Encoding: chunked' in r\n\n\n@contextlib.contextmanager\ndef stdin_processes(httpbin, *args, warn_threshold=0.1):\n    process_1 = subprocess.Popen(\n        [\n            \"cat\"\n        ],\n        stdin=subprocess.PIPE,\n        stdout=subprocess.PIPE\n    )\n    process_2 = subprocess.Popen(\n        [\n            sys.executable,\n            main.__file__,\n            \"POST\",\n            httpbin + \"/post\",\n            *args\n        ],\n        stdin=process_1.stdout,\n        stderr=subprocess.PIPE,\n        env={\n            **os.environ,\n            \"HTTPIE_STDIN_READ_WARN_THRESHOLD\": str(warn_threshold)\n        }\n    )\n    try:\n        yield process_1, process_2\n    finally:\n        process_1.terminate()\n        process_2.terminate()\n\n\n@pytest.mark.parametrize(\"wait\", (True, False))\n@pytest.mark.requires_external_processes\n@pytest.mark.skipif(is_windows, reason=\"Windows doesn't support select() calls into files\")\ndef test_reading_from_stdin(httpbin, wait):\n    with stdin_processes(httpbin) as (process_1, process_2):\n        process_1.communicate(timeout=0.1, input=b\"bleh\")\n        # Since there is data, it doesn't matter if there\n        # you wait or not.\n        if wait:\n            time.sleep(1)\n\n        try:\n            _, errs = process_2.communicate(timeout=MAX_RESPONSE_WAIT_TIME)\n        except subprocess.TimeoutExpired:\n            errs = b''\n\n        assert b'> warning: no stdin data read in 0.1s' not in errs\n\n\n@pytest.mark.requires_external_processes\n@pytest.mark.skipif(is_windows, reason=\"Windows doesn't support select() calls into files\")\ndef test_stdin_read_warning(httpbin):\n    with stdin_processes(httpbin) as (process_1, process_2):\n        # Wait before sending any data\n        time.sleep(1)\n        process_1.communicate(timeout=0.1, input=b\"bleh\\n\")\n\n        try:\n            _, errs = process_2.communicate(timeout=MAX_RESPONSE_WAIT_TIME)\n        except subprocess.TimeoutExpired:\n            errs = b''\n\n        assert b'> warning: no stdin data read in 0.1s' in errs\n\n\n@pytest.mark.requires_external_processes\n@pytest.mark.skipif(is_windows, reason=\"Windows doesn't support select() calls into files\")\ndef test_stdin_read_warning_with_quiet(httpbin):\n    with stdin_processes(httpbin, \"-qq\") as (process_1, process_2):\n        # Wait before sending any data\n        time.sleep(1)\n        process_1.communicate(timeout=0.1, input=b\"bleh\\n\")\n\n        try:\n            _, errs = process_2.communicate(timeout=MAX_RESPONSE_WAIT_TIME)\n        except subprocess.TimeoutExpired:\n            errs = b''\n\n        assert b'> warning: no stdin data read in 0.1s' not in errs\n\n\n@pytest.mark.requires_external_processes\n@pytest.mark.skipif(is_windows, reason=\"Windows doesn't support select() calls into files\")\ndef test_stdin_read_warning_blocking_exit(httpbin):\n    # Use a very large number.\n    with stdin_processes(httpbin, warn_threshold=999) as (process_1, process_2):\n        # Wait before sending any data\n        time.sleep(1)\n        process_1.communicate(timeout=0.1, input=b\"some input\\n\")\n\n        # If anything goes wrong, and the thread starts the block this\n        # will timeout and let us know.\n        process_2.communicate(timeout=MAX_RESPONSE_WAIT_TIME)\n\n\nclass TestMultipartFormDataFileUpload:\n\n    def test_non_existent_file_raises_parse_error(self, httpbin):\n        with pytest.raises(ParseError):\n            http('--form',\n                 'POST', httpbin + '/post', 'foo@/__does_not_exist__')\n\n    def test_upload_ok(self, httpbin):\n        r = http('--form', '--verbose', 'POST', httpbin + '/post',\n                 f'test-file@{FILE_PATH_ARG}', 'foo=bar')\n        assert HTTP_OK in r\n        assert 'Content-Disposition: form-data; name=\"foo\"' in r\n        assert 'Content-Disposition: form-data; name=\"test-file\";' \\\n               f' filename=\"{os.path.basename(FILE_PATH)}\"' in r\n        assert FILE_CONTENT in r\n        assert '\"foo\": \"bar\"' in r\n        assert 'Content-Type: text/plain' in r\n\n    def test_upload_multiple_fields_with_the_same_name(self, httpbin):\n        r = http('--form', '--verbose', 'POST', httpbin + '/post',\n                 f'test-file@{FILE_PATH_ARG}',\n                 f'test-file@{FILE_PATH_ARG}')\n        assert HTTP_OK in r\n        assert r.count('Content-Disposition: form-data; name=\"test-file\";'\n                       f' filename=\"{os.path.basename(FILE_PATH)}\"') == 2\n        # Should be 4, but is 3 because httpbin\n        # doesn't seem to support filed field lists\n        assert r.count(FILE_CONTENT) in [3, 4]\n        assert r.count('Content-Type: text/plain') == 2\n\n    def test_upload_custom_content_type(self, httpbin):\n        r = http(\n            '--form',\n            '--verbose',\n            httpbin + '/post',\n            f'test-file@{FILE_PATH_ARG};type=image/vnd.microsoft.icon'\n        )\n        assert HTTP_OK in r\n        # Content type is stripped from the filename\n        assert 'Content-Disposition: form-data; name=\"test-file\";' \\\n               f' filename=\"{os.path.basename(FILE_PATH)}\"' in r\n        assert r.count(FILE_CONTENT) == 2\n        assert 'Content-Type: image/vnd.microsoft.icon' in r\n\n    def test_form_no_files_urlencoded(self, httpbin):\n        r = http(\n            '--form',\n            '--verbose',\n            httpbin + '/post',\n            'AAAA=AAA',\n            'BBB=BBB',\n        )\n        assert HTTP_OK in r\n        assert FORM_CONTENT_TYPE in r\n\n    def test_multipart(self, httpbin):\n        r = http(\n            '--verbose',\n            '--multipart',\n            httpbin + '/post',\n            'AAAA=AAA',\n            'BBB=BBB',\n        )\n        assert HTTP_OK in r\n        assert FORM_CONTENT_TYPE not in r\n        assert 'multipart/form-data' in r\n\n    def test_form_multipart_custom_boundary(self, httpbin):\n        boundary = 'HTTPIE_FTW'\n        r = http(\n            '--print=HB',\n            '--check-status',\n            '--multipart',\n            f'--boundary={boundary}',\n            httpbin + '/post',\n            'AAAA=AAA',\n            'BBB=BBB',\n        )\n        assert f'multipart/form-data; boundary={boundary}' in r\n        assert r.count(boundary) == 4\n\n    def test_multipart_custom_content_type_boundary_added(self, httpbin):\n        boundary = 'HTTPIE_FTW'\n        r = http(\n            '--print=HB',\n            '--check-status',\n            '--multipart',\n            f'--boundary={boundary}',\n            httpbin + '/post',\n            'Content-Type: multipart/magic',\n            'AAAA=AAA',\n            'BBB=BBB',\n        )\n        assert f'multipart/magic; boundary={boundary}' in r\n        assert r.count(boundary) == 4\n\n    def test_multipart_custom_content_type_boundary_preserved(self, httpbin):\n        # Allow explicit nonsense requests.\n        boundary_in_header = 'HEADER_BOUNDARY'\n        boundary_in_body = 'BODY_BOUNDARY'\n        r = http(\n            '--print=HB',\n            '--check-status',\n            '--multipart',\n            f'--boundary={boundary_in_body}',\n            httpbin + '/post',\n            f'Content-Type: multipart/magic; boundary={boundary_in_header}',\n            'AAAA=AAA',\n            'BBB=BBB',\n        )\n        assert f'multipart/magic; boundary={boundary_in_header}' in r\n        assert r.count(boundary_in_body) == 3\n\n    def test_multipart_chunked(self, httpbin_with_chunked_support):\n        r = http(\n            '--verbose',\n            '--multipart',\n            '--chunked',\n            httpbin_with_chunked_support + '/post',\n            'AAA=AAA',\n        )\n        assert 'Transfer-Encoding: chunked' in r\n        assert 'multipart/form-data' in r\n        assert 'name=\"AAA\"' in r  # in request\n        assert '\"AAA\": \"AAA\"', r  # in response\n\n    def test_multipart_preserve_order(self, httpbin):\n        r = http(\n            '--form',\n            '--offline',\n            httpbin + '/post',\n            'text_field=foo',\n            f'file_field@{FILE_PATH_ARG}',\n        )\n        assert r.index('text_field') < r.index('file_field')\n\n        r = http(\n            '--form',\n            '--offline',\n            httpbin + '/post',\n            f'file_field@{FILE_PATH_ARG}',\n            'text_field=foo',\n        )\n        assert r.index('text_field') > r.index('file_field')\n\n\nclass TestRequestBodyFromFilePath:\n    \"\"\"\n    `http URL @file'\n\n    \"\"\"\n\n    def test_request_body_from_file_by_path(self, httpbin):\n        r = http(\n            '--verbose',\n            'POST', httpbin + '/post',\n            '@' + FILE_PATH_ARG,\n        )\n        assert HTTP_OK in r\n        assert r.count(FILE_CONTENT) == 2\n        assert '\"Content-Type\": \"text/plain\"' in r\n\n    def test_request_body_from_file_by_path_chunked(self, httpbin_with_chunked_support):\n        r = http(\n            '--verbose', '--chunked',\n            httpbin_with_chunked_support + '/post',\n            '@' + FILE_PATH_ARG,\n        )\n        assert HTTP_OK in r\n        assert 'Transfer-Encoding: chunked' in r\n        assert '\"Content-Type\": \"text/plain\"' in r\n        assert r.count(FILE_CONTENT) == 2\n\n    def test_request_body_from_file_by_path_with_explicit_content_type(\n            self, httpbin):\n        r = http('--verbose',\n                 'POST', httpbin + '/post', '@' + FILE_PATH_ARG,\n                 'Content-Type:text/plain; charset=UTF-8')\n        assert HTTP_OK in r\n        assert FILE_CONTENT in r\n        assert 'Content-Type: text/plain; charset=UTF-8' in r\n\n    def test_request_body_from_file_by_path_no_field_name_allowed(\n            self, httpbin):\n        env = MockEnvironment(stdin_isatty=True)\n        r = http('POST', httpbin + '/post', 'field-name@' + FILE_PATH_ARG,\n                 env=env, tolerate_error_exit_status=True)\n        assert 'perhaps you meant --form?' in r.stderr\n\n    def test_request_body_from_file_by_path_no_data_items_allowed(\n            self, httpbin):\n        env = MockEnvironment(stdin_isatty=False)\n        r = http(\n            'POST',\n            httpbin + '/post',\n            '@' + FILE_PATH_ARG, 'foo=bar',\n            env=env,\n            tolerate_error_exit_status=True,\n        )\n        assert r.exit_status == ExitStatus.ERROR\n        assert 'cannot be mixed' in r.stderr\n\n    def test_multiple_request_bodies_from_file_by_path(self, httpbin):\n        env = MockEnvironment(stdin_isatty=True)\n        r = http(\n            '--verbose',\n            'POST', httpbin + '/post',\n            '@' + FILE_PATH_ARG,\n            '@' + FILE_PATH_ARG,\n            env=env,\n            tolerate_error_exit_status=True,\n        )\n        assert r.exit_status == ExitStatus.ERROR\n        assert 'from multiple files' in r.stderr\n"
  },
  {
    "path": "tests/test_windows.py",
    "content": "import pytest\nfrom httpie.context import Environment\n\nfrom .utils import MockEnvironment, http\nfrom httpie.compat import is_windows\n\n\n@pytest.mark.skipif(not is_windows, reason='windows-only')\nclass TestWindowsOnly:\n\n    @pytest.mark.skipif(True,\n                        reason='this test for some reason kills the process')\n    def test_windows_colorized_output(self, httpbin):\n        # Spits out the colorized output.\n        http(httpbin + '/get', env=Environment())\n\n\nclass TestFakeWindows:\n    def test_output_file_pretty_not_allowed_on_windows(self, tmp_path, httpbin):\n        env = MockEnvironment(is_windows=True)\n        output_file = tmp_path / 'test_output_file_pretty_not_allowed_on_windows'\n        r = http('--output', str(output_file),\n                 '--pretty=all', 'GET', httpbin + '/get',\n                 env=env, tolerate_error_exit_status=True)\n        assert 'Only terminal output can be colorized on Windows' in r.stderr\n"
  },
  {
    "path": "tests/test_xml.py",
    "content": "import sys\n\nimport pytest\nimport responses\n\nfrom httpie.encoding import UTF8\nfrom httpie.output.formatters.xml import parse_xml, pretty_xml\n\nfrom .fixtures import XML_FILES_PATH, XML_FILES_VALID, XML_FILES_INVALID, XML_DATA_RAW, XML_DATA_FORMATTED\nfrom .utils import http, DUMMY_URL\n\n\n@pytest.mark.parametrize(\n    'options, expected_xml',\n    [\n        ('xml.format:false', XML_DATA_RAW),\n        ('xml.indent:2', XML_DATA_FORMATTED),\n        ('xml.indent:4', pretty_xml(parse_xml(XML_DATA_RAW), indent=4)),\n    ]\n)\n@responses.activate\ndef test_xml_format_options(options, expected_xml):\n    responses.add(\n        responses.GET,\n        DUMMY_URL,\n        body=XML_DATA_RAW,\n        content_type='application/xml',\n    )\n\n    r = http('--format-options', options, DUMMY_URL)\n    assert expected_xml in r\n\n\n@pytest.mark.parametrize('file', XML_FILES_VALID)\n@responses.activate\ndef test_valid_xml(file):\n    \"\"\"Test XML formatter limits with data containing comments, doctypes\n    and other XML-specific subtles.\n    \"\"\"\n    if 'standalone' in file.stem and sys.version_info < (3, 9):\n        pytest.skip('Standalone XML requires Python 3.9+')\n\n    xml_data = file.read_text(encoding=UTF8)\n    expected_xml_file = file.with_name(file.name.replace('_raw', '_formatted'))\n    expected_xml_output = expected_xml_file.read_text(encoding=UTF8)\n    responses.add(\n        responses.GET,\n        DUMMY_URL,\n        body=xml_data,\n        content_type='application/xml',\n    )\n\n    r = http(DUMMY_URL)\n    assert expected_xml_output in r\n\n\n@responses.activate\ndef test_xml_xhtml():\n    \"\"\"XHTML responses are handled by the XML formatter.\"\"\"\n    file = XML_FILES_PATH / 'xhtml' / 'xhtml_raw.xml'\n    xml_data = file.read_text(encoding=UTF8)\n\n    # Python < 3.8 was sorting attributes (https://bugs.python.org/issue34160)\n    # so we have 2 different output expected given the Python version.\n    expected_file_name = (\n        'xhtml_formatted_python_less_than_3.8.xml'\n        if sys.version_info < (3, 8)\n        else 'xhtml_formatted.xml'\n    )\n    expected_xml_file = file.with_name(expected_file_name)\n    expected_xml_output = expected_xml_file.read_text(encoding=UTF8)\n    responses.add(\n        responses.GET,\n        DUMMY_URL,\n        body=xml_data,\n        content_type='application/xhtml+xml',\n    )\n\n    r = http(DUMMY_URL)\n    assert expected_xml_output in r\n\n\n@pytest.mark.parametrize('file', XML_FILES_INVALID)\n@responses.activate\ndef test_invalid_xml(file):\n    \"\"\"Testing several problematic XML files, none should be formatted\n    and none should make HTTPie to crash.\n    \"\"\"\n    xml_data = file.read_text(encoding=UTF8)\n    responses.add(\n        responses.GET,\n        DUMMY_URL,\n        body=xml_data,\n        content_type='application/xml',\n    )\n\n    # No formatting done, data is simply printed as-is.\n    r = http(DUMMY_URL)\n    assert xml_data in r\n"
  },
  {
    "path": "tests/utils/__init__.py",
    "content": "\"\"\"Utilities for HTTPie test suite.\"\"\"\nimport re\nimport shlex\nimport os\nimport sys\nimport time\nimport json\nimport tempfile\nimport warnings\nimport pytest\nfrom contextlib import suppress\nfrom io import BytesIO\nfrom pathlib import Path\nfrom typing import Any, Optional, Union, List, Iterable\n\nimport httpie.core as core\nimport httpie.manager.__main__ as manager\n\nfrom httpie.status import ExitStatus\nfrom httpie.config import Config\nfrom httpie.encoding import UTF8\nfrom httpie.context import Environment\nfrom httpie.utils import url_as_host\n\n\nREMOTE_HTTPBIN_DOMAIN = 'pie.dev'\n\n# pytest-httpbin currently does not support chunked requests:\n# <https://github.com/kevin1024/pytest-httpbin/issues/33>\n# <https://github.com/kevin1024/pytest-httpbin/issues/28>\nHTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN = 'pie.dev'\nHTTPBIN_WITH_CHUNKED_SUPPORT = 'http://' + HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN\n\nIS_PYOPENSSL = os.getenv('HTTPIE_TEST_WITH_PYOPENSSL', '0') == '1'\n\nTESTS_ROOT = Path(__file__).parent.parent\nCRLF = '\\r\\n'\nCOLOR = '\\x1b['\nCOLOR_RE = re.compile(r'\\x1b\\[\\d+(;\\d+)*?m', re.MULTILINE)\n\nHTTP_OK = '200 OK'\n# noinspection GrazieInspection\nHTTP_OK_COLOR = (\n    'HTTP\\x1b[39m\\x1b[38;5;245m/\\x1b[39m\\x1b'\n    '[38;5;37m1.1\\x1b[39m\\x1b[38;5;245m \\x1b[39m\\x1b[38;5;37m200'\n    '\\x1b[39m\\x1b[38;5;245m \\x1b[39m\\x1b[38;5;136mOK'\n)\n\nDUMMY_URL = 'http://this-should.never-resolve'  # Note: URL never fetched\nDUMMY_HOST = url_as_host(DUMMY_URL)\n\n# We don't want hundreds of subprocesses trying to access GitHub API\n# during the tests.\nConfig.DEFAULTS['disable_update_warnings'] = True\n\n\ndef strip_colors(colorized_msg: str) -> str:\n    return COLOR_RE.sub('', colorized_msg)\n\n\ndef mk_config_dir() -> Path:\n    dirname = tempfile.mkdtemp(prefix='httpie_config_')\n    return Path(dirname)\n\n\ndef add_auth(url, auth):\n    proto, rest = url.split('://', 1)\n    return f'{proto}://{auth}@{rest}'\n\n\nclass Encoder:\n    \"\"\"\n    Encode binary fragments into a text stream. This is used\n    to embed raw binary data (which can't be decoded) into the\n    fake standard output we use on MockEnvironment.\n\n    Each data fragment is embedded by it's hash:\n        \"Some data hash(XXX) more data.\"\n\n    Which then later converted back to a bytes object:\n        b\"Some data <real data> more data.\"\n    \"\"\"\n\n    TEMPLATE = 'hash({})'\n\n    STR_PATTERN = re.compile(r'hash\\((.*)\\)')\n    BYTES_PATTERN = re.compile(rb'hash\\((.*)\\)')\n\n    def __init__(self):\n        self.substitutions = {}\n\n    def substitute(self, data: bytes) -> str:\n        idx = hash(data)\n        self.substitutions[idx] = data\n        return self.TEMPLATE.format(idx)\n\n    def decode(self, data: str) -> Union[str, bytes]:\n        if self.STR_PATTERN.search(data) is None:\n            return data\n\n        raw_data = data.encode()\n        return self.BYTES_PATTERN.sub(\n            lambda match: self.substitutions[int(match.group(1))],\n            raw_data\n        )\n\n\nclass FakeBytesIOBuffer(BytesIO):\n\n    def __init__(self, original, encoder, *args, **kwargs):\n        self.original_buffer = original\n        self.encoder = encoder\n        super().__init__(*args, **kwargs)\n\n    def write(self, data):\n        try:\n            self.original_buffer.write(data.decode(UTF8))\n        except UnicodeDecodeError:\n            self.original_buffer.write(self.encoder.substitute(data))\n        finally:\n            self.original_buffer.flush()\n\n\nclass StdinBytesIO(BytesIO):\n    \"\"\"To be used for `MockEnvironment.stdin`\"\"\"\n    len = 0  # See `prepare_request_body()`\n\n\nclass MockEnvironment(Environment):\n    \"\"\"Environment subclass with reasonable defaults for testing.\"\"\"\n    colors = 0  # For easier debugging\n    stdin_isatty = True\n    stdout_isatty = True\n    is_windows = False\n    show_displays = False\n\n    def __init__(self, create_temp_config_dir=True, **kwargs):\n        self._encoder = Encoder()\n        if 'stdout' not in kwargs:\n            kwargs['stdout'] = tempfile.NamedTemporaryFile(\n                mode='w+t',\n                prefix='httpie_stderr',\n                newline='',\n                encoding=UTF8,\n            )\n            kwargs['stdout'].buffer = FakeBytesIOBuffer(kwargs['stdout'], self._encoder)\n        if 'stderr' not in kwargs:\n            kwargs['stderr'] = tempfile.TemporaryFile(\n                mode='w+t',\n                prefix='httpie_stderr',\n                encoding=UTF8,\n            )\n        super().__init__(**kwargs)\n        self._create_temp_config_dir = create_temp_config_dir\n        self._delete_config_dir = False\n        self._temp_dir = Path(tempfile.gettempdir())\n\n    @property\n    def config(self) -> Config:\n        if (self._create_temp_config_dir\n                and self._temp_dir not in self.config_dir.parents):\n            self.create_temp_config_dir()\n        return super().config\n\n    def create_temp_config_dir(self):\n        self.config_dir = mk_config_dir()\n        self._delete_config_dir = True\n\n    def cleanup(self):\n        self.devnull.close()\n        self.stdout.close()\n        self.stderr.close()\n        warnings.resetwarnings()\n        if self._delete_config_dir:\n            assert self._temp_dir in self.config_dir.parents\n            from shutil import rmtree\n            rmtree(self.config_dir, ignore_errors=True)\n\n    def __del__(self):\n        # noinspection PyBroadException\n        try:\n            self.cleanup()\n        except Exception:\n            pass\n\n\nclass PersistentMockEnvironment(MockEnvironment):\n    def cleanup(self):\n        pass\n\n\nclass BaseCLIResponse:\n    \"\"\"\n    Represents the result of simulated `$ http' invocation via `http()`.\n\n    Holds and provides access to:\n\n        - stdout output: print(self)\n        - stderr output: print(self.stderr)\n        - devnull output: print(self.devnull)\n        - exit_status output: print(self.exit_status)\n\n    \"\"\"\n    stderr: str = None\n    devnull: str = None\n    json: dict = None\n    exit_status: ExitStatus = None\n    command: str = None\n    args: List[str] = []\n    complete_args: List[str] = []\n\n    @property\n    def command(self):  # noqa: F811\n        cmd = ' '.join(shlex.quote(arg) for arg in ['http', *self.args])\n        # pytest-httpbin to real httpbin.\n        return re.sub(r'127\\.0\\.0\\.1:\\d+', 'httpbin.org', cmd)\n\n    @classmethod\n    def from_raw_data(self, data: Union[str, bytes]) -> 'BaseCLIResponse':\n        if isinstance(data, bytes):\n            with suppress(UnicodeDecodeError):\n                data = data.decode()\n\n        if isinstance(data, bytes):\n            return BytesCLIResponse(data)\n        else:\n            return StrCLIResponse(data)\n\n\nclass BytesCLIResponse(bytes, BaseCLIResponse):\n    \"\"\"\n    Used as a fallback when a StrCLIResponse cannot be used.\n\n    E.g. when the output contains binary data or when it is colorized.\n\n    `.json` will always be None.\n\n    \"\"\"\n\n\nclass StrCLIResponse(str, BaseCLIResponse):\n\n    @property\n    def json(self) -> Optional[dict]:\n        \"\"\"\n        Return deserialized the request or response JSON body,\n        if one (and only one) included in the output and is parsable.\n\n        \"\"\"\n        if not hasattr(self, '_json'):\n            self._json = None\n            # De-serialize JSON body if possible.\n            if COLOR in self:\n                # Colorized output cannot be parsed.\n                pass\n            elif self.strip().startswith('{'):\n                # Looks like JSON body.\n                self._json = json.loads(self)\n            elif self.count('Content-Type:') == 1:\n                # Looks like a HTTP message,\n                # try to extract JSON from its body.\n                try:\n                    j = self.strip()[self.strip().rindex('\\r\\n\\r\\n'):]\n                except ValueError:\n                    pass\n                else:\n                    try:\n                        # noinspection PyAttributeOutsideInit\n                        self._json = json.loads(j)\n                    except ValueError:\n                        pass\n        return self._json\n\n\nclass ExitStatusError(Exception):\n    pass\n\n\n@pytest.fixture\ndef mock_env() -> MockEnvironment:\n    env = MockEnvironment()\n    yield env\n    env.cleanup()\n\n\ndef normalize_args(args: Iterable[Any]) -> List[str]:\n    return [str(arg) for arg in args]\n\n\ndef httpie(\n    *args,\n    **kwargs\n) -> StrCLIResponse:\n    \"\"\"\n    Run HTTPie manager command with the given\n    args/kwargs, and capture stderr/out and exit\n    status.\n    \"\"\"\n\n    env = kwargs.setdefault('env', MockEnvironment())\n    cli_args = ['httpie']\n    if not kwargs.pop('no_debug', False):\n        cli_args.append('--debug')\n    cli_args += normalize_args(args)\n    exit_status = manager.main(\n        args=cli_args,\n        **kwargs\n    )\n\n    env.stdout.seek(0)\n    env.stderr.seek(0)\n    try:\n        response = BaseCLIResponse.from_raw_data(env.stdout.read())\n        response.stderr = env.stderr.read()\n        response.exit_status = exit_status\n        response.args = cli_args\n    finally:\n        env.stdout.truncate(0)\n        env.stderr.truncate(0)\n        env.stdout.seek(0)\n        env.stderr.seek(0)\n\n    return response\n\n\ndef http(\n    *args,\n    program_name='http',\n    tolerate_error_exit_status=False,\n    **kwargs,\n) -> Union[StrCLIResponse, BytesCLIResponse]:\n    # noinspection PyUnresolvedReferences\n    \"\"\"\n    Run HTTPie and capture stderr/out and exit status.\n    Content written to devnull will be captured only if\n    env.devnull is set manually.\n\n    Invoke `httpie.core.main()` with `args` and `kwargs`,\n    and return a `CLIResponse` subclass instance.\n\n    The return value is either a `StrCLIResponse`, or `BytesCLIResponse`\n    if unable to decode the output. Devnull is string when possible,\n    bytes otherwise.\n\n    The response has the following attributes:\n\n        `stdout` is represented by the instance itself (print r)\n        `stderr`: text written to stderr\n        `devnull` text written to devnull.\n        `exit_status`: the exit status\n        `json`: decoded JSON (if possible) or `None`\n\n    Exceptions are propagated.\n\n    If you pass ``tolerate_error_exit_status=True``, then error exit statuses\n    won't result into an exception.\n\n    Example:\n\n    $ http --auth=user:password GET pie.dev/basic-auth/user/password\n\n        >>> httpbin = getfixture('httpbin')\n        >>> r = http('-a', 'user:pw', httpbin + '/basic-auth/user/pw')\n        >>> type(r) == StrCLIResponse\n        True\n        >>> r.exit_status is ExitStatus.SUCCESS\n        True\n        >>> r.stderr\n        ''\n        >>> 'HTTP/1.1 200 OK' in r\n        True\n        >>> r.json == {'authenticated': True, 'user': 'user'}\n        True\n\n    \"\"\"\n    env = kwargs.get('env')\n    if not env:\n        env = kwargs['env'] = MockEnvironment()\n\n    stdout = env.stdout\n    stderr = env.stderr\n    devnull = env.devnull\n\n    args = list(args)\n    args_with_config_defaults = args + env.config.default_options\n    add_to_args = []\n    if '--debug' not in args_with_config_defaults:\n        if (not tolerate_error_exit_status\n                and '--traceback' not in args_with_config_defaults):\n            add_to_args.append('--traceback')\n        if not any('--timeout' in arg for arg in args_with_config_defaults):\n            add_to_args.append('--timeout=3')\n\n    complete_args = [program_name, *add_to_args, *args]\n    # print(' '.join(complete_args))\n\n    def dump_stderr():\n        stderr.seek(0)\n        sys.stderr.write(stderr.read())\n\n    try:\n        try:\n            exit_status = core.main(args=complete_args, **kwargs)\n            if '--download' in args:\n                # Let the progress reporter thread finish.\n                time.sleep(.5)\n        except SystemExit:\n            if tolerate_error_exit_status:\n                exit_status = ExitStatus.ERROR\n            else:\n                dump_stderr()\n                raise\n        except Exception:\n            stderr.seek(0)\n            sys.stderr.write(stderr.read())\n            raise\n        else:\n            if (not tolerate_error_exit_status\n                    and exit_status != ExitStatus.SUCCESS):\n                dump_stderr()\n                raise ExitStatusError(\n                    'httpie.core.main() unexpectedly returned'\n                    f' a non-zero exit status: {exit_status}'\n                )\n\n        stdout.seek(0)\n        stderr.seek(0)\n        devnull.seek(0)\n        output = stdout.read()\n        devnull_output = devnull.read()\n\n        if hasattr(env, '_encoder'):\n            output = env._encoder.decode(output)\n\n        r = BaseCLIResponse.from_raw_data(output)\n\n        try:\n            devnull_output = devnull_output.decode()\n        except Exception:\n            pass\n\n        r.devnull = devnull_output\n        r.stderr = stderr.read()\n        r.exit_status = exit_status\n        r.args = args\n        r.complete_args = ' '.join(complete_args)\n\n        if r.exit_status != ExitStatus.SUCCESS:\n            sys.stderr.write(r.stderr)\n\n        # print(f'\\n\\n$ {r.command}\\n')\n        return r\n\n    finally:\n        env.cleanup()\n"
  },
  {
    "path": "tests/utils/http_server.py",
    "content": "import threading\nimport json\n\nfrom collections import defaultdict\nfrom contextlib import contextmanager\nfrom http import HTTPStatus\nfrom http.cookies import SimpleCookie\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\nfrom urllib.parse import urlparse, parse_qs\n\nimport pytest\n\n\nclass TestHandler(BaseHTTPRequestHandler):\n    handlers = defaultdict(dict)\n\n    @classmethod\n    def handler(cls, method, path):\n        def inner(func):\n            cls.handlers[method][path] = func\n            return func\n        return inner\n\n    def do_generic(self):\n        parse_result = urlparse(self.path)\n        func = self.handlers[self.command].get(parse_result.path)\n        if func is None:\n            return self.send_error(HTTPStatus.NOT_FOUND)\n\n        return func(self)\n\n    do_GET = do_generic\n    do_POST = do_generic\n\n\n@TestHandler.handler('GET', '/headers')\ndef get_headers(handler):\n    handler.send_response(200)\n    for key, value in handler.headers.items():\n        handler.send_header(key, value)\n    handler.send_header('Content-Length', 0)\n    handler.end_headers()\n\n\n@TestHandler.handler('GET', '/drip')\ndef chunked_drip(handler):\n    handler.send_response(200)\n    accept = handler.headers.get('Accept')\n    if accept is not None:\n        handler.send_header('Content-Type', accept)\n    handler.send_header('Transfer-Encoding', 'chunked')\n    handler.end_headers()\n\n    for _ in range(3):\n        body = 'test\\n'\n        handler.wfile.write(f'{len(body):X}\\r\\n{body}\\r\\n'.encode('utf-8'))\n\n    handler.wfile.write('0\\r\\n\\r\\n'.encode('utf-8'))\n\n\n@TestHandler.handler('GET', '/stream/encoding/random')\ndef random_encoding(handler):\n    from tests.fixtures import ASCII_FILE_CONTENT, FILE_CONTENT as UNICODE_FILE_CONTENT\n\n    handler.send_response(200)\n    handler.send_header('Transfer-Encoding', 'chunked')\n    handler.end_headers()\n\n    for body in [\n        ASCII_FILE_CONTENT,\n        ASCII_FILE_CONTENT,\n        UNICODE_FILE_CONTENT,\n        UNICODE_FILE_CONTENT,\n        UNICODE_FILE_CONTENT,\n    ]:\n        body += \"\\n\"\n        handler.wfile.write(f'{len(body.encode()):X}\\r\\n{body}\\r\\n'.encode())\n\n    handler.wfile.write('0\\r\\n\\r\\n'.encode('utf-8'))\n\n\n@TestHandler.handler('POST', '/status/msg')\ndef status_custom_msg(handler):\n    content_len = int(handler.headers.get('content-length', 0))\n    post_body = handler.rfile.read(content_len).decode()\n\n    handler.send_response(200, post_body)\n    handler.end_headers()\n\n\n@TestHandler.handler('GET', '/cookies')\ndef get_cookies(handler):\n    cookies = {\n        'cookies': {\n            key: cookie.value\n            for key, cookie in SimpleCookie(handler.headers.get('Cookie')).items()\n        }\n    }\n    payload = json.dumps(cookies)\n\n    handler.send_response(200)\n    handler.send_header('Content-Length', len(payload))\n    handler.send_header('Content-Type', 'application/json')\n    handler.end_headers()\n    handler.wfile.write(payload.encode('utf-8'))\n\n\n@TestHandler.handler('GET', '/cookies/set')\ndef set_cookies(handler):\n    options = parse_qs(urlparse(handler.path).query)\n\n    handler.send_response(200)\n    for cookie, [value] in options.items():\n        handler.send_header('Set-Cookie', f'{cookie}={value}')\n\n    handler.end_headers()\n\n\n@TestHandler.handler('GET', '/cookies/set-and-redirect')\ndef set_cookie_and_redirect(handler):\n    handler.send_response(302)\n\n    redirect_to = handler.headers.get('X-Redirect-To', '/headers')\n    handler.send_header('Location', redirect_to)\n\n    raw_cookies = handler.headers.get('X-Cookies', 'a=b')\n    for cookie in raw_cookies.split(', '):\n        handler.send_header('Set-Cookie', cookie)\n    handler.end_headers()\n\n\n@contextmanager\ndef _http_server():\n    server = HTTPServer(('localhost', 0), TestHandler)\n    thread = threading.Thread(target=server.serve_forever)\n    thread.start()\n    yield server\n    server.shutdown()\n    thread.join()\n\n\n@pytest.fixture(scope=\"function\")\ndef http_server():\n    \"\"\"A custom HTTP server implementation for our tests, that is\n    built on top of the http.server module. Handy when we need to\n    deal with details which httpbin can not capture.\"\"\"\n\n    with _http_server() as server:\n        yield '{0}:{1}'.format(*server.socket.getsockname())\n\n\n@pytest.fixture(scope=\"function\")\ndef localhost_http_server():\n    \"\"\"Just like the http_server, but uses the static\n    `localhost` name for the host.\"\"\"\n\n    with _http_server() as server:\n        yield 'localhost:{1}'.format(*server.socket.getsockname())\n"
  },
  {
    "path": "tests/utils/matching/__init__.py",
    "content": "\"\"\"\nUtilities for testing output composition.\n\n\"\"\"\nfrom typing import Iterable\n\nimport pytest\n\nfrom .parsing import OutputMatchingError, expect_tokens\nfrom .tokens import Expect, ExpectSequence\n\n\n__all__ = [\n    'assert_output_matches',\n    'assert_output_does_not_match',\n    'Expect',\n    'ExpectSequence',\n]\n\n\ndef assert_output_matches(output: str, tokens: Iterable[Expect]):\n    r\"\"\"\n    Check the command `output` for an exact full sequence of `tokens`.\n\n    >>> out = 'GET / HTTP/1.1\\r\\nAAA:BBB\\r\\n\\r\\nCCC\\n\\n'\n    >>> assert_output_matches(out, [Expect.REQUEST_HEADERS, Expect.BODY, Expect.SEPARATOR])\n\n    \"\"\"\n    # TODO: auto-remove ansi colors to allow for testing of colorized output as well.\n    expect_tokens(tokens=tokens, s=output)\n\n\ndef assert_output_does_not_match(output: str, tokens: Iterable[Expect]):\n    r\"\"\"\n    >>> assert_output_does_not_match('\\r\\n', [Expect.BODY])\n    \"\"\"\n    with pytest.raises(OutputMatchingError):\n        assert_output_matches(output=output, tokens=tokens)\n"
  },
  {
    "path": "tests/utils/matching/parsing.py",
    "content": "import re\nfrom typing import Iterable\n\nfrom httpie.output.writer import MESSAGE_SEPARATOR\nfrom .tokens import Expect\nfrom ...utils import CRLF\n\n\nSEPARATOR_RE = re.compile(f'^{MESSAGE_SEPARATOR}')\nKEY_VALUE_RE = re.compile(r'[\\n]*((.*?):(.+)[\\n]?)+[\\n]*')\n\n\ndef make_headers_re(message_type: Expect):\n    assert message_type in {Expect.REQUEST_HEADERS, Expect.RESPONSE_HEADERS}\n\n    # language=RegExp\n    crlf = r'[\\r][\\n]'\n    non_crlf = rf'[^{CRLF}]'\n\n    # language=RegExp\n    http_version = r'HTTP/\\d+\\.\\d+'\n    if message_type is Expect.REQUEST_HEADERS:\n        # POST /post HTTP/1.1\n        start_line_re = fr'{non_crlf}*{http_version}{crlf}'\n    else:\n        # HTTP/1.1 200 OK\n        start_line_re = fr'{http_version}{non_crlf}*{crlf}'\n\n    return re.compile(\n        fr'''\n            ^\n            {start_line_re}\n            ({non_crlf}+:{non_crlf}+{crlf})+\n            {crlf}\n        ''',\n        flags=re.VERBOSE\n    )\n\n\nBODY_ENDINGS = [\n    MESSAGE_SEPARATOR,\n    CRLF,  # Not really but useful for testing (just remember not to include it in a body).\n]\nTOKEN_REGEX_MAP = {\n    Expect.REQUEST_HEADERS: make_headers_re(Expect.REQUEST_HEADERS),\n    Expect.RESPONSE_HEADERS: make_headers_re(Expect.RESPONSE_HEADERS),\n    Expect.RESPONSE_META: KEY_VALUE_RE,\n    Expect.SEPARATOR: SEPARATOR_RE,\n}\n\n\nclass OutputMatchingError(ValueError):\n    pass\n\n\ndef expect_tokens(tokens: Iterable[Expect], s: str):\n    for token in tokens:\n        s = expect_token(token, s)\n    if s:\n        raise OutputMatchingError(f'Unmatched remaining output for {tokens} in {s!r}')\n\n\ndef expect_token(token: Expect, s: str) -> str:\n    if token is Expect.BODY:\n        s = expect_body(s)\n    else:\n        s = expect_regex(token, s)\n    return s\n\n\ndef expect_regex(token: Expect, s: str) -> str:\n    match = TOKEN_REGEX_MAP[token].match(s)\n    if not match:\n        raise OutputMatchingError(f'No match for {token} in {s!r}')\n    return s[match.end():]\n\n\ndef expect_body(s: str) -> str:\n    \"\"\"\n    We require some text, and continue to read until we find an ending or until the end of the string.\n\n    \"\"\"\n    if 'content-disposition:' in s.lower():\n        # Multipart body heuristic.\n        final_boundary_re = re.compile('\\r\\n--[^-]+?--\\r\\n')\n        match = final_boundary_re.search(s)\n        if match:\n            return s[match.end():]\n\n    endings = [s.index(sep) for sep in BODY_ENDINGS if sep in s]\n    if not endings:\n        s = ''  # Only body\n    else:\n        end = min(endings)\n        if end == 0:\n            raise OutputMatchingError(f'Empty body: {s!r}')\n        s = s[end:]\n    return s\n"
  },
  {
    "path": "tests/utils/matching/test_matching.py",
    "content": "\"\"\"\nHere we test our output parsing and matching implementation, not HTTPie itself.\n\n\"\"\"\nfrom httpie.models import ELAPSED_TIME_LABEL\nfrom httpie.output.writer import MESSAGE_SEPARATOR\nfrom ...utils import CRLF\nfrom . import assert_output_does_not_match, assert_output_matches, Expect\n\n\ndef test_assert_output_matches_headers_incomplete():\n    assert_output_does_not_match(f'HTTP/1.1{CRLF}', [Expect.RESPONSE_HEADERS])\n\n\ndef test_assert_output_matches_headers_unterminated():\n    assert_output_does_not_match(\n        (\n            f'HTTP/1.1{CRLF}'\n            'AAA:BBB'\n            f'{CRLF}'\n        ),\n        [Expect.RESPONSE_HEADERS],\n    )\n\n\ndef test_assert_output_matches_response_headers():\n    assert_output_matches(\n        (\n            f'HTTP/1.1 200 OK{CRLF}'\n            f'AAA:BBB{CRLF}'\n            f'{CRLF}'\n        ),\n        [Expect.RESPONSE_HEADERS],\n    )\n\n\ndef test_assert_output_matches_request_headers():\n    assert_output_matches(\n        (\n            f'GET / HTTP/1.1{CRLF}'\n            f'AAA:BBB{CRLF}'\n            f'{CRLF}'\n        ),\n        [Expect.REQUEST_HEADERS],\n    )\n\n\ndef test_assert_output_matches_headers_and_separator():\n    assert_output_matches(\n        (\n            f'HTTP/1.1{CRLF}'\n            f'AAA:BBB{CRLF}'\n            f'{CRLF}'\n            f'{MESSAGE_SEPARATOR}'\n        ),\n        [Expect.RESPONSE_HEADERS, Expect.SEPARATOR],\n    )\n\n\ndef test_assert_output_matches_body_unmatched_crlf():\n    assert_output_does_not_match(f'AAA{CRLF}', [Expect.BODY])\n\n\ndef test_assert_output_matches_body_unmatched_separator():\n    assert_output_does_not_match(f'AAA{MESSAGE_SEPARATOR}', [Expect.BODY])\n\n\ndef test_assert_output_matches_body_and_separator():\n    assert_output_matches(f'AAA{MESSAGE_SEPARATOR}', [Expect.BODY, Expect.SEPARATOR])\n\n\ndef test_assert_output_matches_body_r():\n    assert_output_matches('AAA\\r', [Expect.BODY])\n\n\ndef test_assert_output_matches_body_n():\n    assert_output_matches('AAA\\n', [Expect.BODY])\n\n\ndef test_assert_output_matches_body_r_body():\n    assert_output_matches('AAA\\rBBB', [Expect.BODY])\n\n\ndef test_assert_output_matches_body_n_body():\n    assert_output_matches('AAA\\nBBB', [Expect.BODY])\n\n\ndef test_assert_output_matches_headers_and_body():\n    assert_output_matches(\n        (\n            f'HTTP/1.1{CRLF}'\n            f'AAA:BBB{CRLF}'\n            f'{CRLF}'\n            'CCC'\n        ),\n        [Expect.RESPONSE_HEADERS, Expect.BODY]\n    )\n\n\ndef test_assert_output_matches_headers_with_body_and_separator():\n    assert_output_matches(\n        (\n            f'HTTP/1.1 {CRLF}'\n            f'AAA:BBB{CRLF}{CRLF}'\n            f'CCC{MESSAGE_SEPARATOR}'\n        ),\n        [Expect.RESPONSE_HEADERS, Expect.BODY, Expect.SEPARATOR]\n    )\n\n\ndef test_assert_output_matches_response_meta():\n    assert_output_matches(\n        (\n            'Key: Value\\n'\n            f'{ELAPSED_TIME_LABEL}: 3.3s'\n        ),\n        [Expect.RESPONSE_META]\n    )\n\n\ndef test_assert_output_matches_whole_response():\n    assert_output_matches(\n        (\n            f'HTTP/1.1{CRLF}'\n            f'AAA:BBB{CRLF}'\n            f'{CRLF}'\n            f'CCC{MESSAGE_SEPARATOR}'\n            f'{ELAPSED_TIME_LABEL}: 3.3s'\n        ),\n        [Expect.RESPONSE_HEADERS, Expect.BODY, Expect.RESPONSE_META]\n    )\n\n\ndef test_assert_output_matches_multiple_messages():\n    assert_output_matches(\n        (\n            f'POST / HTTP/1.1{CRLF}'\n            f'AAA:BBB{CRLF}'\n            f'{CRLF}'\n\n            f'CCC'\n            f'{MESSAGE_SEPARATOR}'\n\n            f'HTTP/1.1 200 OK{CRLF}'\n            f'EEE:FFF{CRLF}'\n            f'{CRLF}'\n\n            'GGG'\n            f'{MESSAGE_SEPARATOR}'\n        ), [\n            Expect.REQUEST_HEADERS,\n            Expect.BODY,\n            Expect.SEPARATOR,\n            Expect.RESPONSE_HEADERS,\n            Expect.BODY,\n            Expect.SEPARATOR,\n        ]\n    )\n\n\ndef test_assert_output_matches_multipart_body():\n    output = (\n        'POST / HTTP/1.1\\r\\n'\n        'User-Agent: HTTPie/2.4.0-dev\\r\\n'\n        'Accept-Encoding: gzip, deflate\\r\\n'\n        'Accept: */*\\r\\n'\n        'Connection: keep-alive\\r\\n'\n        'Content-Type: multipart/form-data; boundary=1e22169de43e4a2e8d9e41c0a1c93cc5\\r\\n'\n        'Content-Length: 212\\r\\n'\n        'Host: pie.dev\\r\\n'\n        '\\r\\n'\n        '--1e22169de43e4a2e8d9e41c0a1c93cc5\\r\\n'\n        'Content-Disposition: form-data; name=\"AAA\"\\r\\n'\n        '\\r\\n'\n        'BBB\\r\\n'\n        '--1e22169de43e4a2e8d9e41c0a1c93cc5\\r\\n'\n        'Content-Disposition: form-data; name=\"CCC\"\\r\\n'\n        '\\r\\n'\n        'DDD\\r\\n'\n        '--1e22169de43e4a2e8d9e41c0a1c93cc5--\\r\\n'\n    )\n    assert_output_matches(output, [Expect.REQUEST_HEADERS, Expect.BODY])\n\n\ndef test_assert_output_matches_multipart_body_with_separator():\n    output = (\n        'POST / HTTP/1.1\\r\\n'\n        'User-Agent: HTTPie/2.4.0-dev\\r\\n'\n        'Accept-Encoding: gzip, deflate\\r\\n'\n        'Accept: */*\\r\\n'\n        'Connection: keep-alive\\r\\n'\n        'Content-Type: multipart/form-data; boundary=1e22169de43e4a2e8d9e41c0a1c93cc5\\r\\n'\n        'Content-Length: 212\\r\\n'\n        'Host: pie.dev\\r\\n'\n        '\\r\\n'\n        '--1e22169de43e4a2e8d9e41c0a1c93cc5\\r\\n'\n        'Content-Disposition: form-data; name=\"AAA\"\\r\\n'\n        '\\r\\n'\n        'BBB\\r\\n'\n        '--1e22169de43e4a2e8d9e41c0a1c93cc5\\r\\n'\n        'Content-Disposition: form-data; name=\"CCC\"\\r\\n'\n        '\\r\\n'\n        'DDD\\r\\n'\n        '--1e22169de43e4a2e8d9e41c0a1c93cc5--\\r\\n'\n        f'{MESSAGE_SEPARATOR}'\n    )\n    assert_output_matches(output, [Expect.REQUEST_HEADERS, Expect.BODY, Expect.SEPARATOR])\n\n\ndef test_assert_output_matches_multiple_separators():\n    assert_output_matches(\n        MESSAGE_SEPARATOR + MESSAGE_SEPARATOR + 'AAA' + MESSAGE_SEPARATOR + MESSAGE_SEPARATOR,\n        [Expect.SEPARATOR, Expect.SEPARATOR, Expect.BODY, Expect.SEPARATOR, Expect.SEPARATOR]\n    )\n"
  },
  {
    "path": "tests/utils/matching/tokens.py",
    "content": "from enum import Enum, auto\n\n\nclass Expect(Enum):\n    \"\"\"\n    Predefined token types we can expect in the output.\n\n    \"\"\"\n    REQUEST_HEADERS = auto()\n    RESPONSE_HEADERS = auto()\n    RESPONSE_META = auto()\n    BODY = auto()\n    SEPARATOR = auto()\n\n\nclass ExpectSequence:\n    \"\"\"\n    Standard combined chunks. These predefined requests and responses assume a body.\n\n    \"\"\"\n    RAW_REQUEST = [\n        Expect.REQUEST_HEADERS,\n        Expect.BODY,\n    ]\n    RAW_RESPONSE = [\n        Expect.RESPONSE_HEADERS,\n        Expect.BODY,\n    ]\n    RAW_EXCHANGE = [\n        *RAW_REQUEST,\n        Expect.SEPARATOR,  # Good choice?\n        *RAW_RESPONSE,\n    ]\n    RAW_BODY = [\n        Expect.BODY,\n    ]\n    TERMINAL_REQUEST = [\n        *RAW_REQUEST,\n        Expect.SEPARATOR,\n    ]\n    TERMINAL_RESPONSE = [\n        *RAW_RESPONSE,\n        Expect.SEPARATOR,\n    ]\n    TERMINAL_EXCHANGE = [\n        *TERMINAL_REQUEST,\n        *TERMINAL_RESPONSE,\n    ]\n    TERMINAL_EXCHANGE_META = [\n        *TERMINAL_EXCHANGE,\n        Expect.RESPONSE_META\n    ]\n    TERMINAL_BODY = [\n        RAW_BODY,\n        Expect.SEPARATOR\n    ]\n"
  },
  {
    "path": "tests/utils/plugins_cli.py",
    "content": "import secrets\nimport site\nimport sys\nimport textwrap\n\nimport pytest\n\nfrom collections import defaultdict\nfrom dataclasses import dataclass, field, asdict\nfrom pathlib import Path\nfrom typing import Any, List, Dict, Tuple\nfrom unittest.mock import patch\n\nfrom httpie.context import Environment\nfrom httpie.compat import importlib_metadata\nfrom httpie.status import ExitStatus\nfrom httpie.plugins.manager import (\n    enable_plugins,\n    ENTRY_POINT_CLASSES as CLASSES,\n)\n\n\ndef make_name() -> str:\n    return 'httpie-' + secrets.token_hex(4)\n\n\n@dataclass\nclass EntryPoint:\n    name: str\n    group: str\n\n    def dump(self) -> Dict[str, str]:\n        return asdict(self)\n\n\n@dataclass\nclass Plugin:\n    interface: 'Interface'\n\n    name: str = field(default_factory=make_name)\n    version: str = '1.0.0'\n    entry_points: List[EntryPoint] = field(default_factory=list)\n\n    def build(self) -> None:\n        '''\n        Create an installable dummy plugin at the given path.\n\n        It will create a setup.py with the specified entry points,\n        as well as dummy classes in a python module to imitate\n        real plugins.\n        '''\n\n        groups = defaultdict(list)\n        for entry_point in self.entry_points:\n            groups[entry_point.group].append(entry_point.name)\n\n        setup_eps = {\n            group: [\n                f'{name} = {self.import_name}:{name.title()}'\n                for name in names\n            ]\n            for group, names in groups.items()\n        }\n\n        self.path.mkdir(parents=True, exist_ok=True)\n        with open(self.path / 'setup.py', 'w') as stream:\n            stream.write(textwrap.dedent(f'''\n            from setuptools import setup\n\n            setup(\n                name='{self.name}',\n                version='{self.version}',\n                py_modules=['{self.import_name}'],\n                entry_points={setup_eps!r},\n                install_requires=['httpie']\n            )\n            '''))\n\n        with open(self.path / (self.import_name + '.py'), 'w') as stream:\n            stream.write('from httpie.plugins import *\\n')\n            stream.writelines(\n                f'class {name.title()}({CLASSES[group].__name__}): ...\\n'\n                for group, names in groups.items()\n                for name in names\n            )\n\n    def dump(self) -> Dict[str, Any]:\n        return {\n            'version': self.version,\n            'entry_points': [\n                entry_point.dump()\n                for entry_point in self.entry_points\n            ]\n        }\n\n    @property\n    def path(self) -> Path:\n        return self.interface.path / self.name\n\n    @property\n    def import_name(self) -> str:\n        return self.name.replace('-', '_')\n\n\n@dataclass\nclass Interface:\n    path: Path\n    environment: Environment\n\n    def get_plugin(self, target: str) -> importlib_metadata.Distribution:\n        with enable_plugins(self.environment.config.plugins_dir):\n            return importlib_metadata.distribution(target)\n\n    def is_installed(self, target: str) -> bool:\n        try:\n            self.get_plugin(target)\n        except ModuleNotFoundError:\n            return False\n        else:\n            return True\n\n    def make_dummy_plugin(self, build=True, **kwargs) -> Plugin:\n        kwargs.setdefault('entry_points', [EntryPoint('test', 'httpie.plugins.auth.v1')])\n\n        plugin = Plugin(self, **kwargs)\n        if build:\n            plugin.build()\n        return plugin\n\n\ndef parse_listing(lines: List[str]) -> Dict[str, Any]:\n    plugins = {}\n    current_plugin = None\n\n    def parse_entry_point(line: str) -> Tuple[str, str]:\n        entry_point, raw_group = line.strip().split()\n        return entry_point, raw_group[1:-1]\n\n    def parse_plugin(line: str) -> Tuple[str, str]:\n        plugin, raw_version = line.strip().split()\n        return plugin, raw_version[1:-1]\n\n    for line in lines:\n        if not line.strip():\n            continue\n\n        if line[0].isspace():\n            # <indent> $entry_point ($group)\n            assert current_plugin is not None\n            entry_point, group = parse_entry_point(line)\n            plugins[current_plugin]['entry_points'].append({\n                'name': entry_point,\n                'group': group\n            })\n        else:\n            # $plugin ($version)\n            current_plugin, version = parse_plugin(line)\n            plugins[current_plugin] = {\n                'version': version,\n                'entry_points': []\n            }\n\n    return plugins\n\n\n@pytest.fixture(scope='function')\ndef interface(tmp_path):\n    from tests.utils import MockEnvironment\n\n    return Interface(\n        path=tmp_path / 'interface',\n        environment=MockEnvironment()\n    )\n\n\n@pytest.fixture(scope='function')\ndef dummy_plugin(interface):\n    return interface.make_dummy_plugin()\n\n\n@pytest.fixture(scope='function')\ndef broken_plugin(interface):\n    base_plugin = interface.make_dummy_plugin()\n    with open(base_plugin.path / (base_plugin.import_name + '.py'), 'a') as stream:\n        stream.write('raise ValueError(\"broken plugin\")\\n')\n    return base_plugin\n\n\n@pytest.fixture(scope='function')\ndef dummy_plugins(interface):\n    # Multiple plugins with different configurations\n    return [\n        interface.make_dummy_plugin(),\n        interface.make_dummy_plugin(\n            version='3.2.0'\n        ),\n        interface.make_dummy_plugin(\n            entry_points=[\n                EntryPoint('test_1', 'httpie.plugins.converter.v1'),\n                EntryPoint('test_2', 'httpie.plugins.formatter.v1')\n            ]\n        ),\n    ]\n\n\n@pytest.fixture\ndef httpie_plugins(interface):\n    from tests.utils import httpie\n    from httpie.plugins.registry import plugin_manager\n\n    def runner(*args, cli_mode: bool = True):\n        args = list(args)\n        if cli_mode:\n            args.insert(0, 'cli')\n        args.insert(cli_mode, 'plugins')\n\n        # Prevent installed plugins from showing up.\n        original_plugins = plugin_manager.copy()\n        clean_sys_path = set(sys.path).difference(site.getsitepackages())\n        with patch('sys.path', list(clean_sys_path)):\n            response = httpie(*args, env=interface.environment)\n        plugin_manager.clear()\n        plugin_manager.extend(original_plugins)\n        return response\n\n    return runner\n\n\n@pytest.fixture\ndef httpie_plugins_success(httpie_plugins):\n    def runner(*args, cli_mode: bool = True):\n        response = httpie_plugins(*args, cli_mode=True)\n        assert response.exit_status == ExitStatus.SUCCESS\n        return response.splitlines()\n    return runner\n"
  }
]