[
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing\n\nThank you for being interested in contributing to HTTPX.\nThere are many ways you can contribute to the project:\n\n- Try HTTPX and [report bugs/issues you find](https://github.com/encode/httpx/issues/new)\n- [Implement new features](https://github.com/encode/httpx/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)\n- [Review Pull Requests of others](https://github.com/encode/httpx/pulls)\n- Write documentation\n- Participate in discussions\n\n## Reporting Bugs or Other Issues\n\nFound something that HTTPX should support?\nStumbled upon some unexpected behaviour?\n\nContributions should generally start out with [a discussion](https://github.com/encode/httpx/discussions).\nPossible bugs may be raised as a \"Potential Issue\" discussion, feature requests may\nbe raised as an \"Ideas\" discussion. We can then determine if the discussion needs\nto be escalated into an \"Issue\" or not, or if we'd consider a pull request.\n\nTry to be more descriptive as you can and in case of a bug report,\nprovide as much information as possible like:\n\n- OS platform\n- Python version\n- Installed dependencies and versions (`python -m pip freeze`)\n- Code snippet\n- Error traceback\n\nYou should always try to reduce any examples to the *simplest possible case*\nthat demonstrates the issue.\n\nSome possibly useful tips for narrowing down potential issues...\n\n- Does the issue exist on HTTP/1.1, or HTTP/2, or both?\n- Does the issue exist with `Client`, `AsyncClient`, or both?\n- When using `AsyncClient` does the issue exist when using `asyncio` or `trio`, or both?\n\n## Development\n\nTo start developing HTTPX create a **fork** of the\n[HTTPX repository](https://github.com/encode/httpx) on GitHub.\n\nThen clone your fork with the following command replacing `YOUR-USERNAME` with\nyour GitHub username:\n\n```shell\n$ git clone https://github.com/YOUR-USERNAME/httpx\n```\n\nYou can now install the project and its dependencies using:\n\n```shell\n$ cd httpx\n$ scripts/install\n```\n\n## Testing and Linting\n\nWe use custom shell scripts to automate testing, linting,\nand documentation building workflow.\n\nTo run the tests, use:\n\n```shell\n$ scripts/test\n```\n\n!!! warning\n    The test suite spawns testing servers on ports **8000** and **8001**.\n    Make sure these are not in use, so the tests can run properly.\n\nYou can run a single test script like this:\n\n```shell\n$ scripts/test -- tests/test_multipart.py\n```\n\nTo run the code auto-formatting:\n\n```shell\n$ scripts/lint\n```\n\nLastly, to run code checks separately (they are also run as part of `scripts/test`), run:\n\n```shell\n$ scripts/check\n```\n\n## Documenting\n\nDocumentation pages are located under the `docs/` folder.\n\nTo run the documentation site locally (useful for previewing changes), use:\n\n```shell\n$ scripts/docs\n```\n\n## Resolving Build / CI Failures\n\nOnce you've submitted your pull request, the test suite will automatically run, and the results will show up in GitHub.\nIf the test suite fails, you'll want to click through to the \"Details\" link, and try to identify why the test suite failed.\n\n<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img src=\"https://raw.githubusercontent.com/encode/httpx/master/docs/img/gh-actions-fail.png\" alt='Failing PR commit status'>\n</p>\n\nHere are some common ways the test suite can fail:\n\n### Check Job Failed\n\n<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img src=\"https://raw.githubusercontent.com/encode/httpx/master/docs/img/gh-actions-fail-check.png\" alt='Failing GitHub action lint job'>\n</p>\n\nThis job failing means there is either a code formatting issue or type-annotation issue.\nYou can look at the job output to figure out why it's failed or within a shell run:\n\n```shell\n$ scripts/check\n```\n\nIt may be worth it to run `$ scripts/lint` to attempt auto-formatting the code\nand if that job succeeds commit the changes.\n\n### Docs Job Failed\n\nThis job failing means the documentation failed to build. This can happen for\na variety of reasons like invalid markdown or missing configuration within `mkdocs.yml`.\n\n### Python 3.X Job Failed\n\n<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img src=\"https://raw.githubusercontent.com/encode/httpx/master/docs/img/gh-actions-fail-test.png\" alt='Failing GitHub action test job'>\n</p>\n\nThis job failing means the unit tests failed or not all code paths are covered by unit tests.\n\nIf tests are failing you will see this message under the coverage report:\n\n`=== 1 failed, 435 passed, 1 skipped, 1 xfailed in 11.09s ===`\n\nIf tests succeed but coverage doesn't reach our current threshold, you will see this\nmessage under the coverage report:\n\n`FAIL Required test coverage of 100% not reached. Total coverage: 99.00%`\n\n## Releasing\n\n*This section is targeted at HTTPX maintainers.*\n\nBefore releasing a new version, create a pull request that includes:\n\n- **An update to the changelog**:\n    - We follow the format from [keepachangelog](https://keepachangelog.com/en/1.0.0/).\n    - [Compare](https://github.com/encode/httpx/compare/) `master` with the tag of the latest release, and list all entries that are of interest to our users:\n        - Things that **must** go in the changelog: added, changed, deprecated or removed features, and bug fixes.\n        - Things that **should not** go in the changelog: changes to documentation, tests or tooling.\n        - Try sorting entries in descending order of impact / importance.\n        - Keep it concise and to-the-point. 🎯\n- **A version bump**: see `__version__.py`.\n\nFor an example, see [#1006](https://github.com/encode/httpx/pull/1006).\n\nOnce the release PR is merged, create a\n[new release](https://github.com/encode/httpx/releases/new) including:\n\n- Tag version like `0.13.3`.\n- Release title `Version 0.13.3`\n- Description copied from the changelog.\n\nOnce created this release will be automatically uploaded to PyPI.\n\nIf something goes wrong with the PyPI job the release can be published using the\n`scripts/publish` script.\n\n## Development proxy setup\n\nTo test and debug requests via a proxy it's best to run a proxy server locally.\nAny server should do but HTTPCore's test suite uses\n[`mitmproxy`](https://mitmproxy.org/) which is written in Python, it's fully\nfeatured and has excellent UI and tools for introspection of requests.\n\nYou can install `mitmproxy` using `pip install mitmproxy` or [several\nother ways](https://docs.mitmproxy.org/stable/overview-installation/).\n\n`mitmproxy` does require setting up local TLS certificates for HTTPS requests,\nas its main purpose is to allow developers to inspect requests that pass through\nit. We can set them up follows:\n\n1. [`pip install trustme-cli`](https://github.com/sethmlarson/trustme-cli/).\n2. `trustme-cli -i example.org www.example.org`, assuming you want to test\nconnecting to that domain, this will create three files: `server.pem`,\n`server.key` and `client.pem`.\n3. `mitmproxy` requires a PEM file that includes the private key and the\ncertificate so we need to concatenate them:\n`cat server.key server.pem > server.withkey.pem`.\n4. Start the proxy server `mitmproxy --certs server.withkey.pem`, or use the\n[other mitmproxy commands](https://docs.mitmproxy.org/stable/) with different\nUI options.\n\nAt this point the server is ready to start serving requests, you'll need to\nconfigure HTTPX as described in the\n[proxy section](https://www.python-httpx.org/advanced/#http-proxying) and\nthe [SSL certificates section](https://www.python-httpx.org/advanced/#ssl-certificates),\nthis is where our previously generated `client.pem` comes in:\n\n```\nimport httpx\n\nssl_context = httpx.SSLContext()\nssl_context.load_verify_locations(\"/path/to/client.pem\")\n\nwith httpx.Client(proxy=\"http://127.0.0.1:8080/\", ssl_context=ssl_context) as client:\n    response = client.get(\"https://example.org\")\n    print(response.status_code)  # should print 200\n```\n\nNote, however, that HTTPS requests will only succeed to the host specified\nin the SSL/TLS certificate we generated, HTTPS requests to other hosts will\nraise an error like:\n\n```\nssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate\nverify failed: Hostname mismatch, certificate is not valid for\n'duckduckgo.com'. (_ssl.c:1108)\n```\n\nIf you want to make requests to more hosts you'll need to regenerate the\ncertificates and include all the hosts you intend to connect to in the\nseconds step, i.e.\n\n`trustme-cli -i example.org www.example.org duckduckgo.com www.duckduckgo.com`\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: encode\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-issue.md",
    "content": "---\nname: Issue\nabout: Please only raise an issue if you've been advised to do so after discussion. Thanks! 🙏\n---\n\nThe starting point for issues should usually be a discussion...\n\nhttps://github.com/encode/httpx/discussions\n\nPossible bugs may be raised as a \"Potential Issue\" discussion, feature requests may be raised as an \"Ideas\" discussion. We can then determine if the discussion needs to be escalated into an \"Issue\" or not.\n\nThis will help us ensure that the \"Issues\" list properly reflects ongoing or needed work on the project.\n\n---\n\n- [ ] Initially raised as discussion #...\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser\nblank_issues_enabled: false\ncontact_links:\n- name: Discussions\n  url: https://github.com/encode/httpx/discussions\n  about: >\n    The \"Discussions\" forum is where you want to start. 💖\n- name: Chat\n  url: https://gitter.im/encode/community\n  about: >\n    Our community chat forum.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Thanks for contributing to HTTPX! 💚\nGiven this is a project maintained by volunteers, please read this template to not waste your time, or ours! 😁 -->\n\n# Summary\n\n<!-- Write a small summary about what is happening here. -->\n\n# Checklist\n\n- [ ] I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)\n- [ ] I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.\n- [ ] I've updated the documentation accordingly.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"pip\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n    groups:\n      python-packages:\n        patterns:\n          - \"*\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule: \n      interval: monthly\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish\n\non:\n  push:\n    tags:\n      - '*'\n\njobs:\n  publish:\n    name: \"Publish release\"\n    runs-on: \"ubuntu-latest\"\n\n    environment:\n       name: deploy\n\n    steps:\n      - uses: \"actions/checkout@v4\"\n      - uses: \"actions/setup-python@v6\"\n        with:\n          python-version: 3.9\n      - name: \"Install dependencies\"\n        run: \"scripts/install\"\n      - name: \"Build package & docs\"\n        run: \"scripts/build\"\n      - name: \"Publish to PyPI & deploy docs\"\n        run: \"scripts/publish\"\n        env:\n          TWINE_USERNAME: __token__\n          TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/test-suite.yml",
    "content": "---\nname: Test Suite\n\non:\n  push:\n    branches: [\"master\"]\n  pull_request:\n    branches: [\"master\", \"version-*\"]\n\njobs:\n  tests:\n    name: \"Python ${{ matrix.python-version }}\"\n    runs-on: \"ubuntu-latest\"\n\n    strategy:\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\"]\n\n    steps:\n      - uses: \"actions/checkout@v4\"\n      - uses: \"actions/setup-python@v6\"\n        with:\n          python-version: \"${{ matrix.python-version }}\"\n          allow-prereleases: true\n      - name: \"Install dependencies\"\n        run: \"scripts/install\"\n      - name: \"Run linting checks\"\n        run: \"scripts/check\"\n      - name: \"Build package & docs\"\n        run: \"scripts/build\"\n      - name: \"Run tests\"\n        run: \"scripts/test\"\n      - name: \"Enforce coverage\"\n        run: \"scripts/coverage\"\n"
  },
  {
    "path": ".gitignore",
    "content": "*.pyc\n.coverage\n.pytest_cache/\n.mypy_cache/\n__pycache__/\nhtmlcov/\nsite/\n*.egg-info/\nvenv*/\n.python-version\nbuild/\ndist/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).\n\n## [UNRELEASED]\n\n### Removed\n\n* Drop support for Python 3.8\n\n### Added\n\n* Expose `FunctionAuth` from the public API. (#3699)\n\n## 0.28.1 (6th December, 2024)\n\n* Fix SSL case where `verify=False` together with client side certificates.\n \n## 0.28.0 (28th November, 2024)\n\nBe aware that the default *JSON request bodies now use a more compact representation*. This is generally considered a prefered style, tho may require updates to test suites.\n\nThe 0.28 release includes a limited set of deprecations...\n\n**Deprecations**:\n\nWe are working towards a simplified SSL configuration API.\n\n*For users of the standard `verify=True` or `verify=False` cases, or `verify=<ssl_context>` case this should require no changes. The following cases have been deprecated...*\n\n* The `verify` argument as a string argument is now deprecated and will raise warnings.\n* The `cert` argument is now deprecated and will raise warnings.\n\nOur revised [SSL documentation](docs/advanced/ssl.md) covers how to implement the same behaviour with a more constrained API.\n\n**The following changes are also included**:\n\n* The deprecated `proxies` argument has now been removed.\n* The deprecated `app` argument has now been removed.\n* JSON request bodies use a compact representation. (#3363)\n* Review URL percent escape sets, based on WHATWG spec. (#3371, #3373)\n* Ensure `certifi` and `httpcore` are only imported if required. (#3377)\n* Treat `socks5h` as a valid proxy scheme. (#3178)\n* Cleanup `Request()` method signature in line with `client.request()` and `httpx.request()`. (#3378)\n* Bugfix: When passing `params={}`, always strictly update rather than merge with an existing querystring. (#3364)\n\n## 0.27.2 (27th August, 2024)\n\n### Fixed\n\n* Reintroduced supposedly-private `URLTypes` shortcut. (#2673)\n\n## 0.27.1 (27th August, 2024)\n\n### Added\n\n* Support for `zstd` content decoding using the python `zstandard` package is added. Installable using `httpx[zstd]`. (#3139)\n\n### Fixed\n\n* Improved error messaging for `InvalidURL` exceptions. (#3250)\n* Fix `app` type signature in `ASGITransport`. (#3109)\n\n## 0.27.0 (21st February, 2024)\n\n### Deprecated\n\n* The `app=...` shortcut has been deprecated. Use the explicit style of `transport=httpx.WSGITransport()` or `transport=httpx.ASGITransport()` instead.\n\n### Fixed\n\n* Respect the `http1` argument while configuring proxy transports. (#3023)\n* Fix RFC 2069 mode digest authentication. (#3045)\n\n## 0.26.0 (20th December, 2023)\n\n### Added\n\n* The `proxy` argument was added. You should use the `proxy` argument instead of the deprecated `proxies`, or use `mounts=` for more complex configurations. (#2879)\n\n### Deprecated\n\n* The `proxies` argument is now deprecated. It will still continue to work, but it will be removed in the future. (#2879)\n\n### Fixed\n\n* Fix cases of double escaping of URL path components. Allow / as a safe character in the query portion. (#2990)\n* Handle `NO_PROXY` envvar cases when a fully qualified URL is supplied as the value. (#2741)\n* Allow URLs where username or password contains unescaped '@'. (#2986)\n* Ensure ASGI `raw_path` does not include URL query component. (#2999)\n* Ensure `Response.iter_text()` cannot yield empty strings. (#2998)\n\n## 0.25.2 (24th November, 2023)\n\n### Added\n\n* Add missing type hints to few `__init__()` methods. (#2938)\n\n## 0.25.1 (3rd November, 2023)\n\n### Added\n\n* Add support for Python 3.12. (#2854)\n* Add support for httpcore 1.0 (#2885)\n\n### Fixed\n\n* Raise `ValueError` on `Response.encoding` being set after `Response.text` has been accessed. (#2852)\n\n## 0.25.0 (11th September, 2023)\n\n### Removed\n\n* Drop support for Python 3.7. (#2813)\n\n### Added\n\n* Support HTTPS proxies. (#2845)\n* Change the type of `Extensions` from `Mapping[Str, Any]` to `MutableMapping[Str, Any]`. (#2803)\n* Add `socket_options` argument to `httpx.HTTPTransport` and `httpx.AsyncHTTPTransport` classes. (#2716)\n* The `Response.raise_for_status()` method now returns the response instance. For example: `data = httpx.get('...').raise_for_status().json()`. (#2776)\n\n### Fixed\n\n* Return `500` error response instead of exceptions when `raise_app_exceptions=False` is set on `ASGITransport`. (#2669)\n* Ensure all `WSGITransport` environs have a `SERVER_PROTOCOL`. (#2708)\n* Always encode forward slashes as `%2F` in query parameters (#2723)\n* Use Mozilla documentation instead of `httpstatuses.com` for HTTP error reference (#2768)\n\n## 0.24.1 (17th May, 2023)\n\n### Added\n\n* Provide additional context in some `InvalidURL` exceptions. (#2675)\n\n### Fixed\n\n* Fix optional percent-encoding behaviour. (#2671)\n* More robust checking for opening upload files in binary mode. (#2630)\n* Properly support IP addresses in `NO_PROXY` environment variable. (#2659)\n* Set default file for `NetRCAuth()` to `None` to use the stdlib default. (#2667)\n* Set logging request lines to INFO level for async requests, in line with sync requests. (#2656)\n* Fix which gen-delims need to be escaped for path/query/fragment components in URL. (#2701)\n\n## 0.24.0 (6th April, 2023)\n\n### Changed\n\n* The logging behaviour has been changed to be more in-line with other standard Python logging usages. We no longer have a custom `TRACE` log level, and we no longer use the `HTTPX_LOG_LEVEL` environment variable to auto-configure logging. We now have a significant amount of `DEBUG` logging available at the network level. Full documentation is available at https://www.python-httpx.org/logging/ (#2547, encode/httpcore#648)\n* The `Response.iter_lines()` method now matches the stdlib behaviour and does not include the newline characters. It also resolves a performance issue. (#2423)\n* Query parameter encoding switches from using + for spaces and %2F for forward slash, to instead using %20 for spaces and treating forward slash as a safe, unescaped character. This differs from `requests`, but is in line with browser behavior in Chrome, Safari, and Firefox. Both options are RFC valid. (#2543)\n* NetRC authentication is no longer automatically handled, but is instead supported by an explicit `httpx.NetRCAuth()` authentication class. See the documentation at https://www.python-httpx.org/advanced/authentication/#netrc-authentication (#2525)\n\n### Removed\n\n* The `rfc3986` dependancy has been removed. (#2252)\n\n## 0.23.3 (4th January, 2023)\n\n### Fixed\n\n* Version 0.23.2 accidentally included stricter type checking on query parameters. This shouldn've have been included in a minor version bump, and is now reverted. (#2523, #2539)\n\n## 0.23.2 (2nd January, 2023)\n\n### Added\n\n* Support digest auth nonce counting to avoid multiple auth requests. (#2463)\n\n### Fixed\n\n* Multipart file uploads where the file length cannot be determine now use chunked transfer encoding, rather than loading the entire file into memory in order to determine the `Content-Length`. (#2382)\n* Raise `TypeError` if content is passed a dict-instance. (#2495)\n* Partially revert the API breaking change in 0.23.1, which removed `RawURL`. We continue to expose a `url.raw` property which is now a plain named-tuple. This API is still expected to be deprecated, but we will do so with a major version bump. (#2481)\n\n## 0.23.1 (18th November, 2022)\n\n**Note**: The 0.23.1 release should have used a proper version bump, rather than a minor point release.\nThere are API surface area changes that may affect some users.\nSee the \"Removed\" section of these release notes for details.\n\n### Added\n\n* Support for Python 3.11. (#2420)\n* Allow setting an explicit multipart boundary in `Content-Type` header. (#2278)\n* Allow `tuple` or `list` for multipart values, not just `list`. (#2355)\n* Allow `str` content for multipart upload files. (#2400)\n* Support connection upgrades. See https://www.encode.io/httpcore/extensions/#upgrade-requests\n\n### Fixed\n\n* Don't drop empty query parameters. (#2354)\n\n### Removed\n\n* Upload files *must* always be opened in binary mode. (#2400)\n* Drop `.read`/`.aread` from `SyncByteStream`/`AsyncByteStream`. (#2407)\n* Drop `RawURL`. (#2241)\n\n## 0.23.0 (23rd May, 2022)\n\n### Changed\n\n* Drop support for Python 3.6. (#2097)\n* Use `utf-8` as the default character set, instead of falling back to `charset-normalizer` for auto-detection. To enable automatic character set detection, see [the documentation](https://www.python-httpx.org/advanced/text-encodings/#using-auto-detection). (#2165)\n\n### Fixed\n\n* Fix `URL.copy_with` for some oddly formed URL cases. (#2185)\n* Digest authentication should use case-insensitive comparison for determining which algorithm is being used. (#2204)\n* Fix console markup escaping in command line client. (#1866)\n* When files are used in multipart upload, ensure we always seek to the start of the file. (#2065)\n* Ensure that `iter_bytes` never yields zero-length chunks. (#2068)\n* Preserve `Authorization` header for redirects that are to the same origin, but are an `http`-to-`https` upgrade. (#2074)\n* When responses have binary output, don't print the output to the console in the command line client. Use output like `<16086 bytes of binary data>` instead. (#2076)\n* Fix display of `--proxies` argument in the command line client help. (#2125)\n* Close responses when task cancellations occur during stream reading. (#2156)\n* Fix type error on accessing `.request` on `HTTPError` exceptions. (#2158)\n\n## 0.22.0 (26th January, 2022)\n\n### Added\n\n* Support for [the SOCKS5 proxy protocol](https://www.python-httpx.org/advanced/proxies/#socks) via [the `socksio` package](https://github.com/sethmlarson/socksio). (#2034)\n* Support for custom headers in multipart/form-data requests (#1936)\n\n### Fixed\n\n* Don't perform unreliable close/warning on `__del__` with unclosed clients. (#2026)\n* Fix `Headers.update(...)` to correctly handle repeated headers (#2038)\n\n## 0.21.3 (6th January, 2022)\n\n### Fixed\n\n* Fix streaming uploads using `SyncByteStream` or `AsyncByteStream`. Regression in 0.21.2. (#2016)\n\n## 0.21.2 (5th January, 2022)\n\n### Fixed\n\n* HTTP/2 support for tunnelled proxy cases. (#2009)\n* Improved the speed of large file uploads. (#1948)\n\n## 0.21.1 (16th November, 2021)\n\n### Fixed\n\n* The `response.url` property is now correctly annotated as `URL`, instead of `Optional[URL]`. (#1940)\n\n## 0.21.0 (15th November, 2021)\n\nThe 0.21.0 release integrates against a newly redesigned `httpcore` backend.\n\nBoth packages ought to automatically update to the required versions, but if you are\nseeing any issues, you should ensure that you have `httpx==0.21.*` and `httpcore==0.14.*` installed.\n\n### Added\n\n* The command-line client will now display connection information when `-v/--verbose` is used.\n* The command-line client will now display server certificate information when `-v/--verbose` is used.\n* The command-line client is now able to properly detect if the outgoing request\nshould be formatted as HTTP/1.1 or HTTP/2, based on the result of the HTTP/2 negotiation.\n\n### Removed\n\n* Curio support is no longer currently included. Please get in touch if you require this, so that we can assess priorities.\n\n## 0.20.0 (13th October, 2021)\n\nThe 0.20.0 release adds an integrated command-line client, and also includes some\ndesign changes. The most notable of these is that redirect responses are no longer\nautomatically followed, unless specifically requested.\n\nThis design decision prioritises a more explicit approach to redirects, in order\nto avoid code that unintentionally issues multiple requests as a result of\nmisconfigured URLs.\n\nFor example, previously a client configured to send requests to `http://api.github.com/`\nwould end up sending every API request twice, as each request would be redirected to `https://api.github.com/`.\n\nIf you do want auto-redirect behaviour, you can enable this either by configuring\nthe client instance with `Client(follow_redirects=True)`, or on a per-request\nbasis, with `.get(..., follow_redirects=True)`.\n\nThis change is a classic trade-off between convenience and precision, with no \"right\"\nanswer. See [discussion #1785](https://github.com/encode/httpx/discussions/1785) for more\ncontext.\n\nThe other major design change is an update to the Transport API, which is the low-level\ninterface against which requests are sent. Previously this interface used only primitive\ndatastructures, like so...\n\n```python\n(status_code, headers, stream, extensions) = transport.handle_request(method, url, headers, stream, extensions)\ntry\n    ...\nfinally:\n    stream.close()\n```\n\nNow the interface is much simpler...\n\n```python\nresponse = transport.handle_request(request)\ntry\n    ...\nfinally:\n    response.close()\n```\n\n### Changed\n\n* The `allow_redirects` flag is now `follow_redirects` and defaults to `False`.\n* The `raise_for_status()` method will now raise an exception for any responses\n  except those with 2xx status codes. Previously only 4xx and 5xx status codes\n  would result in an exception.\n* The low-level transport API changes to the much simpler `response = transport.handle_request(request)`.\n* The `client.send()` method no longer accepts a `timeout=...` argument, but the\n  `client.build_request()` does. This required by the signature change of the\n  Transport API. The request timeout configuration is now stored on the request\n  instance, as `request.extensions['timeout']`.\n\n### Added\n\n* Added the `httpx` command-line client.\n* Response instances now include `.is_informational`, `.is_success`, `.is_redirect`, `.is_client_error`, and `.is_server_error`\n  properties for checking 1xx, 2xx, 3xx, 4xx, and 5xx response types. Note that the behaviour of `.is_redirect` is slightly different in that it now returns True for all 3xx responses, in order to allow for a consistent set of properties onto the different HTTP status code types. The `response.has_redirect_location` location may be used to determine responses with properly formed URL redirects.\n\n### Fixed\n\n* `response.iter_bytes()` no longer raises a ValueError when called on a response with no content. (Pull #1827)\n* The `'wsgi.error'` configuration now defaults to `sys.stderr`, and is corrected to be a `TextIO` interface, not a `BytesIO` interface. Additionally, the WSGITransport now accepts a `wsgi_error` configuration. (Pull #1828)\n* Follow the WSGI spec by properly closing the iterable returned by the application. (Pull #1830)\n\n## 0.19.0 (19th August, 2021)\n\n### Added\n\n* Add support for `Client(allow_redirects=<bool>)`. (Pull #1790)\n* Add automatic character set detection, when no `charset` is included in the response `Content-Type` header. (Pull #1791)\n\n### Changed\n\n* Event hooks are now also called for any additional redirect or auth requests/responses. (Pull #1806)\n* Strictly enforce that upload files must be opened in binary mode. (Pull #1736)\n* Strictly enforce that client instances can only be opened and closed once, and cannot be re-opened. (Pull #1800)\n* Drop `mode` argument from `httpx.Proxy(..., mode=...)`. (Pull #1795)\n\n## 0.18.2 (17th June, 2021)\n\n### Added\n\n* Support for Python 3.10. (Pull #1687)\n* Expose `httpx.USE_CLIENT_DEFAULT`, used as the default to `auth` and `timeout` parameters in request methods. (Pull #1634)\n* Support [HTTP/2 \"prior knowledge\"](https://python-hyper.org/projects/hyper-h2/en/v2.3.1/negotiating-http2.html#prior-knowledge), using `httpx.Client(http1=False, http2=True)`. (Pull #1624)\n\n### Fixed\n\n* Clean up some cases where warnings were being issued. (Pull #1687)\n* Prefer Content-Length over Transfer-Encoding: chunked for content=<file-like> cases. (Pull #1619)\n\n## 0.18.1 (29th April, 2021)\n\n### Changed\n\n* Update brotli support to use the `brotlicffi` package (Pull #1605)\n* Ensure that `Request(..., stream=...)` does not auto-generate any headers on the request instance. (Pull #1607)\n\n### Fixed\n\n* Pass through `timeout=...` in top-level httpx.stream() function. (Pull #1613)\n* Map httpcore transport close exceptions to httpx exceptions. (Pull #1606)\n\n## 0.18.0 (27th April, 2021)\n\nThe 0.18.x release series formalises our low-level Transport API, introducing the base classes `httpx.BaseTransport` and `httpx.AsyncBaseTransport`.\n\nSee the \"[Custom transports](https://www.python-httpx.org/advanced/transports/#custom-transports)\" documentation and the [`httpx.BaseTransport.handle_request()`](https://github.com/encode/httpx/blob/397aad98fdc8b7580a5fc3e88f1578b4302c6382/httpx/_transports/base.py#L77-L147) docstring for more complete details on implementing custom transports.\n\nPull request #1522 includes a checklist of differences from the previous `httpcore` transport API, for developers implementing custom transports.\n\nThe following API changes have been issuing deprecation warnings since 0.17.0 onwards, and are now fully deprecated...\n\n* You should now use httpx.codes consistently instead of httpx.StatusCodes.\n* Use limits=... instead of pool_limits=....\n* Use proxies={\"http://\": ...} instead of proxies={\"http\": ...} for scheme-specific mounting.\n\n### Changed\n\n* Transport instances now inherit from `httpx.BaseTransport` or `httpx.AsyncBaseTransport`,\n  and should implement either the `handle_request` method or `handle_async_request` method. (Pull #1522, #1550)\n* The `response.ext` property and `Response(ext=...)` argument are now named `extensions`. (Pull #1522)\n* The recommendation to not use `data=<bytes|str|bytes (a)iterator>` in favour of `content=<bytes|str|bytes (a)iterator>` has now been escalated to a deprecation warning. (Pull #1573)\n* Drop `Response(on_close=...)` from API, since it was a bit of leaking implementation detail. (Pull #1572)\n* When using a client instance, cookies should always be set on the client, rather than on a per-request basis. We prefer enforcing a stricter API here because it provides clearer expectations around cookie persistence, particularly when redirects occur. (Pull #1574)\n* The runtime exception `httpx.ResponseClosed` is now named `httpx.StreamClosed`. (#1584)\n* The `httpx.QueryParams` model now presents an immutable interface. There is a discussion on [the design and motivation here](https://github.com/encode/httpx/discussions/1599). Use `client.params = client.params.merge(...)` instead of `client.params.update(...)`. The basic query manipulation methods are `query.set(...)`, `query.add(...)`, and `query.remove()`. (#1600)\n\n### Added\n\n* The `Request` and `Response` classes can now be serialized using pickle. (#1579)\n* Handle `data={\"key\": [None|int|float|bool]}` cases. (Pull #1539)\n* Support `httpx.URL(**kwargs)`, for example `httpx.URL(scheme=\"https\", host=\"www.example.com\", path=\"/')`, or `httpx.URL(\"https://www.example.com/\", username=\"tom@gmail.com\", password=\"123 456\")`. (Pull #1601)\n* Support `url.copy_with(params=...)`. (Pull #1601)\n* Add `url.params` parameter, returning an immutable `QueryParams` instance. (Pull #1601)\n* Support query manipulation methods on the URL class. These are `url.copy_set_param()`, `url.copy_add_param()`, `url.copy_remove_param()`, `url.copy_merge_params()`. (Pull #1601)\n* The `httpx.URL` class now performs port normalization, so `:80` ports are stripped from `http` URLs and `:443` ports are stripped from `https` URLs. (Pull #1603)\n* The `URL.host` property returns unicode strings for internationalized domain names. The `URL.raw_host` property returns byte strings with IDNA escaping applied. (Pull #1590)\n\n### Fixed\n\n* Fix Content-Length for cases of `files=...` where unicode string is used as the file content. (Pull #1537)\n* Fix some cases of merging relative URLs against `Client(base_url=...)`. (Pull #1532)\n* The `request.content` attribute is now always available except for streaming content, which requires an explicit `.read()`. (Pull #1583)\n\n## 0.17.1 (March 15th, 2021)\n\n### Fixed\n\n* Type annotation on `CertTypes` allows `keyfile` and `password` to be optional. (Pull #1503)\n* Fix httpcore pinned version. (Pull #1495)\n\n## 0.17.0 (February 28th, 2021)\n\n### Added\n\n* Add `httpx.MockTransport()`, allowing to mock out a transport using pre-determined responses. (Pull #1401, Pull #1449)\n* Add `httpx.HTTPTransport()` and `httpx.AsyncHTTPTransport()` default transports. (Pull #1399)\n* Add mount API support, using `httpx.Client(mounts=...)`. (Pull #1362)\n* Add `chunk_size` parameter to `iter_raw()`, `iter_bytes()`, `iter_text()`. (Pull #1277)\n* Add `keepalive_expiry` parameter to `httpx.Limits()` configuration. (Pull #1398)\n* Add repr to `httpx.Cookies` to display available cookies. (Pull #1411)\n* Add support for `params=<tuple>` (previously only `params=<list>` was supported). (Pull #1426)\n\n### Fixed\n\n* Add missing `raw_path` to ASGI scope. (Pull #1357)\n* Tweak `create_ssl_context` defaults to use `trust_env=True`. (Pull #1447)\n* Properly URL-escape WSGI `PATH_INFO`. (Pull #1391)\n* Properly set default ports in WSGI transport. (Pull #1469)\n* Properly encode slashes when using `base_url`. (Pull #1407)\n* Properly map exceptions in `request.aclose()`. (Pull #1465)\n\n## 0.16.1 (October 8th, 2020)\n\n### Fixed\n\n* Support literal IPv6 addresses in URLs. (Pull #1349)\n* Force lowercase headers in ASGI scope dictionaries. (Pull #1351)\n\n## 0.16.0 (October 6th, 2020)\n\n### Changed\n\n* Preserve HTTP header casing. (Pull #1338, encode/httpcore#216, python-hyper/h11#104)\n* Drop `response.next()` and `response.anext()` methods in favour of `response.next_request` attribute. (Pull #1339)\n* Closed clients now raise a runtime error if attempting to send a request. (Pull #1346)\n\n### Added\n\n* Add Python 3.9 to officially supported versions.\n* Type annotate `__enter__`/`__exit__`/`__aenter__`/`__aexit__` in a way that supports subclasses of `Client` and `AsyncClient`. (Pull #1336)\n\n## 0.15.5 (October 1st, 2020)\n\n### Added\n\n* Add `response.next_request` (Pull #1334)\n\n## 0.15.4 (September 25th, 2020)\n\n### Added\n\n* Support direct comparisons between `Headers` and dicts or lists of two-tuples. Eg. `assert response.headers == {\"Content-Length\": 24}` (Pull #1326)\n\n### Fixed\n\n* Fix automatic `.read()` when `Response` instances are created with `content=<str>` (Pull #1324)\n\n## 0.15.3 (September 24th, 2020)\n\n### Fixed\n\n* Fixed connection leak in async client due to improper closing of response streams. (Pull #1316)\n\n## 0.15.2 (September 23nd, 2020)\n\n### Fixed\n\n* Fixed `response.elapsed` property. (Pull #1313)\n* Fixed client authentication interaction with `.stream()`. (Pull #1312)\n\n## 0.15.1 (September 23nd, 2020)\n\n### Fixed\n\n* ASGITransport now properly applies URL decoding to the `path` component, as-per the ASGI spec. (Pull #1307)\n\n## 0.15.0 (September 22nd, 2020)\n\n### Added\n\n* Added support for curio. (Pull https://github.com/encode/httpcore/pull/168)\n* Added support for event hooks. (Pull #1246)\n* Added support for authentication flows which require either sync or async I/O. (Pull #1217)\n* Added support for monitoring download progress with `response.num_bytes_downloaded`. (Pull #1268)\n* Added `Request(content=...)` for byte content, instead of overloading `Request(data=...)` (Pull #1266)\n* Added support for all URL components as parameter names when using `url.copy_with(...)`. (Pull #1285)\n* Neater split between automatically populated headers on `Request` instances, vs default `client.headers`. (Pull #1248)\n* Unclosed `AsyncClient` instances will now raise warnings if garbage collected. (Pull #1197)\n* Support `Response(content=..., text=..., html=..., json=...)` for creating usable response instances in code. (Pull #1265, #1297)\n* Support instantiating requests from the low-level transport API. (Pull #1293)\n* Raise errors on invalid URL types. (Pull #1259)\n\n### Changed\n\n* Cleaned up expected behaviour for URL escaping. `url.path` is now URL escaped. (Pull #1285)\n* Cleaned up expected behaviour for bytes vs str in URL components. `url.userinfo` and `url.query` are not URL escaped, and so return bytes. (Pull #1285)\n* Drop `url.authority` property in favour of `url.netloc`, since \"authority\" was semantically incorrect. (Pull #1285)\n* Drop `url.full_path` property in favour of `url.raw_path`, for better consistency with other parts of the API. (Pull #1285)\n* No longer use the `chardet` library for auto-detecting charsets, instead defaulting to a simpler approach when no charset is specified. (#1269)\n\n### Fixed\n\n* Swapped ordering of redirects and authentication flow. (Pull #1267)\n* `.netrc` lookups should use host, not host+port. (Pull #1298)\n\n### Removed\n\n* The `URLLib3Transport` class no longer exists. We've published it instead as an example of [a custom transport class](https://gist.github.com/florimondmanca/d56764d78d748eb9f73165da388e546e). (Pull #1182)\n* Drop `request.timer` attribute, which was being used internally to set `response.elapsed`. (Pull #1249)\n* Drop `response.decoder` attribute, which was being used internally. (Pull #1276)\n* `Request.prepare()` is now a private method. (Pull #1284)\n* The `Headers.getlist()` method had previously been deprecated in favour of `Headers.get_list()`. It is now fully removed.\n* The `QueryParams.getlist()` method had previously been deprecated in favour of `QueryParams.get_list()`. It is now fully removed.\n* The `URL.is_ssl` property had previously been deprecated in favour of `URL.scheme == \"https\"`. It is now fully removed.\n* The `httpx.PoolLimits` class had previously been deprecated in favour of `httpx.Limits`. It is now fully removed.\n* The `max_keepalive` setting had previously been deprecated in favour of the more explicit `max_keepalive_connections`. It is now fully removed.\n* The verbose `httpx.Timeout(5.0, connect_timeout=60.0)` style had previously been deprecated in favour of `httpx.Timeout(5.0, connect=60.0)`. It is now fully removed.\n* Support for instantiating a timeout config missing some defaults, such as `httpx.Timeout(connect=60.0)`, had previously been deprecated in favour of enforcing a more explicit style, such as `httpx.Timeout(5.0, connect=60.0)`. This is now strictly enforced.\n\n## 0.14.3 (September 2nd, 2020)\n\n### Added\n\n* `http.Response()` may now be instantiated without a `request=...` parameter. Useful for some unit testing cases. (Pull #1238)\n* Add `103 Early Hints` and `425 Too Early` status codes. (Pull #1244)\n\n### Fixed\n\n* `DigestAuth` now handles responses that include multiple 'WWW-Authenticate' headers. (Pull #1240)\n* Call into transport `__enter__`/`__exit__` or `__aenter__`/`__aexit__` when client is used in a context manager style. (Pull #1218)\n\n## 0.14.2 (August 24th, 2020)\n\n### Added\n\n* Support `client.get(..., auth=None)` to bypass the default authentication on a clients. (Pull #1115)\n* Support `client.auth = ...` property setter. (Pull #1185)\n* Support `httpx.get(..., proxies=...)` on top-level request functions. (Pull #1198)\n* Display instances with nicer import styles. (Eg. <httpx.ReadTimeout ...>) (Pull #1155)\n* Support `cookies=[(key, value)]` list-of-two-tuples style usage. (Pull #1211)\n\n### Fixed\n\n* Ensure that automatically included headers on a request may be modified. (Pull #1205)\n* Allow explicit `Content-Length` header on streaming requests. (Pull #1170)\n* Handle URL quoted usernames and passwords properly. (Pull #1159)\n* Use more consistent default for `HEAD` requests, setting `allow_redirects=True`. (Pull #1183)\n* If a transport error occurs while streaming the response, raise an `httpx` exception, not the underlying `httpcore` exception. (Pull #1190)\n* Include the underlying `httpcore` traceback, when transport exceptions occur. (Pull #1199)\n\n## 0.14.1 (August 11th, 2020)\n\n### Added\n\n* The `httpx.URL(...)` class now raises `httpx.InvalidURL` on invalid URLs, rather than exposing the underlying `rfc3986` exception. If a redirect response includes an invalid 'Location' header, then a `RemoteProtocolError` exception is raised, which will be associated with the request that caused it. (Pull #1163)\n\n### Fixed\n\n* Handling multiple `Set-Cookie` headers became broken in the 0.14.0 release, and is now resolved. (Pull #1156)\n\n## 0.14.0 (August 7th, 2020)\n\nThe 0.14 release includes a range of improvements to the public API, intended on preparing for our upcoming 1.0 release.\n\n* Our HTTP/2 support is now fully optional. **You now need to use `pip install httpx[http2]` if you want to include the HTTP/2 dependencies.**\n* Our HSTS support has now been removed. Rewriting URLs from `http` to `https` if the host is on the HSTS list can be beneficial in avoiding roundtrips to incorrectly formed URLs, but on balance we've decided to remove this feature, on the principle of least surprise. Most programmatic clients do not include HSTS support, and for now we're opting to remove our support for it.\n* Our exception hierarchy has been overhauled. Most users will want to stick with their existing `httpx.HTTPError` usage, but we've got a clearer overall structure now. See https://www.python-httpx.org/exceptions/ for more details.\n\nWhen upgrading you should be aware of the following public API changes. Note that deprecated usages will currently continue to function, but will issue warnings.\n\n* You should now use `httpx.codes` consistently instead of `httpx.StatusCodes`.\n* Usage of `httpx.Timeout()` should now always include an explicit default. Eg. `httpx.Timeout(None, pool=5.0)`.\n* When using `httpx.Timeout()`, we now have more concisely named keyword arguments. Eg. `read=5.0`, instead of `read_timeout=5.0`.\n* Use `httpx.Limits()` instead of `httpx.PoolLimits()`, and `limits=...` instead of `pool_limits=...`.\n* The `httpx.Limits(max_keepalive=...)` argument is now deprecated in favour of a more explicit `httpx.Limits(max_keepalive_connections=...)`.\n* Keys used with `Client(proxies={...})` should now be in the style of `{\"http://\": ...}`, rather than `{\"http\": ...}`.\n* The multidict methods `Headers.getlist()` and `QueryParams.getlist()` are deprecated in favour of more consistent `.get_list()` variants.\n* The `URL.is_ssl` property is deprecated in favour of `URL.scheme == \"https\"`.\n* The `URL.join(relative_url=...)` method is now `URL.join(url=...)`. This change does not support warnings for the deprecated usage style.\n\nOne notable aspect of the 0.14.0 release is that it tightens up the public API for `httpx`, by ensuring that several internal attributes and methods have now become strictly private.\n\nThe following previously had nominally public names on the client, but were all undocumented and intended solely for internal usage. They are all now replaced with underscored names, and should not be relied on or accessed.\n\nThese changes should not affect users who have been working from the `httpx` documentation.\n\n* `.merge_url()`, `.merge_headers()`, `.merge_cookies()`, `.merge_queryparams()`\n* `.build_auth()`, `.build_redirect_request()`\n* `.redirect_method()`, `.redirect_url()`, `.redirect_headers()`, `.redirect_stream()`\n* `.send_handling_redirects()`, `.send_handling_auth()`, `.send_single_request()`\n* `.init_transport()`, `.init_proxy_transport()`\n* `.proxies`, `.transport`, `.netrc`, `.get_proxy_map()`\n\nSee pull requests #997, #1065, #1071.\n\nSome areas of API which were already on the deprecation path, and were raising warnings or errors in 0.13.x have now been escalated to being fully removed.\n\n* Drop `ASGIDispatch`, `WSGIDispatch`, which have been replaced by `ASGITransport`, `WSGITransport`.\n* Drop `dispatch=...`` on client, which has been replaced by `transport=...``\n* Drop `soft_limit`, `hard_limit`, which have been replaced by `max_keepalive` and `max_connections`.\n* Drop `Response.stream` and` `Response.raw`, which have been replaced by ``.aiter_bytes` and `.aiter_raw`.\n* Drop `proxies=<transport instance>` in favor of `proxies=httpx.Proxy(...)`.\n\nSee pull requests #1057, #1058.\n\n### Added\n\n* Added dedicated exception class `httpx.HTTPStatusError` for `.raise_for_status()` exceptions. (Pull #1072)\n* Added `httpx.create_ssl_context()` helper function. (Pull #996)\n* Support for proxy exclusions like `proxies={\"https://www.example.com\": None}`. (Pull #1099)\n* Support `QueryParams(None)` and `client.params = None`. (Pull #1060)\n\n### Changed\n\n* Use `httpx.codes` consistently in favour of `httpx.StatusCodes` which is placed into deprecation. (Pull #1088)\n* Usage of `httpx.Timeout()` should now always include an explicit default. Eg. `httpx.Timeout(None, pool=5.0)`. (Pull #1085)\n* Switch to more concise `httpx.Timeout()` keyword arguments. Eg. `read=5.0`, instead of `read_timeout=5.0`. (Pull #1111)\n* Use `httpx.Limits()` instead of `httpx.PoolLimits()`, and `limits=...` instead of `pool_limits=...`. (Pull #1113)\n* Keys used with `Client(proxies={...})` should now be in the style of `{\"http://\": ...}`, rather than `{\"http\": ...}`. (Pull #1127)\n* The multidict methods `Headers.getlist` and `QueryParams.getlist` are deprecated in favour of more consistent `.get_list()` variants. (Pull #1089)\n* `URL.port` becomes `Optional[int]`. Now only returns a port if one is explicitly included in the URL string. (Pull #1080)\n* The `URL(..., allow_relative=[bool])` parameter no longer exists. All URL instances may be relative. (Pull #1073)\n* Drop unnecessary `url.full_path = ...` property setter. (Pull #1069)\n* The `URL.join(relative_url=...)` method is now `URL.join(url=...)`. (Pull #1129)\n* The `URL.is_ssl` property is deprecated in favour of `URL.scheme == \"https\"`. (Pull #1128)\n\n### Fixed\n\n* Add missing `Response.next()` method. (Pull #1055)\n* Ensure all exception classes are exposed as public API. (Pull #1045)\n* Support multiple items with an identical field name in multipart encodings. (Pull #777)\n* Skip HSTS preloading on single-label domains. (Pull #1074)\n* Fixes for `Response.iter_lines()`. (Pull #1033, #1075)\n* Ignore permission errors when accessing `.netrc` files. (Pull #1104)\n* Allow bare hostnames in `HTTP_PROXY` etc... environment variables. (Pull #1120)\n* Settings `app=...` or `transport=...` bypasses any environment based proxy defaults. (Pull #1122)\n* Fix handling of `.base_url` when a path component is included in the base URL. (Pull #1130)\n\n---\n\n## 0.13.3 (May 29th, 2020)\n\n### Fixed\n\n* Include missing keepalive expiry configuration. (Pull #1005)\n* Improved error message when URL redirect has a custom scheme. (Pull #1002)\n\n## 0.13.2 (May 27th, 2020)\n\n### Fixed\n\n* Include explicit \"Content-Length: 0\" on POST, PUT, PATCH if no request body is used. (Pull #995)\n* Add `http2` option to `httpx.Client`. (Pull #982)\n* Tighten up API typing in places. (Pull #992, #999)\n\n## 0.13.1 (May 22nd, 2020)\n\n### Fixed\n\n* Fix pool options deprecation warning. (Pull #980)\n* Include `httpx.URLLib3ProxyTransport` in top-level API. (Pull #979)\n\n## 0.13.0 (May 22nd, 2020)\n\nThis release switches to `httpcore` for all the internal networking, which means:\n\n* We're using the same codebase for both our sync and async clients.\n* HTTP/2 support is now available with the sync client.\n* We no longer have a `urllib3` dependency for our sync client, although there is still an *optional* `URLLib3Transport` class.\n\nIt also means we've had to remove our UDS support, since maintaining that would have meant having to push back our work towards a 1.0 release, which isn't a trade-off we wanted to make.\n\nWe also now have [a public \"Transport API\"](https://www.python-httpx.org/advanced/transports/#custom-transports), which you can use to implement custom transport implementations against. This formalises and replaces our previously private \"Dispatch API\".\n\n### Changed\n\n* Use `httpcore` for underlying HTTP transport. Drop `urllib3` requirement. (Pull #804, #967)\n* Rename pool limit options from `soft_limit`/`hard_limit` to `max_keepalive`/`max_connections`. (Pull #968)\n* The previous private \"Dispatch API\" has now been promoted to a public \"Transport API\". When customizing the transport use `transport=...`. The `ASGIDispatch` and `WSGIDispatch` class naming is deprecated in favour of `ASGITransport` and `WSGITransport`. (Pull #963)\n\n### Added\n\n* Added `URLLib3Transport` class for optional `urllib3` transport support. (Pull #804, #963)\n* Streaming multipart uploads. (Pull #857)\n* Logging via HTTPCORE_LOG_LEVEL and HTTPX_LOG_LEVEL environment variables\nand TRACE level logging. (Pull encode/httpcore#79)\n\n### Fixed\n\n* Performance improvement in brotli decoder. (Pull #906)\n* Proper warning level of deprecation notice in `Response.stream` and `Response.raw`. (Pull #908)\n* Fix support for generator based WSGI apps. (Pull #887)\n* Reuse of connections on HTTP/2 in close concurrency situations. (Pull encode/httpcore#81)\n* Honor HTTP/2 max concurrent streams settings (Pull encode/httpcore#89, encode/httpcore#90)\n* Fix bytes support in multipart uploads. (Pull #974)\n* Improve typing support for `files=...`. (Pull #976)\n\n### Removed\n\n* Dropped support for `Client(uds=...)` (Pull #804)\n\n## 0.13.0.dev2 (May 12th, 2020)\n\nThe 0.13.0.dev2 is a *pre-release* version. To install it, use `pip install httpx --pre`.\n\n### Added\n\n* Logging via HTTPCORE_LOG_LEVEL and HTTPX_LOG_LEVEL environment variables\nand TRACE level logging. (HTTPCore Pull #79)\n\n### Fixed\n\n* Reuse of connections on HTTP/2 in close concurrency situations. (HTTPCore Pull #81)\n* When using an `app=<ASGI app>` observe neater disconnect behaviour instead of sending empty body messages. (Pull #919)\n\n## 0.13.0.dev1 (May 6th, 2020)\n\nThe 0.13.0.dev1 is a *pre-release* version. To install it, use `pip install httpx --pre`.\n\n### Fixed\n\n* Passing `http2` flag to proxy dispatchers. (Pull #934)\n* Use [`httpcore` v0.8.3](https://github.com/encode/httpcore/releases/tag/0.8.3)\nwhich addresses problems in handling of headers when using proxies.\n\n## 0.13.0.dev0 (April 30th, 2020)\n\nThe 0.13.0.dev0 is a *pre-release* version. To install it, use `pip install httpx --pre`.\n\nThis release switches to `httpcore` for all the internal networking, which means:\n\n* We're using the same codebase for both our sync and async clients.\n* HTTP/2 support is now available with the sync client.\n* We no longer have a `urllib3` dependency for our sync client, although there is still an *optional* `URLLib3Dispatcher` class.\n\nIt also means we've had to remove our UDS support, since maintaining that would have meant having to push back our work towards a 1.0 release, which isn't a trade-off we wanted to make.\n\n### Changed\n\n* Use `httpcore` for underlying HTTP transport. Drop `urllib3` requirement. (Pull #804)\n\n### Added\n\n* Added `URLLib3Dispatcher` class for optional `urllib3` transport support. (Pull #804)\n* Streaming multipart uploads. (Pull #857)\n\n### Fixed\n\n* Performance improvement in brotli decoder. (Pull #906)\n* Proper warning level of deprecation notice in `Response.stream` and `Response.raw`. (Pull #908)\n* Fix support for generator based WSGI apps. (Pull #887)\n\n### Removed\n\n* Dropped support for `Client(uds=...)` (Pull #804)\n\n---\n\n## 0.12.1 (March 19th, 2020)\n\n### Fixed\n\n* Resolved packaging issue, where additional files were being included.\n\n## 0.12.0 (March 9th, 2020)\n\nThe 0.12 release tightens up the API expectations for `httpx` by switching to private module names to enforce better clarity around public API.\n\nAll imports of `httpx` should import from the top-level package only, such as `from httpx import Request`, rather than importing from privately namespaced modules such as `from httpx._models import Request`.\n\n### Added\n\n* Support making response body available to auth classes with `.requires_response_body`. (Pull #803)\n* Export `NetworkError` exception. (Pull #814)\n* Add support for `NO_PROXY` environment variable. (Pull #835)\n\n### Changed\n\n* Switched to private module names. (Pull #785)\n* Drop redirect looping detection and the `RedirectLoop` exception, instead using `TooManyRedirects`. (Pull #819)\n* Drop `backend=...` parameter on `AsyncClient`, in favour of always autodetecting `trio`/`asyncio`. (Pull #791)\n\n### Fixed\n\n* Support basic auth credentials in proxy URLs. (Pull #780)\n* Fix `httpx.Proxy(url, mode=\"FORWARD_ONLY\")` configuration. (Pull #788)\n* Fallback to setting headers as UTF-8 if no encoding is specified. (Pull #820)\n* Close proxy dispatches classes on client close. (Pull #826)\n* Support custom `cert` parameters even if `verify=False`. (Pull #796)\n* Don't support invalid dict-of-dicts form data in `data=...`. (Pull #811)\n\n---\n\n## 0.11.1 (January 17th, 2020)\n\n### Fixed\n\n* Fixed usage of `proxies=...` on `Client()`. (Pull #763)\n* Support both `zlib` and `deflate` style encodings on `Content-Encoding: deflate`. (Pull #758)\n* Fix for streaming a redirect response body with `allow_redirects=False`. (Pull #766)\n* Handle redirect with malformed Location headers missing host. (Pull #774)\n\n## 0.11.0 (January 9th, 2020)\n\nThe 0.11 release reintroduces our sync support, so that `httpx` now supports both a standard thread-concurrency API, and an async API.\n\nExisting async `httpx` users that are upgrading to 0.11 should ensure that:\n\n* Async codebases should always use a client instance to make requests, instead of the top-level API.\n* The async client is named as `httpx.AsyncClient()`, instead of `httpx.Client()`.\n* When instantiating proxy configurations use the `httpx.Proxy()` class, instead of the previous `httpx.HTTPProxy()`. This new configuration class works for configuring both sync and async clients.\n\nWe believe the API is now pretty much stable, and are aiming for a 1.0 release sometime on or before April 2020.\n\n### Changed\n\n- Top level API such as `httpx.get(url, ...)`, `httpx.post(url, ...)`, `httpx.request(method, url, ...)` becomes synchronous.\n- Added `httpx.Client()` for synchronous clients, with `httpx.AsyncClient` being used for async clients.\n- Switched to `proxies=httpx.Proxy(...)` for proxy configuration.\n- Network connection errors are wrapped in `httpx.NetworkError`, rather than exposing lower-level exception types directly.\n\n### Removed\n\n- The `request.url.origin` property and `httpx.Origin` class are no longer available.\n- The per-request `cert`, `verify`, and `trust_env` arguments are escalated from raising errors if used, to no longer being available. These arguments should be used on a per-client instance instead, or in the top-level API.\n- The `stream` argument has escalated from raising an error when used, to no longer being available. Use the `client.stream(...)` or `httpx.stream()` streaming API instead.\n\n### Fixed\n\n- Redirect loop detection matches against `(method, url)` rather than `url`. (Pull #734)\n\n---\n\n## 0.10.1 (December 31st, 2019)\n\n### Fixed\n\n- Fix issue with concurrent connection acquisition. (Pull #700)\n- Fix write error on closing HTTP/2 connections. (Pull #699)\n\n## 0.10.0 (December 29th, 2019)\n\nThe 0.10.0 release makes some changes that will allow us to support both sync and async interfaces.\n\nIn particular with streaming responses the `response.read()` method becomes `response.aread()`, and the `response.close()` method becomes `response.aclose()`.\n\nIf following redirects explicitly the `response.next()` method becomes `response.anext()`.\n\n### Fixed\n\n- End HTTP/2 streams immediately on no-body requests, rather than sending an empty body message. (Pull #682)\n- Improve typing for `Response.request`: switch from `Optional[Request]` to `Request`. (Pull #666)\n- `Response.elapsed` now reflects the entire download time. (Pull #687, #692)\n\n### Changed\n\n- Added `AsyncClient` as a synonym for `Client`. (Pull #680)\n- Switch to `response.aread()` for conditionally reading streaming responses. (Pull #674)\n- Switch to `response.aclose()` and `client.aclose()` for explicit closing. (Pull #674, #675)\n- Switch to `response.anext()` for resolving the next redirect response. (Pull #676)\n\n### Removed\n\n- When using a client instance, the per-request usage of `verify`, `cert`, and `trust_env` have now escalated from raising a warning to raising an error. You should set these arguments on the client instead. (Pull #617)\n- Removed the undocumented `request.read()`, since end users should not require it.\n\n---\n\n## 0.9.5 (December 20th, 2019)\n\n### Fixed\n\n- Fix Host header and HSTS rewrites when an explicit `:80` port is included in URL. (Pull #649)\n- Query Params on the URL string are merged with any `params=...` argument. (Pull #653)\n- More robust behavior when closing connections. (Pull #640)\n- More robust behavior when handling HTTP/2 headers with trailing whitespace. (Pull #637)\n- Allow any explicit `Content-Type` header to take precedence over the encoding default. (Pull #633)\n\n## 0.9.4 (December 12th, 2019)\n\n### Fixed\n\n- Added expiry to Keep-Alive connections, resolving issues with acquiring connections. (Pull #627)\n- Increased flow control windows on HTTP/2, resolving download speed issues. (Pull #629)\n\n## 0.9.3 (December 7th, 2019)\n\n### Fixed\n\n- Fixed HTTP/2 with autodetection backend. (Pull #614)\n\n## 0.9.2 (December 7th, 2019)\n\n* Released due to packaging build artifact.\n\n## 0.9.1 (December 6th, 2019)\n\n* Released due to packaging build artifact.\n\n## 0.9.0 (December 6th, 2019)\n\nThe 0.9 releases brings some major new features, including:\n\n* A new streaming API.\n* Autodetection of either asyncio or trio.\n* Nicer timeout configuration.\n* HTTP/2 support off by default, but can be enabled.\n\nWe've also removed all private types from the top-level package export.\n\nIn order to ensure you are only ever working with public API you should make\nsure to only import the top-level package eg. `import httpx`, rather than\nimporting modules within the package.\n\n### Added\n\n- Added concurrency backend autodetection. (Pull #585)\n- Added `Client(backend='trio')` and `Client(backend='asyncio')` API. (Pull #585)\n- Added `response.stream_lines()` API. (Pull #575)\n- Added `response.is_error` API. (Pull #574)\n- Added support for `timeout=Timeout(5.0, connect_timeout=60.0)` styles. (Pull #593)\n\n### Fixed\n\n- Requests or Clients with `timeout=None` now correctly always disable timeouts. (Pull #592)\n- Request 'Authorization' headers now have priority over `.netrc` authentication info. (Commit 095b691)\n- Files without a filename no longer set a Content-Type in multipart data. (Commit ed94950)\n\n### Changed\n\n- Added `httpx.stream()` API. Using `stream=True` now results in a warning. (Pull #600, #610)\n- HTTP/2 support is switched to \"off by default\", but can be enabled explicitly. (Pull #584)\n- Switched to `Client(http2=True)` API from `Client(http_versions=[\"HTTP/1.1\", \"HTTP/2\"])`. (Pull #586)\n- Removed all private types from the top-level package export. (Pull #608)\n- The SSL configuration settings of `verify`, `cert`, and `trust_env` now raise warnings if used per-request when using a Client instance. They should always be set on the Client instance itself. (Pull #597)\n- Use plain strings \"TUNNEL_ONLY\" or \"FORWARD_ONLY\" on the HTTPProxy `proxy_mode` argument. The `HTTPProxyMode` enum still exists, but its usage will raise warnings. (#610)\n- Pool timeouts are now on the timeout configuration, not the pool limits configuration. (Pull #563)\n- The timeout configuration is now named `httpx.Timeout(...)`, not `httpx.TimeoutConfig(...)`. The old version currently remains as a synonym for backwards compatibility.  (Pull #591)\n\n---\n\n## 0.8.0 (November 27, 2019)\n\n### Removed\n\n- The synchronous API has been removed, in order to allow us to fundamentally change how we approach supporting both sync and async variants. (See #588 for more details.)\n\n---\n\n## 0.7.8 (November 17, 2019)\n\n### Added\n\n- Add support for proxy tunnels for Python 3.6 + asyncio. (Pull #521)\n\n## 0.7.7 (November 15, 2019)\n\n### Fixed\n\n- Resolve an issue with cookies behavior on redirect requests. (Pull #529)\n\n### Added\n\n- Add request/response DEBUG logs. (Pull #502)\n- Use TRACE log level for low level info. (Pull #500)\n\n## 0.7.6 (November 2, 2019)\n\n### Removed\n\n- Drop `proxies` parameter from the high-level API. (Pull #485)\n\n### Fixed\n\n- Tweak multipart files: omit null filenames, add support for `str` file contents. (Pull #482)\n- Cache NETRC authentication per-client. (Pull #400)\n- Rely on `getproxies` for all proxy environment variables. (Pull #470)\n- Wait for the `asyncio` stream to close when closing a connection. (Pull #494)\n\n## 0.7.5 (October 10, 2019)\n\n### Added\n\n- Allow lists of values to be passed to `params`. (Pull #386)\n- `ASGIDispatch`, `WSGIDispatch` are now available in the `httpx.dispatch` namespace. (Pull #407)\n- `HTTPError` is now available in the `httpx` namespace.  (Pull #421)\n- Add support for `start_tls()` to the Trio concurrency backend. (Pull #467)\n\n### Fixed\n\n- Username and password are no longer included in the `Host` header when basic authentication\n  credentials are supplied via the URL. (Pull #417)\n\n### Removed\n\n- The `.delete()` function no longer has `json`, `data`, or `files` parameters\n  to match the expected semantics of the `DELETE` method. (Pull #408)\n- Removed the `trio` extra. Trio support is detected automatically. (Pull #390)\n\n## 0.7.4 (September 25, 2019)\n\n### Added\n\n- Add Trio concurrency backend. (Pull #276)\n- Add `params` parameter to `Client` for setting default query parameters. (Pull #372)\n- Add support for `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables. (Pull #307)\n- Add debug logging to calls into ASGI apps. (Pull #371)\n- Add debug logging to SSL configuration. (Pull #378)\n\n### Fixed\n\n- Fix a bug when using `Client` without timeouts in Python 3.6. (Pull #383)\n- Propagate `Client` configuration to HTTP proxies. (Pull #377)\n\n## 0.7.3 (September 20, 2019)\n\n### Added\n\n- HTTP Proxy support. (Pulls #259, #353)\n- Add Digest authentication. (Pull #332)\n- Add `.build_request()` method to `Client` and `AsyncClient`. (Pull #319)\n- Add `.elapsed` property on responses. (Pull #351)\n- Add support for `SSLKEYLOGFILE` in Python 3.8b4+. (Pull #301)\n\n### Removed\n\n- Drop NPN support for HTTP version negotiation. (Pull #314)\n\n### Fixed\n\n- Fix distribution of type annotations for mypy (Pull #361).\n- Set `Host` header when redirecting cross-origin. (Pull #321)\n- Drop `Content-Length` headers on `GET` redirects. (Pull #310)\n- Raise `KeyError` if header isn't found in `Headers`. (Pull #324)\n- Raise `NotRedirectResponse` in `response.next()` if there is no redirection to perform. (Pull #297)\n- Fix bug in calculating the HTTP/2 maximum frame size. (Pull #153)\n\n## 0.7.2 (August 28, 2019)\n\n- Enforce using `httpx.AsyncioBackend` for the synchronous client. (Pull #232)\n- `httpx.ConnectionPool` will properly release a dropped connection. (Pull #230)\n- Remove the `raise_app_exceptions` argument from `Client`. (Pull #238)\n- `DecodeError` will no longer be raised for an empty body encoded with Brotli. (Pull #237)\n- Added `http_versions` parameter to `Client`. (Pull #250)\n- Only use HTTP/1.1 on short-lived connections like `httpx.get()`. (Pull #284)\n- Convert `Client.cookies` and `Client.headers` when set as a property. (Pull #274)\n- Setting `HTTPX_DEBUG=1` enables debug logging on all requests. (Pull #277)\n\n## 0.7.1 (August 18, 2019)\n\n- Include files with source distribution to be installable. (Pull #233)\n\n## 0.7.0 (August 17, 2019)\n\n- Add the `trust_env` property to `BaseClient`. (Pull #187)\n- Add the `links` property to `BaseResponse`. (Pull #211)\n- Accept `ssl.SSLContext` instances into `SSLConfig(verify=...)`. (Pull #215)\n- Add `Response.stream_text()` with incremental encoding detection. (Pull #183)\n- Properly updated the `Host` header when a redirect changes the origin. (Pull #199)\n- Ignore invalid `Content-Encoding` headers. (Pull #196)\n- Use `~/.netrc` and `~/_netrc` files by default when `trust_env=True`. (Pull #189)\n- Create exception base class `HTTPError` with `request` and `response` properties. (Pull #162)\n- Add HSTS preload list checking within `BaseClient` to upgrade HTTP URLs to HTTPS. (Pull #184)\n- Switch IDNA encoding from IDNA 2003 to IDNA 2008. (Pull #161)\n- Expose base classes for alternate concurrency backends. (Pull #178)\n- Improve Multipart parameter encoding. (Pull #167)\n- Add the `headers` property to `BaseClient`. (Pull #159)\n- Add support for Google's `brotli` library. (Pull #156)\n- Remove deprecated TLS versions (TLSv1 and TLSv1.1) from default `SSLConfig`. (Pull #155)\n- Fix `URL.join(...)` to work similarly to RFC 3986 URL joining. (Pull #144)\n\n---\n\n## 0.6.8 (July 25, 2019)\n\n- Check for disconnections when searching for an available\n  connection in `ConnectionPool.keepalive_connections` (Pull #145)\n- Allow string comparison for `URL` objects (Pull #139)\n- Add HTTP status codes 418 and 451 (Pull #135)\n- Add support for client certificate passwords (Pull #118)\n- Enable post-handshake client cert authentication for TLSv1.3 (Pull #118)\n- Disable using `commonName` for hostname checking for OpenSSL 1.1.0+ (Pull #118)\n- Detect encoding for `Response.json()` (Pull #116)\n\n## 0.6.7 (July 8, 2019)\n\n- Check for connection aliveness on re-acquisition (Pull #111)\n\n## 0.6.6 (July 3, 2019)\n\n- Improve `USER_AGENT` (Pull #110)\n- Add `Connection: keep-alive` by default to HTTP/1.1 connections. (Pull #110)\n\n## 0.6.5 (June 27, 2019)\n\n- Include `Host` header by default. (Pull #109)\n- Improve HTTP protocol detection. (Pull #107)\n\n## 0.6.4 (June 25, 2019)\n\n- Implement read and write timeouts (Pull #104)\n\n## 0.6.3 (June 24, 2019)\n\n- Handle early connection closes (Pull #103)\n\n## 0.6.2 (June 23, 2019)\n\n- Use urllib3's `DEFAULT_CIPHERS` for the `SSLConfig` object. (Pull #100)\n\n## 0.6.1 (June 21, 2019)\n\n- Add support for setting a `base_url` on the `Client`.\n\n## 0.6.0 (June 21, 2019)\n\n- Honor `local_flow_control_window` for HTTP/2 connections (Pull #98)\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright © 2019, [Encode OSS Ltd](https://www.encode.io/).\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://www.python-httpx.org/\"><img width=\"350\" height=\"208\" src=\"https://raw.githubusercontent.com/encode/httpx/master/docs/img/butterfly.png\" alt='HTTPX'></a>\n</p>\n\n<p align=\"center\"><strong>HTTPX</strong> <em>- A next-generation HTTP client for Python.</em></p>\n\n<p align=\"center\">\n<a href=\"https://github.com/encode/httpx/actions\">\n    <img src=\"https://github.com/encode/httpx/workflows/Test%20Suite/badge.svg\" alt=\"Test Suite\">\n</a>\n<a href=\"https://pypi.org/project/httpx/\">\n    <img src=\"https://badge.fury.io/py/httpx.svg\" alt=\"Package version\">\n</a>\n</p>\n\nHTTPX is a fully featured HTTP client library for Python 3. It includes **an integrated command line client**, has support for both **HTTP/1.1 and HTTP/2**, and provides both **sync and async APIs**.\n\n---\n\nInstall HTTPX using pip:\n\n```shell\n$ pip install httpx\n```\n\nNow, let's get started:\n\n```pycon\n>>> import httpx\n>>> r = httpx.get('https://www.example.org/')\n>>> r\n<Response [200 OK]>\n>>> r.status_code\n200\n>>> r.headers['content-type']\n'text/html; charset=UTF-8'\n>>> r.text\n'<!doctype html>\\n<html>\\n<head>\\n<title>Example Domain</title>...'\n```\n\nOr, using the command-line client.\n\n```shell\n$ pip install 'httpx[cli]'  # The command line client is an optional dependency.\n```\n\nWhich now allows us to use HTTPX directly from the command-line...\n\n<p align=\"center\">\n  <img width=\"700\" src=\"docs/img/httpx-help.png\" alt='httpx --help'>\n</p>\n\nSending a request...\n\n<p align=\"center\">\n  <img width=\"700\" src=\"docs/img/httpx-request.png\" alt='httpx http://httpbin.org/json'>\n</p>\n\n## Features\n\nHTTPX builds on the well-established usability of `requests`, and gives you:\n\n* A broadly [requests-compatible API](https://www.python-httpx.org/compatibility/).\n* An integrated command-line client.\n* HTTP/1.1 [and HTTP/2 support](https://www.python-httpx.org/http2/).\n* Standard synchronous interface, but with [async support if you need it](https://www.python-httpx.org/async/).\n* Ability to make requests directly to [WSGI applications](https://www.python-httpx.org/advanced/transports/#wsgi-transport) or [ASGI applications](https://www.python-httpx.org/advanced/transports/#asgi-transport).\n* Strict timeouts everywhere.\n* Fully type annotated.\n* 100% test coverage.\n\nPlus all the standard features of `requests`...\n\n* International Domains and URLs\n* Keep-Alive & Connection Pooling\n* Sessions with Cookie Persistence\n* Browser-style SSL Verification\n* Basic/Digest Authentication\n* Elegant Key/Value Cookies\n* Automatic Decompression\n* Automatic Content Decoding\n* Unicode Response Bodies\n* Multipart File Uploads\n* HTTP(S) Proxy Support\n* Connection Timeouts\n* Streaming Downloads\n* .netrc Support\n* Chunked Requests\n\n## Installation\n\nInstall with pip:\n\n```shell\n$ pip install httpx\n```\n\nOr, to include the optional HTTP/2 support, use:\n\n```shell\n$ pip install httpx[http2]\n```\n\nHTTPX requires Python 3.9+.\n\n## Documentation\n\nProject documentation is available at [https://www.python-httpx.org/](https://www.python-httpx.org/).\n\nFor a run-through of all the basics, head over to the [QuickStart](https://www.python-httpx.org/quickstart/).\n\nFor more advanced topics, see the [Advanced Usage](https://www.python-httpx.org/advanced/) section, the [async support](https://www.python-httpx.org/async/) section, or the [HTTP/2](https://www.python-httpx.org/http2/) section.\n\nThe [Developer Interface](https://www.python-httpx.org/api/) provides a comprehensive API reference.\n\nTo find out about tools that integrate with HTTPX, see [Third Party Packages](https://www.python-httpx.org/third_party_packages/).\n\n## Contribute\n\nIf you want to contribute with HTTPX check out the [Contributing Guide](https://www.python-httpx.org/contributing/) to learn how to start.\n\n## Dependencies\n\nThe HTTPX project relies on these excellent libraries:\n\n* `httpcore` - The underlying transport implementation for `httpx`.\n  * `h11` - HTTP/1.1 support.\n* `certifi` - SSL certificates.\n* `idna` - Internationalized domain name support.\n* `sniffio` - Async library autodetection.\n\nAs well as these optional installs:\n\n* `h2` - HTTP/2 support. *(Optional, with `httpx[http2]`)*\n* `socksio` - SOCKS proxy support. *(Optional, with `httpx[socks]`)*\n* `rich` - Rich terminal support. *(Optional, with `httpx[cli]`)*\n* `click` - Command line client support. *(Optional, with `httpx[cli]`)*\n* `brotli` or `brotlicffi` - Decoding for \"brotli\" compressed responses. *(Optional, with `httpx[brotli]`)*\n* `zstandard` - Decoding for \"zstd\" compressed responses. *(Optional, with `httpx[zstd]`)*\n\nA huge amount of credit is due to `requests` for the API layout that\nmuch of this work follows, as well as to `urllib3` for plenty of design\ninspiration around the lower-level networking details.\n\n---\n\n<p align=\"center\"><i>HTTPX is <a href=\"https://github.com/encode/httpx/blob/master/LICENSE.md\">BSD licensed</a> code.<br/>Designed & crafted with care.</i><br/>&mdash; 🦋 &mdash;</p>\n"
  },
  {
    "path": "docs/CNAME",
    "content": "www.python-httpx.org\n"
  },
  {
    "path": "docs/advanced/authentication.md",
    "content": "Authentication can either be included on a per-request basis...\n\n```pycon\n>>> auth = httpx.BasicAuth(username=\"username\", password=\"secret\")\n>>> client = httpx.Client()\n>>> response = client.get(\"https://www.example.com/\", auth=auth)\n```\n\nOr configured on the client instance, ensuring that all outgoing requests will include authentication credentials...\n\n```pycon\n>>> auth = httpx.BasicAuth(username=\"username\", password=\"secret\")\n>>> client = httpx.Client(auth=auth)\n>>> response = client.get(\"https://www.example.com/\")\n```\n\n## Basic authentication\n\nHTTP basic authentication is an unencrypted authentication scheme that uses a simple encoding of the username and password in the request `Authorization` header. Since it is unencrypted it should typically only be used over `https`, although this is not strictly enforced.\n\n```pycon\n>>> auth = httpx.BasicAuth(username=\"finley\", password=\"secret\")\n>>> client = httpx.Client(auth=auth)\n>>> response = client.get(\"https://httpbin.org/basic-auth/finley/secret\")\n>>> response\n<Response [200 OK]>\n```\n\n## Digest authentication\n\nHTTP digest authentication is a challenge-response authentication scheme. Unlike basic authentication it provides encryption, and can be used over unencrypted `http` connections. It requires an additional round-trip in order to negotiate the authentication. \n\n```pycon\n>>> auth = httpx.DigestAuth(username=\"olivia\", password=\"secret\")\n>>> client = httpx.Client(auth=auth)\n>>> response = client.get(\"https://httpbin.org/digest-auth/auth/olivia/secret\")\n>>> response\n<Response [200 OK]>\n>>> response.history\n[<Response [401 UNAUTHORIZED]>]\n```\n\n## NetRC authentication\n\nHTTPX can be configured to use [a `.netrc` config file](https://everything.curl.dev/usingcurl/netrc) for authentication.\n\nThe `.netrc` config file allows authentication credentials to be associated with specified hosts. When a request is made to a host that is found in the netrc file, the username and password will be included using HTTP basic authentication.\n\nExample `.netrc` file:\n\n```\nmachine example.org\nlogin example-username\npassword example-password\n\nmachine python-httpx.org\nlogin other-username\npassword other-password\n```\n\nSome examples of configuring `.netrc` authentication with `httpx`.\n\nUse the default `.netrc` file in the users home directory:\n\n```pycon\n>>> auth = httpx.NetRCAuth()\n>>> client = httpx.Client(auth=auth)\n```\n\nUse an explicit path to a `.netrc` file:\n\n```pycon\n>>> auth = httpx.NetRCAuth(file=\"/path/to/.netrc\")\n>>> client = httpx.Client(auth=auth)\n```\n\nUse the `NETRC` environment variable to configure a path to the `.netrc` file,\nor fallback to the default.\n\n```pycon\n>>> auth = httpx.NetRCAuth(file=os.environ.get(\"NETRC\"))\n>>> client = httpx.Client(auth=auth)\n```\n\nThe `NetRCAuth()` class uses [the `netrc.netrc()` function from the Python standard library](https://docs.python.org/3/library/netrc.html). See the documentation there for more details on exceptions that may be raised if the `.netrc` file is not found, or cannot be parsed.\n\n## Custom authentication schemes\n\nWhen issuing requests or instantiating a client, the `auth` argument can be used to pass an authentication scheme to use. The `auth` argument may be one of the following...\n\n* A two-tuple of `username`/`password`, to be used with basic authentication.\n* An instance of `httpx.BasicAuth()`, `httpx.DigestAuth()`, or `httpx.NetRCAuth()`.\n* A callable, accepting a request and returning an authenticated request instance.\n* An instance of subclasses of `httpx.Auth`.\n\nThe most involved of these is the last, which allows you to create authentication flows involving one or more requests. A subclass of `httpx.Auth` should implement `def auth_flow(request)`, and yield any requests that need to be made...\n\n```python\nclass MyCustomAuth(httpx.Auth):\n    def __init__(self, token):\n        self.token = token\n\n    def auth_flow(self, request):\n        # Send the request, with a custom `X-Authentication` header.\n        request.headers['X-Authentication'] = self.token\n        yield request\n```\n\nIf the auth flow requires more than one request, you can issue multiple yields, and obtain the response in each case...\n\n```python\nclass MyCustomAuth(httpx.Auth):\n    def __init__(self, token):\n        self.token = token\n\n    def auth_flow(self, request):\n      response = yield request\n      if response.status_code == 401:\n          # If the server issues a 401 response then resend the request,\n          # with a custom `X-Authentication` header.\n          request.headers['X-Authentication'] = self.token\n          yield request\n```\n\nCustom authentication classes are designed to not perform any I/O, so that they may be used with both sync and async client instances. If you are implementing an authentication scheme that requires the request body, then you need to indicate this on the class using a `requires_request_body` property.\n\nYou will then be able to access `request.content` inside the `.auth_flow()` method.\n\n```python\nclass MyCustomAuth(httpx.Auth):\n    requires_request_body = True\n\n    def __init__(self, token):\n        self.token = token\n\n    def auth_flow(self, request):\n      response = yield request\n      if response.status_code == 401:\n          # If the server issues a 401 response then resend the request,\n          # with a custom `X-Authentication` header.\n          request.headers['X-Authentication'] = self.sign_request(...)\n          yield request\n\n    def sign_request(self, request):\n        # Create a request signature, based on `request.method`, `request.url`,\n        # `request.headers`, and `request.content`.\n        ...\n```\n\nSimilarly, if you are implementing a scheme that requires access to the response body, then use the `requires_response_body` property.   You will then be able to access response body properties and methods such as `response.content`, `response.text`, `response.json()`, etc.\n\n```python\nclass MyCustomAuth(httpx.Auth):\n    requires_response_body = True\n\n    def __init__(self, access_token, refresh_token, refresh_url):\n        self.access_token = access_token\n        self.refresh_token = refresh_token\n        self.refresh_url = refresh_url\n\n    def auth_flow(self, request):\n        request.headers[\"X-Authentication\"] = self.access_token\n        response = yield request\n\n        if response.status_code == 401:\n            # If the server issues a 401 response, then issue a request to\n            # refresh tokens, and resend the request.\n            refresh_response = yield self.build_refresh_request()\n            self.update_tokens(refresh_response)\n\n            request.headers[\"X-Authentication\"] = self.access_token\n            yield request\n\n    def build_refresh_request(self):\n        # Return an `httpx.Request` for refreshing tokens.\n        ...\n\n    def update_tokens(self, response):\n        # Update the `.access_token` and `.refresh_token` tokens\n        # based on a refresh response.\n        data = response.json()\n        ...\n```\n\nIf you _do_ need to perform I/O other than HTTP requests, such as accessing a disk-based cache, or you need to use concurrency primitives, such as locks, then you should override `.sync_auth_flow()` and `.async_auth_flow()` (instead of `.auth_flow()`). The former will be used by `httpx.Client`, while the latter will be used by `httpx.AsyncClient`.\n\n```python\nimport asyncio\nimport threading\nimport httpx\n\n\nclass MyCustomAuth(httpx.Auth):\n    def __init__(self):\n        self._sync_lock = threading.RLock()\n        self._async_lock = asyncio.Lock()\n\n    def sync_get_token(self):\n        with self._sync_lock:\n            ...\n\n    def sync_auth_flow(self, request):\n        token = self.sync_get_token()\n        request.headers[\"Authorization\"] = f\"Token {token}\"\n        yield request\n\n    async def async_get_token(self):\n        async with self._async_lock:\n            ...\n\n    async def async_auth_flow(self, request):\n        token = await self.async_get_token()\n        request.headers[\"Authorization\"] = f\"Token {token}\"\n        yield request\n```\n\nIf you only want to support one of the two methods, then you should still override it, but raise an explicit `RuntimeError`.\n\n```python\nimport httpx\nimport sync_only_library\n\n\nclass MyCustomAuth(httpx.Auth):\n    def sync_auth_flow(self, request):\n        token = sync_only_library.get_token(...)\n        request.headers[\"Authorization\"] = f\"Token {token}\"\n        yield request\n\n    async def async_auth_flow(self, request):\n        raise RuntimeError(\"Cannot use a sync authentication class with httpx.AsyncClient\")\n```"
  },
  {
    "path": "docs/advanced/clients.md",
    "content": "!!! hint\n    If you are coming from Requests, `httpx.Client()` is what you can use instead of `requests.Session()`.\n\n## Why use a Client?\n\n!!! note \"TL;DR\"\n    If you do anything more than experimentation, one-off scripts, or prototypes, then you should use a `Client` instance.\n\n**More efficient usage of network resources**\n\nWhen you make requests using the top-level API as documented in the [Quickstart](../quickstart.md) guide, HTTPX has to establish a new connection _for every single request_ (connections are not reused). As the number of requests to a host increases, this quickly becomes inefficient.\n\nOn the other hand, a `Client` instance uses [HTTP connection pooling](https://en.wikipedia.org/wiki/HTTP_persistent_connection). This means that when you make several requests to the same host, the `Client` will reuse the underlying TCP connection, instead of recreating one for every single request.\n\nThis can bring **significant performance improvements** compared to using the top-level API, including:\n\n- Reduced latency across requests (no handshaking).\n- Reduced CPU usage and round-trips.\n- Reduced network congestion.\n\n**Extra features**\n\n`Client` instances also support features that aren't available at the top-level API, such as:\n\n- Cookie persistence across requests.\n- Applying configuration across all outgoing requests.\n- Sending requests through HTTP proxies.\n- Using [HTTP/2](../http2.md).\n\nThe other sections on this page go into further detail about what you can do with a `Client` instance.\n\n## Usage\n\nThe recommended way to use a `Client` is as a context manager. This will ensure that connections are properly cleaned up when leaving the `with` block:\n\n```python\nwith httpx.Client() as client:\n    ...\n```\n\nAlternatively, you can explicitly close the connection pool without block-usage using `.close()`:\n\n```python\nclient = httpx.Client()\ntry:\n    ...\nfinally:\n    client.close()\n```\n\n## Making requests\n\nOnce you have a `Client`, you can send requests using `.get()`, `.post()`, etc. For example:\n\n```pycon\n>>> with httpx.Client() as client:\n...     r = client.get('https://example.com')\n...\n>>> r\n<Response [200 OK]>\n```\n\nThese methods accept the same arguments as `httpx.get()`, `httpx.post()`, etc. This means that all features documented in the [Quickstart](../quickstart.md) guide are also available at the client level.\n\nFor example, to send a request with custom headers:\n\n```pycon\n>>> with httpx.Client() as client:\n...     headers = {'X-Custom': 'value'}\n...     r = client.get('https://example.com', headers=headers)\n...\n>>> r.request.headers['X-Custom']\n'value'\n```\n\n## Sharing configuration across requests\n\nClients allow you to apply configuration to all outgoing requests by passing parameters to the `Client` constructor.\n\nFor example, to apply a set of custom headers _on every request_:\n\n```pycon\n>>> url = 'http://httpbin.org/headers'\n>>> headers = {'user-agent': 'my-app/0.0.1'}\n>>> with httpx.Client(headers=headers) as client:\n...     r = client.get(url)\n...\n>>> r.json()['headers']['User-Agent']\n'my-app/0.0.1'\n```\n\n## Merging of configuration\n\nWhen a configuration option is provided at both the client-level and request-level, one of two things can happen:\n\n- For headers, query parameters and cookies, the values are combined together. For example:\n\n```pycon\n>>> headers = {'X-Auth': 'from-client'}\n>>> params = {'client_id': 'client1'}\n>>> with httpx.Client(headers=headers, params=params) as client:\n...     headers = {'X-Custom': 'from-request'}\n...     params = {'request_id': 'request1'}\n...     r = client.get('https://example.com', headers=headers, params=params)\n...\n>>> r.request.url\nURL('https://example.com?client_id=client1&request_id=request1')\n>>> r.request.headers['X-Auth']\n'from-client'\n>>> r.request.headers['X-Custom']\n'from-request'\n```\n\n- For all other parameters, the request-level value takes priority. For example:\n\n```pycon\n>>> with httpx.Client(auth=('tom', 'mot123')) as client:\n...     r = client.get('https://example.com', auth=('alice', 'ecila123'))\n...\n>>> _, _, auth = r.request.headers['Authorization'].partition(' ')\n>>> import base64\n>>> base64.b64decode(auth)\nb'alice:ecila123'\n```\n\nIf you need finer-grained control on the merging of client-level and request-level parameters, see [Request instances](#request-instances).\n\n## Other Client-only configuration options\n\nAdditionally, `Client` accepts some configuration options that aren't available at the request level.\n\nFor example, `base_url` allows you to prepend an URL to all outgoing requests:\n\n```pycon\n>>> with httpx.Client(base_url='http://httpbin.org') as client:\n...     r = client.get('/headers')\n...\n>>> r.request.url\nURL('http://httpbin.org/headers')\n```\n\nFor a list of all available client parameters, see the [`Client`](../api.md#client) API reference.\n\n---\n\n## Request instances\n\nFor maximum control on what gets sent over the wire, HTTPX supports building explicit [`Request`](../api.md#request) instances:\n\n```python\nrequest = httpx.Request(\"GET\", \"https://example.com\")\n```\n\nTo dispatch a `Request` instance across to the network, create a [`Client` instance](#client-instances) and use `.send()`:\n\n```python\nwith httpx.Client() as client:\n    response = client.send(request)\n    ...\n```\n\nIf you need to mix client-level and request-level options in a way that is not supported by the default [Merging of parameters](#merging-of-parameters), you can use `.build_request()` and then make arbitrary modifications to the `Request` instance. For example:\n\n```python\nheaders = {\"X-Api-Key\": \"...\", \"X-Client-ID\": \"ABC123\"}\n\nwith httpx.Client(headers=headers) as client:\n    request = client.build_request(\"GET\", \"https://api.example.com\")\n\n    print(request.headers[\"X-Client-ID\"])  # \"ABC123\"\n\n    # Don't send the API key for this particular request.\n    del request.headers[\"X-Api-Key\"]\n\n    response = client.send(request)\n    ...\n```\n\n## Monitoring download progress\n\nIf you need to monitor download progress of large responses, you can use response streaming and inspect the `response.num_bytes_downloaded` property.\n\nThis interface is required for properly determining download progress, because the total number of bytes returned by `response.content` or `response.iter_content()` will not always correspond with the raw content length of the response if HTTP response compression is being used.\n\nFor example, showing a progress bar using the [`tqdm`](https://github.com/tqdm/tqdm) library while a response is being downloaded could be done like this…\n\n```python\nimport tempfile\n\nimport httpx\nfrom tqdm import tqdm\n\nwith tempfile.NamedTemporaryFile() as download_file:\n    url = \"https://speed.hetzner.de/100MB.bin\"\n    with httpx.stream(\"GET\", url) as response:\n        total = int(response.headers[\"Content-Length\"])\n\n        with tqdm(total=total, unit_scale=True, unit_divisor=1024, unit=\"B\") as progress:\n            num_bytes_downloaded = response.num_bytes_downloaded\n            for chunk in response.iter_bytes():\n                download_file.write(chunk)\n                progress.update(response.num_bytes_downloaded - num_bytes_downloaded)\n                num_bytes_downloaded = response.num_bytes_downloaded\n```\n\n![tqdm progress bar](../img/tqdm-progress.gif)\n\nOr an alternate example, this time using the [`rich`](https://github.com/willmcgugan/rich) library…\n\n```python\nimport tempfile\nimport httpx\nimport rich.progress\n\nwith tempfile.NamedTemporaryFile() as download_file:\n    url = \"https://speed.hetzner.de/100MB.bin\"\n    with httpx.stream(\"GET\", url) as response:\n        total = int(response.headers[\"Content-Length\"])\n\n        with rich.progress.Progress(\n            \"[progress.percentage]{task.percentage:>3.0f}%\",\n            rich.progress.BarColumn(bar_width=None),\n            rich.progress.DownloadColumn(),\n            rich.progress.TransferSpeedColumn(),\n        ) as progress:\n            download_task = progress.add_task(\"Download\", total=total)\n            for chunk in response.iter_bytes():\n                download_file.write(chunk)\n                progress.update(download_task, completed=response.num_bytes_downloaded)\n```\n\n![rich progress bar](../img/rich-progress.gif)\n\n## Monitoring upload progress\n\nIf you need to monitor upload progress of large responses, you can use request content generator streaming.\n\nFor example, showing a progress bar using the [`tqdm`](https://github.com/tqdm/tqdm) library.\n\n```python\nimport io\nimport random\n\nimport httpx\nfrom tqdm import tqdm\n\n\ndef gen():\n    \"\"\"\n    this is a complete example with generated random bytes.\n    you can replace `io.BytesIO` with real file object.\n    \"\"\"\n    total = 32 * 1024 * 1024  # 32m\n    with tqdm(ascii=True, unit_scale=True, unit='B', unit_divisor=1024, total=total) as bar:\n        with io.BytesIO(random.randbytes(total)) as f:\n            while data := f.read(1024):\n                yield data\n                bar.update(len(data))\n\n\nhttpx.post(\"https://httpbin.org/post\", content=gen())\n```\n\n![tqdm progress bar](../img/tqdm-progress.gif)\n\n## Multipart file encoding\n\nAs mentioned in the [quickstart](../quickstart.md#sending-multipart-file-uploads)\nmultipart file encoding is available by passing a dictionary with the\nname of the payloads as keys and either tuple of elements or a file-like object or a string as values.\n\n```pycon\n>>> with open('report.xls', 'rb') as report_file:\n...     files = {'upload-file': ('report.xls', report_file, 'application/vnd.ms-excel')}\n...     r = httpx.post(\"https://httpbin.org/post\", files=files)\n>>> print(r.text)\n{\n  ...\n  \"files\": {\n    \"upload-file\": \"<... binary content ...>\"\n  },\n  ...\n}\n```\n\nMore specifically, if a tuple is used as a value, it must have between 2 and 3 elements:\n\n- The first element is an optional file name which can be set to `None`.\n- The second element may be a file-like object or a string which will be automatically\nencoded in UTF-8.\n- An optional third element can be used to specify the\n[MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_Types)\nof the file being uploaded. If not specified HTTPX will attempt to guess the MIME type based\non the file name, with unknown file extensions defaulting to \"application/octet-stream\".\nIf the file name is explicitly set to `None` then HTTPX will not include a content-type\nMIME header field.\n\n```pycon\n>>> files = {'upload-file': (None, 'text content', 'text/plain')}\n>>> r = httpx.post(\"https://httpbin.org/post\", files=files)\n>>> print(r.text)\n{\n  ...\n  \"files\": {},\n  \"form\": {\n    \"upload-file\": \"text-content\"\n  },\n  ...\n}\n```\n\n!!! tip\n    It is safe to upload large files this way. File uploads are streaming by default, meaning that only one chunk will be loaded into memory at a time.\n\n Non-file data fields can be included in the multipart form using by passing them to `data=...`.\n\nYou can also send multiple files in one go with a multiple file field form.\nTo do that, pass a list of `(field, <file>)` items instead of a dictionary, allowing you to pass multiple items with the same `field`.\nFor instance this request sends 2 files, `foo.png` and `bar.png` in one request on the `images` form field:\n\n```pycon\n>>> with open('foo.png', 'rb') as foo_file, open('bar.png', 'rb') as bar_file:\n...     files = [\n...         ('images', ('foo.png', foo_file, 'image/png')),\n...         ('images', ('bar.png', bar_file, 'image/png')),\n...     ]\n...     r = httpx.post(\"https://httpbin.org/post\", files=files)\n```\n"
  },
  {
    "path": "docs/advanced/event-hooks.md",
    "content": "HTTPX allows you to register \"event hooks\" with the client, that are called\nevery time a particular type of event takes place.\n\nThere are currently two event hooks:\n\n* `request` - Called after a request is fully prepared, but before it is sent to the network. Passed the `request` instance.\n* `response` - Called after the response has been fetched from the network, but before it is returned to the caller. Passed the `response` instance.\n\nThese allow you to install client-wide functionality such as logging, monitoring or tracing.\n\n```python\ndef log_request(request):\n    print(f\"Request event hook: {request.method} {request.url} - Waiting for response\")\n\ndef log_response(response):\n    request = response.request\n    print(f\"Response event hook: {request.method} {request.url} - Status {response.status_code}\")\n\nclient = httpx.Client(event_hooks={'request': [log_request], 'response': [log_response]})\n```\n\nYou can also use these hooks to install response processing code, such as this\nexample, which creates a client instance that always raises `httpx.HTTPStatusError`\non 4xx and 5xx responses.\n\n```python\ndef raise_on_4xx_5xx(response):\n    response.raise_for_status()\n\nclient = httpx.Client(event_hooks={'response': [raise_on_4xx_5xx]})\n```\n\n!!! note\n    Response event hooks are called before determining if the response body\n    should be read or not.\n\n    If you need access to the response body inside an event hook, you'll\n    need to call `response.read()`, or for AsyncClients, `response.aread()`.\n\nThe hooks are also allowed to modify `request` and `response` objects.\n\n```python\ndef add_timestamp(request):\n    request.headers['x-request-timestamp'] = datetime.now(tz=datetime.utc).isoformat()\n\nclient = httpx.Client(event_hooks={'request': [add_timestamp]})\n```\n\nEvent hooks must always be set as a **list of callables**, and you may register\nmultiple event hooks for each type of event.\n\nAs well as being able to set event hooks on instantiating the client, there\nis also an `.event_hooks` property, that allows you to inspect and modify\nthe installed hooks.\n\n```python\nclient = httpx.Client()\nclient.event_hooks['request'] = [log_request]\nclient.event_hooks['response'] = [log_response, raise_on_4xx_5xx]\n```\n\n!!! note\n    If you are using HTTPX's async support, then you need to be aware that\n    hooks registered with `httpx.AsyncClient` MUST be async functions,\n    rather than plain functions.\n"
  },
  {
    "path": "docs/advanced/extensions.md",
    "content": "# Extensions\n\nRequest and response extensions provide a untyped space where additional information may be added.\n\nExtensions should be used for features that may not be available on all transports, and that do not fit neatly into [the simplified request/response model](https://www.encode.io/httpcore/extensions/) that the underlying `httpcore` package uses as its API.\n\nSeveral extensions are supported on the request:\n\n```python\n# Request timeouts actually implemented as an extension on\n# the request, ensuring that they are passed throughout the\n# entire call stack.\nclient = httpx.Client()\nresponse = client.get(\n    \"https://www.example.com\",\n    extensions={\"timeout\": {\"connect\": 5.0}}\n)\nresponse.request.extensions[\"timeout\"]\n{\"connect\": 5.0}\n```\n\nAnd on the response:\n\n```python\nclient = httpx.Client()\nresponse = client.get(\"https://www.example.com\")\nprint(response.extensions[\"http_version\"])  # b\"HTTP/1.1\"\n# Other server responses could have been\n# b\"HTTP/0.9\", b\"HTTP/1.0\", or b\"HTTP/1.1\"\n```\n\n## Request Extensions\n\n### `\"trace\"`\n\nThe trace extension allows a callback handler to be installed to monitor the internal\nflow of events within the underlying `httpcore` transport.\n\nThe simplest way to explain this is with an example:\n\n```python\nimport httpx\n\ndef log(event_name, info):\n    print(event_name, info)\n\nclient = httpx.Client()\nresponse = client.get(\"https://www.example.com/\", extensions={\"trace\": log})\n# connection.connect_tcp.started {'host': 'www.example.com', 'port': 443, 'local_address': None, 'timeout': None}\n# connection.connect_tcp.complete {'return_value': <httpcore.backends.sync.SyncStream object at 0x1093f94d0>}\n# connection.start_tls.started {'ssl_context': <ssl.SSLContext object at 0x1093ee750>, 'server_hostname': b'www.example.com', 'timeout': None}\n# connection.start_tls.complete {'return_value': <httpcore.backends.sync.SyncStream object at 0x1093f9450>}\n# http11.send_request_headers.started {'request': <Request [b'GET']>}\n# http11.send_request_headers.complete {'return_value': None}\n# http11.send_request_body.started {'request': <Request [b'GET']>}\n# http11.send_request_body.complete {'return_value': None}\n# http11.receive_response_headers.started {'request': <Request [b'GET']>}\n# http11.receive_response_headers.complete {'return_value': (b'HTTP/1.1', 200, b'OK', [(b'Age', b'553715'), (b'Cache-Control', b'max-age=604800'), (b'Content-Type', b'text/html; charset=UTF-8'), (b'Date', b'Thu, 21 Oct 2021 17:08:42 GMT'), (b'Etag', b'\"3147526947+ident\"'), (b'Expires', b'Thu, 28 Oct 2021 17:08:42 GMT'), (b'Last-Modified', b'Thu, 17 Oct 2019 07:18:26 GMT'), (b'Server', b'ECS (nyb/1DCD)'), (b'Vary', b'Accept-Encoding'), (b'X-Cache', b'HIT'), (b'Content-Length', b'1256')])}\n# http11.receive_response_body.started {'request': <Request [b'GET']>}\n# http11.receive_response_body.complete {'return_value': None}\n# http11.response_closed.started {}\n# http11.response_closed.complete {'return_value': None}\n```\n\nThe `event_name` and `info` arguments here will be one of the following:\n\n* `{event_type}.{event_name}.started`, `<dictionary of keyword arguments>`\n* `{event_type}.{event_name}.complete`, `{\"return_value\": <...>}`\n* `{event_type}.{event_name}.failed`, `{\"exception\": <...>}`\n\nNote that when using async code the handler function passed to `\"trace\"` must be an `async def ...` function.\n\nThe following event types are currently exposed...\n\n**Establishing the connection**\n\n* `\"connection.connect_tcp\"`\n* `\"connection.connect_unix_socket\"`\n* `\"connection.start_tls\"`\n\n**HTTP/1.1 events**\n\n* `\"http11.send_request_headers\"`\n* `\"http11.send_request_body\"`\n* `\"http11.receive_response\"`\n* `\"http11.receive_response_body\"`\n* `\"http11.response_closed\"`\n\n**HTTP/2 events**\n\n* `\"http2.send_connection_init\"`\n* `\"http2.send_request_headers\"`\n* `\"http2.send_request_body\"`\n* `\"http2.receive_response_headers\"`\n* `\"http2.receive_response_body\"`\n* `\"http2.response_closed\"`\n\nThe exact set of trace events may be subject to change across different versions of `httpcore`. If you need to rely on a particular set of events it is recommended that you pin installation of the package to a fixed version.\n\n### `\"sni_hostname\"`\n\nThe server's hostname, which is used to confirm the hostname supplied by the SSL certificate.\n\nIf you want to connect to an explicit IP address rather than using the standard DNS hostname lookup, then you'll need to use this request extension.\n\nFor example:\n\n``` python\n# Connect to '185.199.108.153' but use 'www.encode.io' in the Host header,\n# and use 'www.encode.io' when SSL verifying the server hostname.\nclient = httpx.Client()\nheaders = {\"Host\": \"www.encode.io\"}\nextensions = {\"sni_hostname\": \"www.encode.io\"}\nresponse = client.get(\n    \"https://185.199.108.153/path\",\n    headers=headers,\n    extensions=extensions\n)\n```\n\n### `\"timeout\"`\n\nA dictionary of `str: Optional[float]` timeout values.\n\nMay include values for `'connect'`, `'read'`, `'write'`, or `'pool'`.\n\nFor example:\n\n```python\n# Timeout if a connection takes more than 5 seconds to established, or if\n# we are blocked waiting on the connection pool for more than 10 seconds.\nclient = httpx.Client()\nresponse = client.get(\n    \"https://www.example.com\",\n    extensions={\"timeout\": {\"connect\": 5.0, \"pool\": 10.0}}\n)\n```\n\nThis extension is how the `httpx` timeouts are implemented, ensuring that the timeout values are associated with the request instance and passed throughout the stack. You shouldn't typically be working with this extension directly, but use the higher level `timeout` API instead.\n\n### `\"target\"`\n\nThe target that is used as [the HTTP target instead of the URL path](https://datatracker.ietf.org/doc/html/rfc2616#section-5.1.2).\n\nThis enables support constructing requests that would otherwise be unsupported.\n\n* URL paths with non-standard escaping applied.\n* Forward proxy requests using an absolute URI.\n* Tunneling proxy requests using `CONNECT` with hostname as the target.\n* Server-wide `OPTIONS *` requests.\n\nSome examples:\n\nUsing the 'target' extension to send requests without the standard path escaping rules...\n\n```python\n# Typically a request to \"https://www.example.com/test^path\" would\n# connect to \"www.example.com\" and send an HTTP/1.1 request like...\n#\n# GET /test%5Epath HTTP/1.1\n#\n# Using the target extension we can include the literal '^'...\n#\n# GET /test^path HTTP/1.1\n#\n# Note that requests must still be valid HTTP requests.\n# For example including whitespace in the target will raise a `LocalProtocolError`.\nextensions = {\"target\": b\"/test^path\"}\nresponse = httpx.get(\"https://www.example.com\", extensions=extensions)\n```\n\nThe `target` extension also allows server-wide `OPTIONS *` requests to be constructed...\n\n```python\n# This will send the following request...\n#\n# CONNECT * HTTP/1.1\nextensions = {\"target\": b\"*\"}\nresponse = httpx.request(\"CONNECT\", \"https://www.example.com\", extensions=extensions)\n```\n\n## Response Extensions\n\n### `\"http_version\"`\n\nThe HTTP version, as bytes. Eg. `b\"HTTP/1.1\"`.\n\nWhen using HTTP/1.1 the response line includes an explicit version, and the value of this key could feasibly be one of `b\"HTTP/0.9\"`, `b\"HTTP/1.0\"`, or `b\"HTTP/1.1\"`.\n\nWhen using HTTP/2 there is no further response versioning included in the protocol, and the value of this key will always be `b\"HTTP/2\"`.\n\n### `\"reason_phrase\"`\n\nThe reason-phrase of the HTTP response, as bytes. For example `b\"OK\"`. Some servers may include a custom reason phrase, although this is not recommended.\n\nHTTP/2 onwards does not include a reason phrase on the wire.\n\nWhen no key is included, a default based on the status code may be used.\n\n### `\"stream_id\"`\n\nWhen HTTP/2 is being used the `\"stream_id\"` response extension can be accessed to determine the ID of the data stream that the response was sent on.\n\n### `\"network_stream\"`\n\nThe `\"network_stream\"` extension allows developers to handle HTTP `CONNECT` and `Upgrade` requests, by providing an API that steps outside the standard request/response model, and can directly read or write to the network.\n\nThe interface provided by the network stream:\n\n* `read(max_bytes, timeout = None) -> bytes`\n* `write(buffer, timeout = None)`\n* `close()`\n* `start_tls(ssl_context, server_hostname = None, timeout = None) -> NetworkStream`\n* `get_extra_info(info) -> Any`\n\nThis API can be used as the foundation for working with HTTP proxies, WebSocket upgrades, and other advanced use-cases.\n\nSee the [network backends documentation](https://www.encode.io/httpcore/network-backends/) for more information on working directly with network streams.\n\n**Extra network information**\n\nThe network stream abstraction also allows access to various low-level information that may be exposed by the underlying socket:\n\n```python\nresponse = httpx.get(\"https://www.example.com\")\nnetwork_stream = response.extensions[\"network_stream\"]\n\nclient_addr = network_stream.get_extra_info(\"client_addr\")\nserver_addr = network_stream.get_extra_info(\"server_addr\")\nprint(\"Client address\", client_addr)\nprint(\"Server address\", server_addr)\n```\n\nThe socket SSL information is also available through this interface, although you need to ensure that the underlying connection is still open, in order to access it...\n\n```python\nwith httpx.stream(\"GET\", \"https://www.example.com\") as response:\n    network_stream = response.extensions[\"network_stream\"]\n\n    ssl_object = network_stream.get_extra_info(\"ssl_object\")\n    print(\"TLS version\", ssl_object.version())\n```\n"
  },
  {
    "path": "docs/advanced/proxies.md",
    "content": "HTTPX supports setting up [HTTP proxies](https://en.wikipedia.org/wiki/Proxy_server#Web_proxy_servers) via the `proxy` parameter to be passed on client initialization or top-level API functions like `httpx.get(..., proxy=...)`.\n\n<div align=\"center\">\n    <img src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/Open_proxy_h2g2bob.svg/480px-Open_proxy_h2g2bob.svg.png\"/>\n    <figcaption><em>Diagram of how a proxy works (source: Wikipedia). The left hand side \"Internet\" blob may be your HTTPX client requesting <code>example.com</code> through a proxy.</em></figcaption>\n</div>\n\n## HTTP Proxies\n\nTo route all traffic (HTTP and HTTPS) to a proxy located at `http://localhost:8030`, pass the proxy URL to the client...\n\n```python\nwith httpx.Client(proxy=\"http://localhost:8030\") as client:\n    ...\n```\n\nFor more advanced use cases, pass a mounts `dict`. For example, to route HTTP and HTTPS requests to 2 different proxies, respectively located at `http://localhost:8030`, and `http://localhost:8031`, pass a `dict` of proxy URLs:\n\n```python\nproxy_mounts = {\n    \"http://\": httpx.HTTPTransport(proxy=\"http://localhost:8030\"),\n    \"https://\": httpx.HTTPTransport(proxy=\"http://localhost:8031\"),\n}\n\nwith httpx.Client(mounts=proxy_mounts) as client:\n    ...\n```\n\nFor detailed information about proxy routing, see the [Routing](#routing) section.\n\n!!! tip \"Gotcha\"\n    In most cases, the proxy URL for the `https://` key _should_ use the `http://` scheme (that's not a typo!).\n\n    This is because HTTP proxying requires initiating a connection with the proxy server. While it's possible that your proxy supports doing it via HTTPS, most proxies only support doing it via HTTP.\n\n    For more information, see [FORWARD vs TUNNEL](#forward-vs-tunnel).\n\n## Authentication\n\nProxy credentials can be passed as the `userinfo` section of the proxy URL. For example:\n\n```python\nwith httpx.Client(proxy=\"http://username:password@localhost:8030\") as client:\n    ...\n```\n\n## Proxy mechanisms\n\n!!! note\n    This section describes **advanced** proxy concepts and functionality.\n\n### FORWARD vs TUNNEL\n\nIn general, the flow for making an HTTP request through a proxy is as follows:\n\n1. The client connects to the proxy (initial connection request).\n2. The proxy transfers data to the server on your behalf.\n\nHow exactly step 2/ is performed depends on which of two proxying mechanisms is used:\n\n* **Forwarding**: the proxy makes the request for you, and sends back the response it obtained from the server.\n* **Tunnelling**: the proxy establishes a TCP connection to the server on your behalf, and the client reuses this connection to send the request and receive the response. This is known as an [HTTP Tunnel](https://en.wikipedia.org/wiki/HTTP_tunnel). This mechanism is how you can access websites that use HTTPS from an HTTP proxy (the client \"upgrades\" the connection to HTTPS by performing the TLS handshake with the server over the TCP connection provided by the proxy).\n\n### Troubleshooting proxies\n\nIf you encounter issues when setting up proxies, please refer to our [Troubleshooting guide](../troubleshooting.md#proxies).\n\n## SOCKS\n\nIn addition to HTTP proxies, `httpcore` also supports proxies using the SOCKS protocol.\nThis is an optional feature that requires an additional third-party library be installed before use.\n\nYou can install SOCKS support using `pip`:\n\n```shell\n$ pip install httpx[socks]\n```\n\nYou can now configure a client to make requests via a proxy using the SOCKS protocol:\n\n```python\nhttpx.Client(proxy='socks5://user:pass@host:port')\n```\n"
  },
  {
    "path": "docs/advanced/resource-limits.md",
    "content": "You can control the connection pool size using the `limits` keyword\nargument on the client. It takes instances of `httpx.Limits` which define:\n\n- `max_keepalive_connections`, number of allowable keep-alive connections, or `None` to always\nallow. (Defaults 20)\n- `max_connections`, maximum number of allowable connections, or `None` for no limits.\n(Default 100)\n- `keepalive_expiry`, time limit on idle keep-alive connections in seconds, or `None` for no limits. (Default 5)\n\n```python\nlimits = httpx.Limits(max_keepalive_connections=5, max_connections=10)\nclient = httpx.Client(limits=limits)\n```"
  },
  {
    "path": "docs/advanced/ssl.md",
    "content": "When making a request over HTTPS, HTTPX needs to verify the identity of the requested host. To do this, it uses a bundle of SSL certificates (a.k.a. CA bundle) delivered by a trusted certificate authority (CA).\n\n### Enabling and disabling verification\n\nBy default httpx will verify HTTPS connections, and raise an error for invalid SSL cases...\n\n```pycon\n>>> httpx.get(\"https://expired.badssl.com/\")\nhttpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997)\n```\n\nYou can disable SSL verification completely and allow insecure requests...\n\n```pycon\n>>> httpx.get(\"https://expired.badssl.com/\", verify=False)\n<Response [200 OK]>\n```\n\n### Configuring client instances\n\nIf you're using a `Client()` instance you should pass any `verify=<...>` configuration when instantiating the client.\n\nBy default the [certifi CA bundle](https://certifiio.readthedocs.io/en/latest/) is used for SSL verification.\n\nFor more complex configurations you can pass an [SSL Context](https://docs.python.org/3/library/ssl.html) instance...\n\n```python\nimport certifi\nimport httpx\nimport ssl\n\n# This SSL context is equivalent to the default `verify=True`.\nctx = ssl.create_default_context(cafile=certifi.where())\nclient = httpx.Client(verify=ctx)\n```\n\nUsing [the `truststore` package](https://truststore.readthedocs.io/) to support system certificate stores...\n\n```python\nimport ssl\nimport truststore\nimport httpx\n\n# Use system certificate stores.\nctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\nclient = httpx.Client(verify=ctx)\n```\n\nLoding an alternative certificate verification store using [the standard SSL context API](https://docs.python.org/3/library/ssl.html)...\n\n```python\nimport httpx\nimport ssl\n\n# Use an explicitly configured certificate store.\nctx = ssl.create_default_context(cafile=\"path/to/certs.pem\")  # Either cafile or capath.\nclient = httpx.Client(verify=ctx)\n```\n\n### Client side certificates\n\nClient side certificates allow a remote server to verify the client. They tend to be used within private organizations to authenticate requests to remote servers.\n\nYou can specify client-side certificates, using the [`.load_cert_chain()`](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain) API...\n\n```python\nctx = ssl.create_default_context()\nctx.load_cert_chain(certfile=\"path/to/client.pem\")  # Optionally also keyfile or password.\nclient = httpx.Client(verify=ctx)\n```\n\n### Working with `SSL_CERT_FILE` and `SSL_CERT_DIR`\n\n`httpx` does respect the `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables by default. For details, refer to [the section on the environment variables page](../environment_variables.md#ssl_cert_file).\n\n### Making HTTPS requests to a local server\n\nWhen making requests to local servers, such as a development server running on `localhost`, you will typically be using unencrypted HTTP connections.\n\nIf you do need to make HTTPS connections to a local server, for example to test an HTTPS-only service, you will need to create and use your own certificates. Here's one way to do it...\n\n1. Use [trustme](https://github.com/python-trio/trustme) to generate a pair of server key/cert files, and a client cert file.\n2. Pass the server key/cert files when starting your local server. (This depends on the particular web server you're using. For example, [Uvicorn](https://www.uvicorn.org) provides the `--ssl-keyfile` and `--ssl-certfile` options.)\n3. Configure `httpx` to use the certificates stored in `client.pem`.\n\n```python\nctx = ssl.create_default_context(cafile=\"client.pem\")\nclient = httpx.Client(verify=ctx)\n```\n"
  },
  {
    "path": "docs/advanced/text-encodings.md",
    "content": "When accessing `response.text`, we need to decode the response bytes into a unicode text representation.\n\nBy default `httpx` will use `\"charset\"` information included in the response `Content-Type` header to determine how the response bytes should be decoded into text.\n\nIn cases where no charset information is included on the response, the default behaviour is to assume \"utf-8\" encoding, which is by far the most widely used text encoding on the internet.\n\n## Using the default encoding\n\nTo understand this better let's start by looking at the default behaviour for text decoding...\n\n```python\nimport httpx\n# Instantiate a client with the default configuration.\nclient = httpx.Client()\n# Using the client...\nresponse = client.get(...)\nprint(response.encoding)  # This will either print the charset given in\n                          # the Content-Type charset, or else \"utf-8\".\nprint(response.text)  # The text will either be decoded with the Content-Type\n                      # charset, or using \"utf-8\".\n```\n\nThis is normally absolutely fine. Most servers will respond with a properly formatted Content-Type header, including a charset encoding. And in most cases where no charset encoding is included, UTF-8 is very likely to be used, since it is so widely adopted.\n\n## Using an explicit encoding\n\nIn some cases we might be making requests to a site where no character set information is being set explicitly by the server, but we know what the encoding is. In this case it's best to set the default encoding explicitly on the client.\n\n```python\nimport httpx\n# Instantiate a client with a Japanese character set as the default encoding.\nclient = httpx.Client(default_encoding=\"shift-jis\")\n# Using the client...\nresponse = client.get(...)\nprint(response.encoding)  # This will either print the charset given in\n                          # the Content-Type charset, or else \"shift-jis\".\nprint(response.text)  # The text will either be decoded with the Content-Type\n                      # charset, or using \"shift-jis\".\n```\n\n## Using auto-detection\n\nIn cases where the server is not reliably including character set information, and where we don't know what encoding is being used, we can enable auto-detection to make a best-guess attempt when decoding from bytes to text.\n\nTo use auto-detection you need to set the `default_encoding` argument to a callable instead of a string. This callable should be a function which takes the input bytes as an argument and returns the character set to use for decoding those bytes to text.\n\nThere are two widely used Python packages which both handle this functionality:\n\n* [`chardet`](https://chardet.readthedocs.io/) - This is a well established package, and is a port of [the auto-detection code in Mozilla](https://www-archive.mozilla.org/projects/intl/chardet.html).\n* [`charset-normalizer`](https://charset-normalizer.readthedocs.io/) - A newer package, motivated by `chardet`, with a different approach.\n\nLet's take a look at installing autodetection using one of these packages...\n\n```shell\n$ pip install httpx\n$ pip install chardet\n```\n\nOnce `chardet` is installed, we can configure a client to use character-set autodetection.\n\n```python\nimport httpx\nimport chardet\n\ndef autodetect(content):\n    return chardet.detect(content).get(\"encoding\")\n\n# Using a client with character-set autodetection enabled.\nclient = httpx.Client(default_encoding=autodetect)\nresponse = client.get(...)\nprint(response.encoding)  # This will either print the charset given in\n                          # the Content-Type charset, or else the auto-detected\n                          # character set.\nprint(response.text)\n```\n"
  },
  {
    "path": "docs/advanced/timeouts.md",
    "content": "HTTPX is careful to enforce timeouts everywhere by default.\n\nThe default behavior is to raise a `TimeoutException` after 5 seconds of\nnetwork inactivity.\n\n## Setting and disabling timeouts\n\nYou can set timeouts for an individual request:\n\n```python\n# Using the top-level API:\nhttpx.get('http://example.com/api/v1/example', timeout=10.0)\n\n# Using a client instance:\nwith httpx.Client() as client:\n    client.get(\"http://example.com/api/v1/example\", timeout=10.0)\n```\n\nOr disable timeouts for an individual request:\n\n```python\n# Using the top-level API:\nhttpx.get('http://example.com/api/v1/example', timeout=None)\n\n# Using a client instance:\nwith httpx.Client() as client:\n    client.get(\"http://example.com/api/v1/example\", timeout=None)\n```\n\n## Setting a default timeout on a client\n\nYou can set a timeout on a client instance, which results in the given\n`timeout` being used as the default for requests made with this client:\n\n```python\nclient = httpx.Client()              # Use a default 5s timeout everywhere.\nclient = httpx.Client(timeout=10.0)  # Use a default 10s timeout everywhere.\nclient = httpx.Client(timeout=None)  # Disable all timeouts by default.\n```\n\n## Fine tuning the configuration\n\nHTTPX also allows you to specify the timeout behavior in more fine grained detail.\n\nThere are four different types of timeouts that may occur. These are **connect**,\n**read**, **write**, and **pool** timeouts.\n\n* The **connect** timeout specifies the maximum amount of time to wait until\na socket connection to the requested host is established. If HTTPX is unable to connect\nwithin this time frame, a `ConnectTimeout` exception is raised.\n* The **read** timeout specifies the maximum duration to wait for a chunk of\ndata to be received (for example, a chunk of the response body). If HTTPX is\nunable to receive data within this time frame, a `ReadTimeout` exception is raised.\n* The **write** timeout specifies the maximum duration to wait for a chunk of\ndata to be sent (for example, a chunk of the request body). If HTTPX is unable\nto send data within this time frame, a `WriteTimeout` exception is raised.\n* The **pool** timeout specifies the maximum duration to wait for acquiring\na connection from the connection pool. If HTTPX is unable to acquire a connection\nwithin this time frame, a `PoolTimeout` exception is raised. A related\nconfiguration here is the maximum number of allowable connections in the\nconnection pool, which is configured by the `limits` argument.\n\nYou can configure the timeout behavior for any of these values...\n\n```python\n# A client with a 60s timeout for connecting, and a 10s timeout elsewhere.\ntimeout = httpx.Timeout(10.0, connect=60.0)\nclient = httpx.Client(timeout=timeout)\n\nresponse = client.get('http://example.com/')\n```"
  },
  {
    "path": "docs/advanced/transports.md",
    "content": "HTTPX's `Client` also accepts a `transport` argument. This argument allows you\nto provide a custom Transport object that will be used to perform the actual\nsending of the requests.\n\n## HTTP Transport\n\nFor some advanced configuration you might need to instantiate a transport\nclass directly, and pass it to the client instance. One example is the\n`local_address` configuration which is only available via this low-level API.\n\n```pycon\n>>> import httpx\n>>> transport = httpx.HTTPTransport(local_address=\"0.0.0.0\")\n>>> client = httpx.Client(transport=transport)\n```\n\nConnection retries are also available via this interface. Requests will be retried the given number of times in case an `httpx.ConnectError` or an `httpx.ConnectTimeout` occurs, allowing smoother operation under flaky networks. If you need other forms of retry behaviors, such as handling read/write errors or reacting to `503 Service Unavailable`, consider general-purpose tools such as [tenacity](https://github.com/jd/tenacity).\n\n```pycon\n>>> import httpx\n>>> transport = httpx.HTTPTransport(retries=1)\n>>> client = httpx.Client(transport=transport)\n```\n\nSimilarly, instantiating a transport directly provides a `uds` option for\nconnecting via a Unix Domain Socket that is only available via this low-level API:\n\n```pycon\n>>> import httpx\n>>> # Connect to the Docker API via a Unix Socket.\n>>> transport = httpx.HTTPTransport(uds=\"/var/run/docker.sock\")\n>>> client = httpx.Client(transport=transport)\n>>> response = client.get(\"http://docker/info\")\n>>> response.json()\n{\"ID\": \"...\", \"Containers\": 4, \"Images\": 74, ...}\n```\n\n## WSGI Transport\n\nYou can configure an `httpx` client to call directly into a Python web application using the WSGI protocol.\n\nThis is particularly useful for two main use-cases:\n\n* Using `httpx` as a client inside test cases.\n* Mocking out external services during tests or in dev or staging environments.\n\n### Example\n\nHere's an example of integrating against a Flask application:\n\n```python\nfrom flask import Flask\nimport httpx\n\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef hello():\n    return \"Hello World!\"\n\ntransport = httpx.WSGITransport(app=app)\nwith httpx.Client(transport=transport, base_url=\"http://testserver\") as client:\n    r = client.get(\"/\")\n    assert r.status_code == 200\n    assert r.text == \"Hello World!\"\n```\n\n### Configuration\n\nFor some more complex cases you might need to customize the WSGI transport. This allows you to:\n\n* Inspect 500 error responses rather than raise exceptions by setting `raise_app_exceptions=False`.\n* Mount the WSGI application at a subpath by setting `script_name` (WSGI).\n* Use a given client address for requests by setting `remote_addr` (WSGI).\n\nFor example:\n\n```python\n# Instantiate a client that makes WSGI requests with a client IP of \"1.2.3.4\".\ntransport = httpx.WSGITransport(app=app, remote_addr=\"1.2.3.4\")\nwith httpx.Client(transport=transport, base_url=\"http://testserver\") as client:\n    ...\n```\n\n## ASGI Transport\n\nYou can configure an `httpx` client to call directly into an async Python web application using the ASGI protocol.\n\nThis is particularly useful for two main use-cases:\n\n* Using `httpx` as a client inside test cases.\n* Mocking out external services during tests or in dev or staging environments.\n\n### Example\n\nLet's take this Starlette application as an example:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.responses import HTMLResponse\nfrom starlette.routing import Route\n\n\nasync def hello(request):\n    return HTMLResponse(\"Hello World!\")\n\n\napp = Starlette(routes=[Route(\"/\", hello)])\n```\n\nWe can make requests directly against the application, like so:\n\n```python\ntransport = httpx.ASGITransport(app=app)\n\nasync with httpx.AsyncClient(transport=transport, base_url=\"http://testserver\") as client:\n    r = await client.get(\"/\")\n    assert r.status_code == 200\n    assert r.text == \"Hello World!\"\n```\n\n### Configuration\n\nFor some more complex cases you might need to customise the ASGI transport. This allows you to:\n\n* Inspect 500 error responses rather than raise exceptions by setting `raise_app_exceptions=False`.\n* Mount the ASGI application at a subpath by setting `root_path`.\n* Use a given client address for requests by setting `client`.\n\nFor example:\n\n```python\n# Instantiate a client that makes ASGI requests with a client IP of \"1.2.3.4\",\n# on port 123.\ntransport = httpx.ASGITransport(app=app, client=(\"1.2.3.4\", 123))\nasync with httpx.AsyncClient(transport=transport, base_url=\"http://testserver\") as client:\n    ...\n```\n\nSee [the ASGI documentation](https://asgi.readthedocs.io/en/latest/specs/www.html#connection-scope) for more details on the `client` and `root_path` keys.\n\n### ASGI startup and shutdown\n\nIt is not in the scope of HTTPX to trigger ASGI lifespan events of your app.\n\nHowever it is suggested to use `LifespanManager` from [asgi-lifespan](https://github.com/florimondmanca/asgi-lifespan#usage) in pair with `AsyncClient`.\n\n## Custom transports\n\nA transport instance must implement the low-level Transport API which deals\nwith sending a single request, and returning a response. You should either\nsubclass `httpx.BaseTransport` to implement a transport to use with `Client`,\nor subclass `httpx.AsyncBaseTransport` to implement a transport to\nuse with `AsyncClient`.\n\nAt the layer of the transport API we're using the familiar `Request` and\n`Response` models.\n\nSee the `handle_request` and `handle_async_request` docstrings for more details\non the specifics of the Transport API.\n\nA complete example of a custom transport implementation would be:\n\n```python\nimport json\nimport httpx\n\nclass HelloWorldTransport(httpx.BaseTransport):\n    \"\"\"\n    A mock transport that always returns a JSON \"Hello, world!\" response.\n    \"\"\"\n\n    def handle_request(self, request):\n        return httpx.Response(200, json={\"text\": \"Hello, world!\"})\n```\n\nOr this example, which uses a custom transport and `httpx.Mounts` to always redirect `http://` requests.\n\n```python\nclass HTTPSRedirect(httpx.BaseTransport):\n    \"\"\"\n    A transport that always redirects to HTTPS.\n    \"\"\"\n    def handle_request(self, request):\n        url = request.url.copy_with(scheme=\"https\")\n        return httpx.Response(303, headers={\"Location\": str(url)})\n\n# A client where any `http` requests are always redirected to `https`\ntransport = httpx.Mounts({\n    'http://': HTTPSRedirect()\n    'https://': httpx.HTTPTransport()\n})\nclient = httpx.Client(transport=transport)\n```\n\nA useful pattern here is custom transport classes that wrap the default HTTP implementation. For example...\n\n```python\nclass DebuggingTransport(httpx.BaseTransport):\n    def __init__(self, **kwargs):\n        self._wrapper = httpx.HTTPTransport(**kwargs)\n\n    def handle_request(self, request):\n        print(f\">>> {request}\")\n        response = self._wrapper.handle_request(request)\n        print(f\"<<< {response}\")\n        return response\n\n    def close(self):\n        self._wrapper.close()\n\ntransport = DebuggingTransport()\nclient = httpx.Client(transport=transport)\n```\n\nHere's another case, where we're using a round-robin across a number of different proxies...\n\n```python\nclass ProxyRoundRobin(httpx.BaseTransport):\n    def __init__(self, proxies, **kwargs):\n        self._transports = [\n            httpx.HTTPTransport(proxy=proxy, **kwargs)\n            for proxy in proxies\n        ]\n        self._idx = 0\n\n    def handle_request(self, request):\n        transport = self._transports[self._idx]\n        self._idx = (self._idx + 1) % len(self._transports)\n        return transport.handle_request(request)\n\n    def close(self):\n        for transport in self._transports:\n            transport.close()\n\nproxies = [\n    httpx.Proxy(\"http://127.0.0.1:8081\"),\n    httpx.Proxy(\"http://127.0.0.1:8082\"),\n    httpx.Proxy(\"http://127.0.0.1:8083\"),\n]\ntransport = ProxyRoundRobin(proxies=proxies)\nclient = httpx.Client(transport=transport)\n```\n\n## Mock transports\n\nDuring testing it can often be useful to be able to mock out a transport,\nand return pre-determined responses, rather than making actual network requests.\n\nThe `httpx.MockTransport` class accepts a handler function, which can be used\nto map requests onto pre-determined responses:\n\n```python\ndef handler(request):\n    return httpx.Response(200, json={\"text\": \"Hello, world!\"})\n\n\n# Switch to a mock transport, if the TESTING environment variable is set.\nif os.environ.get('TESTING', '').upper() == \"TRUE\":\n    transport = httpx.MockTransport(handler)\nelse:\n    transport = httpx.HTTPTransport()\n\nclient = httpx.Client(transport=transport)\n```\n\nFor more advanced use-cases you might want to take a look at either [the third-party\nmocking library, RESPX](https://lundberg.github.io/respx/), or the [pytest-httpx library](https://github.com/Colin-b/pytest_httpx).\n\n## Mounting transports\n\nYou can also mount transports against given schemes or domains, to control\nwhich transport an outgoing request should be routed via, with [the same style\nused for specifying proxy routing](#routing).\n\n```python\nimport httpx\n\nclass HTTPSRedirectTransport(httpx.BaseTransport):\n    \"\"\"\n    A transport that always redirects to HTTPS.\n    \"\"\"\n\n    def handle_request(self, method, url, headers, stream, extensions):\n        scheme, host, port, path = url\n        if port is None:\n            location = b\"https://%s%s\" % (host, path)\n        else:\n            location = b\"https://%s:%d%s\" % (host, port, path)\n        stream = httpx.ByteStream(b\"\")\n        headers = [(b\"location\", location)]\n        extensions = {}\n        return 303, headers, stream, extensions\n\n\n# A client where any `http` requests are always redirected to `https`\nmounts = {'http://': HTTPSRedirectTransport()}\nclient = httpx.Client(mounts=mounts)\n```\n\nA couple of other sketches of how you might take advantage of mounted transports...\n\nDisabling HTTP/2 on a single given domain...\n\n```python\nmounts = {\n    \"all://\": httpx.HTTPTransport(http2=True),\n    \"all://*example.org\": httpx.HTTPTransport()\n}\nclient = httpx.Client(mounts=mounts)\n```\n\nMocking requests to a given domain:\n\n```python\n# All requests to \"example.org\" should be mocked out.\n# Other requests occur as usual.\ndef handler(request):\n    return httpx.Response(200, json={\"text\": \"Hello, World!\"})\n\nmounts = {\"all://example.org\": httpx.MockTransport(handler)}\nclient = httpx.Client(mounts=mounts)\n```\n\nAdding support for custom schemes:\n\n```python\n# Support URLs like \"file:///Users/sylvia_green/websites/new_client/index.html\"\nmounts = {\"file://\": FileSystemTransport()}\nclient = httpx.Client(mounts=mounts)\n```\n\n### Routing\n\nHTTPX provides a powerful mechanism for routing requests, allowing you to write complex rules that specify which transport should be used for each request.\n\nThe `mounts` dictionary maps URL patterns to HTTP transports. HTTPX matches requested URLs against URL patterns to decide which transport should be used, if any. Matching is done from most specific URL patterns (e.g. `https://<domain>:<port>`) to least specific ones (e.g. `https://`).\n\nHTTPX supports routing requests based on **scheme**, **domain**, **port**, or a combination of these.\n\n### Wildcard routing\n\nRoute everything through a transport...\n\n```python\nmounts = {\n    \"all://\": httpx.HTTPTransport(proxy=\"http://localhost:8030\"),\n}\n```\n\n### Scheme routing\n\nRoute HTTP requests through one transport, and HTTPS requests through another...\n\n```python\nmounts = {\n    \"http://\": httpx.HTTPTransport(proxy=\"http://localhost:8030\"),\n    \"https://\": httpx.HTTPTransport(proxy=\"http://localhost:8031\"),\n}\n```\n\n### Domain routing\n\nProxy all requests on domain \"example.com\", let other requests pass through...\n\n```python\nmounts = {\n    \"all://example.com\": httpx.HTTPTransport(proxy=\"http://localhost:8030\"),\n}\n```\n\nProxy HTTP requests on domain \"example.com\", let HTTPS and other requests pass through...\n\n```python\nmounts = {\n    \"http://example.com\": httpx.HTTPTransport(proxy=\"http://localhost:8030\"),\n}\n```\n\nProxy all requests to \"example.com\" and its subdomains, let other requests pass through...\n\n```python\nmounts = {\n    \"all://*example.com\": httpx.HTTPTransport(proxy=\"http://localhost:8030\"),\n}\n```\n\nProxy all requests to strict subdomains of \"example.com\", let \"example.com\" and other requests pass through...\n\n```python\nmounts = {\n    \"all://*.example.com\": httpx.HTTPTransport(proxy=\"http://localhost:8030\"),\n}\n```\n\n### Port routing\n\nProxy HTTPS requests on port 1234 to \"example.com\"...\n\n```python\nmounts = {\n    \"https://example.com:1234\": httpx.HTTPTransport(proxy=\"http://localhost:8030\"),\n}\n```\n\nProxy all requests on port 1234...\n\n```python\nmounts = {\n    \"all://*:1234\": httpx.HTTPTransport(proxy=\"http://localhost:8030\"),\n}\n```\n\n### No-proxy support\n\nIt is also possible to define requests that _shouldn't_ be routed through the transport.\n\nTo do so, pass `None` as the proxy URL. For example...\n\n```python\nmounts = {\n    # Route requests through a proxy by default...\n    \"all://\": httpx.HTTPTransport(proxy=\"http://localhost:8031\"),\n    # Except those for \"example.com\".\n    \"all://example.com\": None,\n}\n```\n\n### Complex configuration example\n\nYou can combine the routing features outlined above to build complex proxy routing configurations. For example...\n\n```python\nmounts = {\n    # Route all traffic through a proxy by default...\n    \"all://\": httpx.HTTPTransport(proxy=\"http://localhost:8030\"),\n    # But don't use proxies for HTTPS requests to \"domain.io\"...\n    \"https://domain.io\": None,\n    # And use another proxy for requests to \"example.com\" and its subdomains...\n    \"all://*example.com\": httpx.HTTPTransport(proxy=\"http://localhost:8031\"),\n    # And yet another proxy if HTTP is used,\n    # and the \"internal\" subdomain on port 5550 is requested...\n    \"http://internal.example.com:5550\": httpx.HTTPTransport(proxy=\"http://localhost:8032\"),\n}\n```\n\n### Environment variables\n\nThere are also environment variables that can be used to control the dictionary of the client mounts. \nThey can be used to configure HTTP proxying for clients.\n\nSee documentation on [`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`](../environment_variables.md#http_proxy-https_proxy-all_proxy)\nand [`NO_PROXY`](../environment_variables.md#no_proxy) for more information.\n"
  },
  {
    "path": "docs/api.md",
    "content": "# Developer Interface\n\n## Helper Functions\n\n!!! note\n    Only use these functions if you're testing HTTPX in a console\n    or making a small number of requests. Using a `Client` will\n    enable HTTP/2 and connection pooling for more efficient and\n    long-lived connections.\n\n::: httpx.request\n    :docstring:\n\n::: httpx.get\n    :docstring:\n\n::: httpx.options\n    :docstring:\n\n::: httpx.head\n    :docstring:\n\n::: httpx.post\n    :docstring:\n\n::: httpx.put\n    :docstring:\n\n::: httpx.patch\n    :docstring:\n\n::: httpx.delete\n    :docstring:\n\n::: httpx.stream\n    :docstring:\n\n## `Client`\n\n::: httpx.Client\n    :docstring:\n    :members: headers cookies params auth request get head options post put patch delete stream build_request send close\n\n## `AsyncClient`\n\n::: httpx.AsyncClient\n    :docstring:\n    :members: headers cookies params auth request get head options post put patch delete stream build_request send aclose\n\n\n## `Response`\n\n*An HTTP response.*\n\n* `def __init__(...)`\n* `.status_code` - **int**\n* `.reason_phrase` - **str**\n* `.http_version` - `\"HTTP/2\"` or `\"HTTP/1.1\"`\n* `.url` - **URL**\n* `.headers` - **Headers**\n* `.content` - **bytes**\n* `.text` - **str**\n* `.encoding` - **str**\n* `.is_redirect` - **bool**\n* `.request` - **Request**\n* `.next_request` - **Optional[Request]**\n* `.cookies` - **Cookies**\n* `.history` - **List[Response]**\n* `.elapsed` - **[timedelta](https://docs.python.org/3/library/datetime.html)**\n  * The amount of time elapsed between sending the request and calling `close()` on the corresponding response received for that request.\n  [total_seconds()](https://docs.python.org/3/library/datetime.html#datetime.timedelta.total_seconds) to correctly get\n  the total elapsed seconds.\n* `def .raise_for_status()` - **Response**\n* `def .json()` - **Any**\n* `def .read()` - **bytes**\n* `def .iter_raw([chunk_size])` - **bytes iterator**\n* `def .iter_bytes([chunk_size])` - **bytes iterator**\n* `def .iter_text([chunk_size])` - **text iterator**\n* `def .iter_lines()` - **text iterator**\n* `def .close()` - **None**\n* `def .next()` - **Response**\n* `def .aread()` - **bytes**\n* `def .aiter_raw([chunk_size])` - **async bytes iterator**\n* `def .aiter_bytes([chunk_size])` - **async bytes iterator**\n* `def .aiter_text([chunk_size])` - **async text iterator**\n* `def .aiter_lines()` - **async text iterator**\n* `def .aclose()` - **None**\n* `def .anext()` - **Response**\n\n## `Request`\n\n*An HTTP request. Can be constructed explicitly for more control over exactly\nwhat gets sent over the wire.*\n\n```pycon\n>>> request = httpx.Request(\"GET\", \"https://example.org\", headers={'host': 'example.org'})\n>>> response = client.send(request)\n```\n\n* `def __init__(method, url, [params], [headers], [cookies], [content], [data], [files], [json], [stream])`\n* `.method` - **str**\n* `.url` - **URL**\n* `.content` - **byte**, **byte iterator**, or **byte async iterator**\n* `.headers` - **Headers**\n* `.cookies` - **Cookies**\n\n## `URL`\n\n*A normalized, IDNA supporting URL.*\n\n```pycon\n>>> url = URL(\"https://example.org/\")\n>>> url.host\n'example.org'\n```\n\n* `def __init__(url, **kwargs)`\n* `.scheme` - **str**\n* `.authority` - **str**\n* `.host` - **str**\n* `.port` - **int**\n* `.path` - **str**\n* `.query` - **str**\n* `.raw_path` - **str**\n* `.fragment` - **str**\n* `.is_ssl` - **bool**\n* `.is_absolute_url` - **bool**\n* `.is_relative_url` - **bool**\n* `def .copy_with([scheme], [authority], [path], [query], [fragment])` - **URL**\n\n## `Headers`\n\n*A case-insensitive multi-dict.*\n\n```pycon\n>>> headers = Headers({'Content-Type': 'application/json'})\n>>> headers['content-type']\n'application/json'\n```\n\n* `def __init__(self, headers, encoding=None)`\n* `def copy()` - **Headers**\n\n## `Cookies`\n\n*A dict-like cookie store.*\n\n```pycon\n>>> cookies = Cookies()\n>>> cookies.set(\"name\", \"value\", domain=\"example.org\")\n```\n\n* `def __init__(cookies: [dict, Cookies, CookieJar])`\n* `.jar` - **CookieJar**\n* `def extract_cookies(response)`\n* `def set_cookie_header(request)`\n* `def set(name, value, [domain], [path])`\n* `def get(name, [domain], [path])`\n* `def delete(name, [domain], [path])`\n* `def clear([domain], [path])`\n* *Standard mutable mapping interface*\n\n## `Proxy`\n\n*A configuration of the proxy server.*\n\n```pycon\n>>> proxy = Proxy(\"http://proxy.example.com:8030\")\n>>> client = Client(proxy=proxy)\n```\n\n* `def __init__(url, [ssl_context], [auth], [headers])`\n* `.url` - **URL**\n* `.auth` - **tuple[str, str]**\n* `.headers` - **Headers**\n* `.ssl_context` - **SSLContext**\n"
  },
  {
    "path": "docs/async.md",
    "content": "# Async Support\n\nHTTPX offers a standard synchronous API by default, but also gives you\nthe option of an async client if you need it.\n\nAsync is a concurrency model that is far more efficient than multi-threading,\nand can provide significant performance benefits and enable the use of\nlong-lived network connections such as WebSockets.\n\nIf you're working with an async web framework then you'll also want to use an\nasync client for sending outgoing HTTP requests.\n\n## Making Async requests\n\nTo make asynchronous requests, you'll need an `AsyncClient`.\n\n```pycon\n>>> async with httpx.AsyncClient() as client:\n...     r = await client.get('https://www.example.com/')\n...\n>>> r\n<Response [200 OK]>\n```\n\n!!! tip\n    Use [IPython](https://ipython.readthedocs.io/en/stable/) or Python 3.9+ with `python -m asyncio` to try this code interactively, as they support executing `async`/`await` expressions in the console.\n\n## API Differences\n\nIf you're using an async client then there are a few bits of API that\nuse async methods.\n\n### Making requests\n\nThe request methods are all async, so you should use `response = await client.get(...)` style for all of the following:\n\n* `AsyncClient.get(url, ...)`\n* `AsyncClient.options(url, ...)`\n* `AsyncClient.head(url, ...)`\n* `AsyncClient.post(url, ...)`\n* `AsyncClient.put(url, ...)`\n* `AsyncClient.patch(url, ...)`\n* `AsyncClient.delete(url, ...)`\n* `AsyncClient.request(method, url, ...)`\n* `AsyncClient.send(request, ...)`\n\n### Opening and closing clients\n\nUse `async with httpx.AsyncClient()` if you want a context-managed client...\n\n```python\nasync with httpx.AsyncClient() as client:\n    ...\n```\n\n!!! warning\n    In order to get the most benefit from connection pooling, make sure you're not instantiating multiple client instances - for example by using `async with` inside a \"hot loop\". This can be achieved either by having a single scoped client that's passed throughout wherever it's needed, or by having a single global client instance.\n\nAlternatively, use `await client.aclose()` if you want to close a client explicitly:\n\n```python\nclient = httpx.AsyncClient()\n...\nawait client.aclose()\n```\n\n### Streaming responses\n\nThe `AsyncClient.stream(method, url, ...)` method is an async context block.\n\n```pycon\n>>> client = httpx.AsyncClient()\n>>> async with client.stream('GET', 'https://www.example.com/') as response:\n...     async for chunk in response.aiter_bytes():\n...         ...\n```\n\nThe async response streaming methods are:\n\n* `Response.aread()` - For conditionally reading a response inside a stream block.\n* `Response.aiter_bytes()` - For streaming the response content as bytes.\n* `Response.aiter_text()` - For streaming the response content as text.\n* `Response.aiter_lines()` - For streaming the response content as lines of text.\n* `Response.aiter_raw()` - For streaming the raw response bytes, without applying content decoding.\n* `Response.aclose()` - For closing the response. You don't usually need this, since `.stream` block closes the response automatically on exit.\n\nFor situations when context block usage is not practical, it is possible to enter \"manual mode\" by sending a [`Request` instance](advanced/clients.md#request-instances) using `client.send(..., stream=True)`.\n\nExample in the context of forwarding the response to a streaming web endpoint with [Starlette](https://www.starlette.io):\n\n```python\nimport httpx\nfrom starlette.background import BackgroundTask\nfrom starlette.responses import StreamingResponse\n\nclient = httpx.AsyncClient()\n\nasync def home(request):\n    req = client.build_request(\"GET\", \"https://www.example.com/\")\n    r = await client.send(req, stream=True)\n    return StreamingResponse(r.aiter_text(), background=BackgroundTask(r.aclose))\n```\n\n!!! warning\n    When using this \"manual streaming mode\", it is your duty as a developer to make sure that `Response.aclose()` is called eventually. Failing to do so would leave connections open, most likely resulting in resource leaks down the line.\n\n### Streaming requests\n\nWhen sending a streaming request body with an `AsyncClient` instance, you should use an async bytes generator instead of a bytes generator:\n\n```python\nasync def upload_bytes():\n    ...  # yield byte content\n\nawait client.post(url, content=upload_bytes())\n```\n\n### Explicit transport instances\n\nWhen instantiating a transport instance directly, you need to use `httpx.AsyncHTTPTransport`.\n\nFor instance:\n\n```pycon\n>>> import httpx\n>>> transport = httpx.AsyncHTTPTransport(retries=1)\n>>> async with httpx.AsyncClient(transport=transport) as client:\n>>>     ...\n```\n\n## Supported async environments\n\nHTTPX supports either `asyncio` or `trio` as an async environment.\n\nIt will auto-detect which of those two to use as the backend\nfor socket operations and concurrency primitives.\n\n### [AsyncIO](https://docs.python.org/3/library/asyncio.html)\n\nAsyncIO is Python's [built-in library](https://docs.python.org/3/library/asyncio.html)\nfor writing concurrent code with the async/await syntax.\n\n```python\nimport asyncio\nimport httpx\n\nasync def main():\n    async with httpx.AsyncClient() as client:\n        response = await client.get('https://www.example.com/')\n        print(response)\n\nasyncio.run(main())\n```\n\n### [Trio](https://github.com/python-trio/trio)\n\nTrio is [an alternative async library](https://trio.readthedocs.io/en/stable/),\ndesigned around the [the principles of structured concurrency](https://en.wikipedia.org/wiki/Structured_concurrency).\n\n```python\nimport httpx\nimport trio\n\nasync def main():\n    async with httpx.AsyncClient() as client:\n        response = await client.get('https://www.example.com/')\n        print(response)\n\ntrio.run(main)\n```\n\n!!! important\n    The `trio` package must be installed to use the Trio backend.\n\n\n### [AnyIO](https://github.com/agronholm/anyio)\n\nAnyIO is an [asynchronous networking and concurrency library](https://anyio.readthedocs.io/) that works on top of either `asyncio` or `trio`. It blends in with native libraries of your chosen backend (defaults to `asyncio`).\n\n```python\nimport httpx\nimport anyio\n\nasync def main():\n    async with httpx.AsyncClient() as client:\n        response = await client.get('https://www.example.com/')\n        print(response)\n\nanyio.run(main, backend='trio')\n```\n\n## Calling into Python Web Apps\n\nFor details on calling directly into ASGI applications, see [the `ASGITransport` docs](../advanced/transports#asgitransport)."
  },
  {
    "path": "docs/code_of_conduct.md",
    "content": "# Code of Conduct\n\nWe expect contributors to our projects and online spaces to follow [the Python Software Foundation’s Code of Conduct](https://www.python.org/psf/conduct/).\n\nThe Python community is made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences great successes and continued growth. When you're working with members of the community, this Code of Conduct will help steer your interactions and keep Python a positive, successful, and growing community.\n\n## Our Community\n\nMembers of the Python community are **open, considerate, and respectful**. Behaviours that reinforce these values contribute to a positive environment, and include:\n\n* **Being open.** Members of the community are open to collaboration, whether it's on PEPs, patches, problems, or otherwise.\n* **Focusing on what is best for the community.** We're respectful of the processes set forth in the community, and we work within them.\n* **Acknowledging time and effort.** We're respectful of the volunteer efforts that permeate the Python community. We're thoughtful when addressing the efforts of others, keeping in mind that often times the labor was completed simply for the good of the community.\n* **Being respectful of differing viewpoints and experiences.** We're receptive to constructive comments and criticism, as the experiences and skill sets of other members contribute to the whole of our efforts.\n* **Showing empathy towards other community members.** We're attentive in our communications, whether in person or online, and we're tactful when approaching differing views.\n* **Being considerate.** Members of the community are considerate of their peers -- other Python users.\n* **Being respectful.** We're respectful of others, their positions, their skills, their commitments, and their efforts.\n* **Gracefully accepting constructive criticism.** When we disagree, we are courteous in raising our issues.\n* **Using welcoming and inclusive language.** We're accepting of all who wish to take part in our activities, fostering an environment where anyone can participate and everyone can make a difference.\n\n## Our Standards\n\nEvery member of our community has the right to have their identity respected. The Python community is dedicated to providing a positive experience for everyone, regardless of age, gender identity and expression, sexual orientation, disability, physical appearance, body size, ethnicity, nationality, race, or religion (or lack thereof), education, or socio-economic status.\n\n## Inappropriate Behavior\n\nExamples of unacceptable behavior by participants include:\n\n* Harassment of any participants in any form\n* Deliberate intimidation, stalking, or following\n* Logging or taking screenshots of online activity for harassment purposes\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Violent threats or language directed against another person\n* Incitement of violence or harassment towards any individual, including encouraging a person to commit suicide or to engage in self-harm\n* Creating additional online accounts in order to harass another person or circumvent a ban\n* Sexual language and imagery in online communities or in any conference venue, including talks\n* Insults, put downs, or jokes that are based upon stereotypes, that are exclusionary, or that hold others up for ridicule\n* Excessive swearing\n* Unwelcome sexual attention or advances\n* Unwelcome physical contact, including simulated physical contact (eg, textual descriptions like \"hug\" or \"backrub\") without consent or after a request to stop\n* Pattern of inappropriate social contact, such as requesting/assuming inappropriate levels of intimacy with others\n* Sustained disruption of online community discussions, in-person presentations, or other in-person events\n* Continued one-on-one communication after requests to cease\n* Other conduct that is inappropriate for a professional audience including people of many different backgrounds\n\nCommunity members asked to stop any inappropriate behavior are expected to comply immediately.\n\n## Enforcement\n\nWe take Code of Conduct violations seriously, and will act to ensure our spaces are welcoming, inclusive, and professional environments to communicate in.\n\nIf you need to raise a Code of Conduct report, you may do so privately by email to tom@tomchristie.com.\n\nReports will be treated confidentially.\n\nAlternately you may [make a report to the Python Software Foundation](https://www.python.org/psf/conduct/reporting/).\n"
  },
  {
    "path": "docs/compatibility.md",
    "content": "# Requests Compatibility Guide\n\nHTTPX aims to be broadly compatible with the `requests` API, although there are a\nfew design differences in places.\n\nThis documentation outlines places where the API differs...\n\n## Redirects\n\nUnlike `requests`, HTTPX does **not follow redirects by default**.\n\nWe differ in behaviour here [because auto-redirects can easily mask unnecessary network\ncalls being made](https://github.com/encode/httpx/discussions/1785).\n\nYou can still enable behaviour to automatically follow redirects, but you need to\ndo so explicitly...\n\n```python\nresponse = client.get(url, follow_redirects=True)\n```\n\nOr else instantiate a client, with redirect following enabled by default...\n\n```python\nclient = httpx.Client(follow_redirects=True)\n```\n\n## Client instances\n\nThe HTTPX equivalent of `requests.Session` is `httpx.Client`.\n\n```python\nsession = requests.Session(**kwargs)\n```\n\nis generally equivalent to\n\n```python\nclient = httpx.Client(**kwargs)\n```\n\n## Request URLs\n\nAccessing `response.url` will return a `URL` instance, rather than a string.\n\nUse `str(response.url)` if you need a string instance.\n\n## Determining the next redirect request\n\nThe `requests` library exposes an attribute `response.next`, which can be used to obtain the next redirect request.\n\n```python\nsession = requests.Session()\nrequest = requests.Request(\"GET\", ...).prepare()\nwhile request is not None:\n    response = session.send(request, allow_redirects=False)\n    request = response.next\n```\n\nIn HTTPX, this attribute is instead named `response.next_request`. For example:\n\n```python\nclient = httpx.Client()\nrequest = client.build_request(\"GET\", ...)\nwhile request is not None:\n    response = client.send(request)\n    request = response.next_request\n```\n\n## Request Content\n\nFor uploading raw text or binary content we prefer to use a `content` parameter,\nin order to better separate this usage from the case of uploading form data.\n\nFor example, using `content=...` to upload raw content:\n\n```python\n# Uploading text, bytes, or a bytes iterator.\nhttpx.post(..., content=b\"Hello, world\")\n```\n\nAnd using `data=...` to send form data:\n\n```python\n# Uploading form data.\nhttpx.post(..., data={\"message\": \"Hello, world\"})\n```\n\nUsing the `data=<text/byte content>` will raise a deprecation warning,\nand is expected to be fully removed with the HTTPX 1.0 release.\n\n## Upload files\n\nHTTPX strictly enforces that upload files must be opened in binary mode, in order\nto avoid character encoding issues that can result from attempting to upload files\nopened in text mode.\n\n## Content encoding\n\nHTTPX uses `utf-8` for encoding `str` request bodies. For example, when using `content=<str>` the request body will be encoded to `utf-8` before being sent over the wire. This differs from Requests which uses `latin1`. If you need an explicit encoding, pass encoded bytes explicitly, e.g. `content=<str>.encode(\"latin1\")`.\nFor response bodies, assuming the server didn't send an explicit encoding then HTTPX will do its best to figure out an appropriate encoding. HTTPX makes a guess at the encoding to use for decoding the response using `charset_normalizer`. Fallback to that or any content with less than 32 octets will be decoded using `utf-8` with the `error=\"replace\"` decoder strategy.\n\n## Cookies\n\nIf using a client instance, then cookies should always be set on the client rather than on a per-request basis.\n\nThis usage is supported:\n\n```python\nclient = httpx.Client(cookies=...)\nclient.post(...)\n```\n\nThis usage is **not** supported:\n\n```python\nclient = httpx.Client()\nclient.post(..., cookies=...)\n```\n\nWe prefer enforcing a stricter API here because it provides clearer expectations around cookie persistence, particularly when redirects occur.\n\n## Status Codes\n\nIn our documentation we prefer the uppercased versions, such as `codes.NOT_FOUND`, but also provide lower-cased versions for API compatibility with `requests`.\n\nRequests includes various synonyms for status codes that HTTPX does not support.\n\n## Streaming responses\n\nHTTPX provides a `.stream()` interface rather than using `stream=True`. This ensures that streaming responses are always properly closed outside of the stream block, and makes it visually clearer at which points streaming I/O APIs may be used with a response.\n\nFor example:\n\n```python\nwith httpx.stream(\"GET\", \"https://www.example.com\") as response:\n    ...\n```\n\nWithin a `stream()` block request data is made available with:\n\n* `.iter_bytes()` - Instead of `response.iter_content()`\n* `.iter_text()` - Instead of `response.iter_content(decode_unicode=True)`\n* `.iter_lines()` - Corresponding to `response.iter_lines()`\n* `.iter_raw()` - Use this instead of `response.raw`\n* `.read()` - Read the entire response body, making `response.text` and `response.content` available.\n\n## Timeouts\n\nHTTPX defaults to including reasonable [timeouts](quickstart.md#timeouts) for all network operations, while Requests has no timeouts by default.\n\nTo get the same behavior as Requests, set the `timeout` parameter to `None`:\n\n```python\nhttpx.get('https://www.example.com', timeout=None)\n```\n\n## Proxy keys\n\nHTTPX uses the mounts argument for HTTP proxying and transport routing.\nIt can do much more than proxies and allows you to configure more than just the proxy route.\nFor more detailed documentation, see [Mounting Transports](advanced/transports.md#mounting-transports).\n\nWhen using `httpx.Client(mounts={...})` to map to a selection of different transports, we use full URL schemes, such as `mounts={\"http://\": ..., \"https://\": ...}`.\n\nThis is different to the `requests` usage of `proxies={\"http\": ..., \"https\": ...}`.\n\nThis change is for better consistency with more complex mappings, that might also include domain names, such as `mounts={\"all://\": ..., httpx.HTTPTransport(proxy=\"all://www.example.com\": None})` which maps all requests onto a proxy, except for requests to \"www.example.com\" which have an explicit exclusion.\n\nAlso note that `requests.Session.request(...)` allows a `proxies=...` parameter, whereas `httpx.Client.request(...)` does not allow `mounts=...`.\n\n## SSL configuration\n\nWhen using a `Client` instance, the ssl configurations should always be passed on client instantiation, rather than passed to the request method.\n\nIf you need more than one different SSL configuration, you should use different client instances for each SSL configuration.\n\n## Request body on HTTP methods\n\nThe HTTP `GET`, `DELETE`, `HEAD`, and `OPTIONS` methods are specified as not supporting a request body. To stay in line with this, the `.get`, `.delete`, `.head` and `.options` functions do not support `content`, `files`, `data`, or `json` arguments.\n\nIf you really do need to send request data using these http methods you should use the generic `.request` function instead.\n\n```python\nhttpx.request(\n  method=\"DELETE\",\n  url=\"https://www.example.com/\",\n  content=b'A request body on a DELETE request.'\n)\n```\n\n## Checking for success and failure responses\n\nWe don't support `response.is_ok` since the naming is ambiguous there, and might incorrectly imply an equivalence to `response.status_code == codes.OK`. Instead we provide the `response.is_success` property, which can be used to check for a 2xx response.\n\n## Request instantiation\n\nThere is no notion of [prepared requests](https://requests.readthedocs.io/en/stable/user/advanced/#prepared-requests) in HTTPX. If you need to customize request instantiation, see [Request instances](advanced/clients.md#request-instances).\n\nBesides, `httpx.Request()` does not support the `auth`, `timeout`, `follow_redirects`, `mounts`, `verify` and `cert` parameters. However these are available in `httpx.request`, `httpx.get`, `httpx.post` etc., as well as on [`Client` instances](advanced/clients.md#client-instances).\n\n## Mocking\n\nIf you need to mock HTTPX the same way that test utilities like `responses` and `requests-mock` does for `requests`, see [RESPX](https://github.com/lundberg/respx).\n\n## Caching\n\nIf you use `cachecontrol` or `requests-cache` to add HTTP Caching support to the `requests` library, you can use [Hishel](https://hishel.com) for HTTPX.\n\n## Networking layer\n\n`requests` defers most of its HTTP networking code to the excellent [`urllib3` library](https://urllib3.readthedocs.io/en/latest/).\n\nOn the other hand, HTTPX uses [HTTPCore](https://github.com/encode/httpcore) as its core HTTP networking layer, which is a different project than `urllib3`.\n\n## Query Parameters\n\n`requests` omits `params` whose values are `None` (e.g. `requests.get(..., params={\"foo\": None})`). This is not supported by HTTPX.\n\nFor both query params (`params=`) and form data (`data=`), `requests` supports sending a list of tuples (e.g. `requests.get(..., params=[('key1', 'value1'), ('key1', 'value2')])`). This is not supported by HTTPX. Instead, use a dictionary with lists as values. E.g.: `httpx.get(..., params={'key1': ['value1', 'value2']})` or with form data: `httpx.post(..., data={'key1': ['value1', 'value2']})`.\n\n## Event Hooks\n\n`requests` allows event hooks to mutate `Request` and `Response` objects. See [examples](https://requests.readthedocs.io/en/master/user/advanced/#event-hooks) given in the documentation for `requests`.\n\nIn HTTPX, event hooks may access properties of requests and responses, but event hook callbacks cannot mutate the original request/response.\n\nIf you are looking for more control, consider checking out [Custom Transports](advanced/transports.md#custom-transports).\n\n## Exceptions and Errors\n\n`requests` exception hierarchy is slightly different to the `httpx` exception hierarchy. `requests` exposes a top level `RequestException`, where as `httpx` exposes a top level `HTTPError`. see the exceptions exposes in requests [here](https://requests.readthedocs.io/en/latest/_modules/requests/exceptions/). See the `httpx` error hierarchy [here](https://www.python-httpx.org/exceptions/).\n"
  },
  {
    "path": "docs/contributing.md",
    "content": "# Contributing\n\nThank you for being interested in contributing to HTTPX.\nThere are many ways you can contribute to the project:\n\n- Try HTTPX and [report bugs/issues you find](https://github.com/encode/httpx/issues/new)\n- [Implement new features](https://github.com/encode/httpx/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)\n- [Review Pull Requests of others](https://github.com/encode/httpx/pulls)\n- Write documentation\n- Participate in discussions\n\n## Reporting Bugs or Other Issues\n\nFound something that HTTPX should support?\nStumbled upon some unexpected behaviour?\n\nContributions should generally start out with [a discussion](https://github.com/encode/httpx/discussions).\nPossible bugs may be raised as a \"Potential Issue\" discussion, feature requests may\nbe raised as an \"Ideas\" discussion. We can then determine if the discussion needs\nto be escalated into an \"Issue\" or not, or if we'd consider a pull request.\n\nTry to be more descriptive as you can and in case of a bug report,\nprovide as much information as possible like:\n\n- OS platform\n- Python version\n- Installed dependencies and versions (`python -m pip freeze`)\n- Code snippet\n- Error traceback\n\nYou should always try to reduce any examples to the *simplest possible case*\nthat demonstrates the issue.\n\nSome possibly useful tips for narrowing down potential issues...\n\n- Does the issue exist on HTTP/1.1, or HTTP/2, or both?\n- Does the issue exist with `Client`, `AsyncClient`, or both?\n- When using `AsyncClient` does the issue exist when using `asyncio` or `trio`, or both?\n\n## Development\n\nTo start developing HTTPX create a **fork** of the\n[HTTPX repository](https://github.com/encode/httpx) on GitHub.\n\nThen clone your fork with the following command replacing `YOUR-USERNAME` with\nyour GitHub username:\n\n```shell\n$ git clone https://github.com/YOUR-USERNAME/httpx\n```\n\nYou can now install the project and its dependencies using:\n\n```shell\n$ cd httpx\n$ scripts/install\n```\n\n## Testing and Linting\n\nWe use custom shell scripts to automate testing, linting,\nand documentation building workflow.\n\nTo run the tests, use:\n\n```shell\n$ scripts/test\n```\n\n!!! warning\n    The test suite spawns testing servers on ports **8000** and **8001**.\n    Make sure these are not in use, so the tests can run properly.\n\nAny additional arguments will be passed to `pytest`. See the [pytest documentation](https://docs.pytest.org/en/latest/how-to/usage.html) for more information.\n\nFor example, to run a single test script:\n\n```shell\n$ scripts/test tests/test_multipart.py\n```\n\nTo run the code auto-formatting:\n\n```shell\n$ scripts/lint\n```\n\nLastly, to run code checks separately (they are also run as part of `scripts/test`), run:\n\n```shell\n$ scripts/check\n```\n\n## Documenting\n\nDocumentation pages are located under the `docs/` folder.\n\nTo run the documentation site locally (useful for previewing changes), use:\n\n```shell\n$ scripts/docs\n```\n\n## Resolving Build / CI Failures\n\nOnce you've submitted your pull request, the test suite will automatically run, and the results will show up in GitHub.\nIf the test suite fails, you'll want to click through to the \"Details\" link, and try to identify why the test suite failed.\n\n<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img src=\"https://raw.githubusercontent.com/encode/httpx/master/docs/img/gh-actions-fail.png\" alt='Failing PR commit status'>\n</p>\n\nHere are some common ways the test suite can fail:\n\n### Check Job Failed\n\n<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img src=\"https://raw.githubusercontent.com/encode/httpx/master/docs/img/gh-actions-fail-check.png\" alt='Failing GitHub action lint job'>\n</p>\n\nThis job failing means there is either a code formatting issue or type-annotation issue.\nYou can look at the job output to figure out why it's failed or within a shell run:\n\n```shell\n$ scripts/check\n```\n\nIt may be worth it to run `$ scripts/lint` to attempt auto-formatting the code\nand if that job succeeds commit the changes.\n\n### Docs Job Failed\n\nThis job failing means the documentation failed to build. This can happen for\na variety of reasons like invalid markdown or missing configuration within `mkdocs.yml`.\n\n### Python 3.X Job Failed\n\n<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img src=\"https://raw.githubusercontent.com/encode/httpx/master/docs/img/gh-actions-fail-test.png\" alt='Failing GitHub action test job'>\n</p>\n\nThis job failing means the unit tests failed or not all code paths are covered by unit tests.\n\nIf tests are failing you will see this message under the coverage report:\n\n`=== 1 failed, 435 passed, 1 skipped, 1 xfailed in 11.09s ===`\n\nIf tests succeed but coverage doesn't reach our current threshold, you will see this\nmessage under the coverage report:\n\n`FAIL Required test coverage of 100% not reached. Total coverage: 99.00%`\n\n## Releasing\n\n*This section is targeted at HTTPX maintainers.*\n\nBefore releasing a new version, create a pull request that includes:\n\n- **An update to the changelog**:\n    - We follow the format from [keepachangelog](https://keepachangelog.com/en/1.0.0/).\n    - [Compare](https://github.com/encode/httpx/compare/) `master` with the tag of the latest release, and list all entries that are of interest to our users:\n        - Things that **must** go in the changelog: added, changed, deprecated or removed features, and bug fixes.\n        - Things that **should not** go in the changelog: changes to documentation, tests or tooling.\n        - Try sorting entries in descending order of impact / importance.\n        - Keep it concise and to-the-point. 🎯\n- **A version bump**: see `__version__.py`.\n\nFor an example, see [#1006](https://github.com/encode/httpx/pull/1006).\n\nOnce the release PR is merged, create a\n[new release](https://github.com/encode/httpx/releases/new) including:\n\n- Tag version like `0.13.3`.\n- Release title `Version 0.13.3`\n- Description copied from the changelog.\n\nOnce created this release will be automatically uploaded to PyPI.\n\nIf something goes wrong with the PyPI job the release can be published using the\n`scripts/publish` script.\n\n## Development proxy setup\n\nTo test and debug requests via a proxy it's best to run a proxy server locally.\nAny server should do but HTTPCore's test suite uses\n[`mitmproxy`](https://mitmproxy.org/) which is written in Python, it's fully\nfeatured and has excellent UI and tools for introspection of requests.\n\nYou can install `mitmproxy` using `pip install mitmproxy` or [several\nother ways](https://docs.mitmproxy.org/stable/overview-installation/).\n\n`mitmproxy` does require setting up local TLS certificates for HTTPS requests,\nas its main purpose is to allow developers to inspect requests that pass through\nit. We can set them up follows:\n\n1. [`pip install trustme-cli`](https://github.com/sethmlarson/trustme-cli/).\n2. `trustme-cli -i example.org www.example.org`, assuming you want to test\nconnecting to that domain, this will create three files: `server.pem`,\n`server.key` and `client.pem`.\n3. `mitmproxy` requires a PEM file that includes the private key and the\ncertificate so we need to concatenate them:\n`cat server.key server.pem > server.withkey.pem`.\n4. Start the proxy server `mitmproxy --certs server.withkey.pem`, or use the\n[other mitmproxy commands](https://docs.mitmproxy.org/stable/) with different\nUI options.\n\nAt this point the server is ready to start serving requests, you'll need to\nconfigure HTTPX as described in the\n[proxy section](https://www.python-httpx.org/advanced/proxies/#http-proxies) and\nthe [SSL certificates section](https://www.python-httpx.org/advanced/ssl/),\nthis is where our previously generated `client.pem` comes in:\n\n```python\nctx = ssl.create_default_context(cafile=\"/path/to/client.pem\")\nclient = httpx.Client(proxy=\"http://127.0.0.1:8080/\", verify=ctx)\n```\n\nNote, however, that HTTPS requests will only succeed to the host specified\nin the SSL/TLS certificate we generated, HTTPS requests to other hosts will\nraise an error like:\n\n```\nssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate\nverify failed: Hostname mismatch, certificate is not valid for\n'duckduckgo.com'. (_ssl.c:1108)\n```\n\nIf you want to make requests to more hosts you'll need to regenerate the\ncertificates and include all the hosts you intend to connect to in the\nseconds step, i.e.\n\n`trustme-cli -i example.org www.example.org duckduckgo.com www.duckduckgo.com`\n"
  },
  {
    "path": "docs/css/custom.css",
    "content": "div.autodoc-docstring {\n  padding-left: 20px;\n  margin-bottom: 30px;\n  border-left: 5px solid rgba(230, 230, 230);\n}\n\ndiv.autodoc-members {\n  padding-left: 20px;\n  margin-bottom: 15px;\n}\n"
  },
  {
    "path": "docs/environment_variables.md",
    "content": "# Environment Variables\n\nThe HTTPX library can be configured via environment variables.\nEnvironment variables are used by default. To ignore environment variables, `trust_env` has to be set `False`. There are two ways to set `trust_env` to disable environment variables:\n\n* On the client via `httpx.Client(trust_env=False)`.\n* Using the top-level API, such as `httpx.get(\"<url>\", trust_env=False)`.\n\nHere is a list of environment variables that HTTPX recognizes and what function they serve:\n\n## Proxies\n\nThe environment variables documented below are used as a convention by various HTTP tooling, including:\n\n* [cURL](https://github.com/curl/curl/blob/master/docs/MANUAL.md#environment-variables)\n* [requests](https://github.com/psf/requests/blob/master/docs/user/advanced.rst#proxies)\n\nFor more information on using proxies in HTTPX, see [HTTP Proxying](advanced/proxies.md#http-proxying).\n\n### `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`\n\nValid values: A URL to a proxy\n\n`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY` set the proxy to be used for `http`, `https`, or all requests respectively.\n\n```bash\nexport HTTP_PROXY=http://my-external-proxy.com:1234\n\n# This request will be sent through the proxy\npython -c \"import httpx; httpx.get('http://example.com')\"\n\n# This request will be sent directly, as we set `trust_env=False`\npython -c \"import httpx; httpx.get('http://example.com', trust_env=False)\"\n\n```\n\n### `NO_PROXY`\n\nValid values: a comma-separated list of hostnames/urls\n\n`NO_PROXY` disables the proxy for specific urls\n\n```bash\nexport HTTP_PROXY=http://my-external-proxy.com:1234\nexport NO_PROXY=http://127.0.0.1,python-httpx.org\n\n# As in the previous example, this request will be sent through the proxy\npython -c \"import httpx; httpx.get('http://example.com')\"\n\n# These requests will be sent directly, bypassing the proxy\npython -c \"import httpx; httpx.get('http://127.0.0.1:5000/my-api')\"\npython -c \"import httpx; httpx.get('https://www.python-httpx.org')\"\n```\n\n## `SSL_CERT_FILE`\n\nValid values: a filename\n\nIf this environment variable is set then HTTPX will load\nCA certificate from the specified file instead of the default\nlocation.\n\nExample:\n\n```console\nSSL_CERT_FILE=/path/to/ca-certs/ca-bundle.crt python -c \"import httpx; httpx.get('https://example.com')\"\n```\n\n## `SSL_CERT_DIR`\n\nValid values: a directory following an [OpenSSL specific layout](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_load_verify_locations.html).\n\nIf this environment variable is set and the directory follows an [OpenSSL specific layout](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_load_verify_locations.html) (ie. you ran `c_rehash`) then HTTPX will load CA certificates from this directory instead of the default location.\n\nExample:\n\n```console\nSSL_CERT_DIR=/path/to/ca-certs/ python -c \"import httpx; httpx.get('https://example.com')\"\n```\n"
  },
  {
    "path": "docs/exceptions.md",
    "content": "# Exceptions\n\nThis page lists exceptions that may be raised when using HTTPX.\n\nFor an overview of how to work with HTTPX exceptions, see [Exceptions (Quickstart)](quickstart.md#exceptions).\n\n## The exception hierarchy\n\n* HTTPError\n    * RequestError\n        * TransportError\n            * TimeoutException\n                * ConnectTimeout\n                * ReadTimeout\n                * WriteTimeout\n                * PoolTimeout\n            * NetworkError\n                * ConnectError\n                * ReadError\n                * WriteError\n                * CloseError\n            * ProtocolError\n                * LocalProtocolError\n                * RemoteProtocolError\n            * ProxyError\n            * UnsupportedProtocol\n        * DecodingError\n        * TooManyRedirects\n    * HTTPStatusError\n* InvalidURL\n* CookieConflict\n* StreamError\n    * StreamConsumed\n    * ResponseNotRead\n    * RequestNotRead\n    * StreamClosed\n\n---\n\n## Exception classes\n\n::: httpx.HTTPError\n    :docstring:\n\n::: httpx.RequestError\n    :docstring:\n\n::: httpx.TransportError\n    :docstring:\n\n::: httpx.TimeoutException\n    :docstring:\n\n::: httpx.ConnectTimeout\n    :docstring:\n\n::: httpx.ReadTimeout\n    :docstring:\n\n::: httpx.WriteTimeout\n    :docstring:\n\n::: httpx.PoolTimeout\n    :docstring:\n\n::: httpx.NetworkError\n    :docstring:\n\n::: httpx.ConnectError\n    :docstring:\n\n::: httpx.ReadError\n    :docstring:\n\n::: httpx.WriteError\n    :docstring:\n\n::: httpx.CloseError\n    :docstring:\n\n::: httpx.ProtocolError\n    :docstring:\n\n::: httpx.LocalProtocolError\n    :docstring:\n\n::: httpx.RemoteProtocolError\n    :docstring:\n\n::: httpx.ProxyError\n    :docstring:\n\n::: httpx.UnsupportedProtocol\n    :docstring:\n\n::: httpx.DecodingError\n    :docstring:\n\n::: httpx.TooManyRedirects\n    :docstring:\n\n::: httpx.HTTPStatusError\n    :docstring:\n\n::: httpx.InvalidURL\n    :docstring:\n\n::: httpx.CookieConflict\n    :docstring:\n\n::: httpx.StreamError\n    :docstring:\n\n::: httpx.StreamConsumed\n    :docstring:\n\n::: httpx.StreamClosed\n    :docstring:\n\n::: httpx.ResponseNotRead\n    :docstring:\n\n::: httpx.RequestNotRead\n    :docstring:\n"
  },
  {
    "path": "docs/http2.md",
    "content": "# HTTP/2\n\nHTTP/2 is a major new iteration of the HTTP protocol, that provides a far more\nefficient transport, with potential performance benefits. HTTP/2 does not change\nthe core semantics of the request or response, but alters the way that data is\nsent to and from the server.\n\nRather than the text format that HTTP/1.1 uses, HTTP/2 is a binary format.\nThe binary format provides full request and response multiplexing, and efficient\ncompression of HTTP headers. The stream multiplexing means that where HTTP/1.1\nrequires one TCP stream for each concurrent request, HTTP/2 allows a single TCP\nstream to handle multiple concurrent requests.\n\nHTTP/2 also provides support for functionality such as response prioritization,\nand server push.\n\nFor a comprehensive guide to HTTP/2 you may want to check out \"[http2 explained](https://http2-explained.haxx.se/)\".\n\n## Enabling HTTP/2\n\nWhen using the `httpx` client, HTTP/2 support is not enabled by default, because\nHTTP/1.1 is a mature, battle-hardened transport layer, and our HTTP/1.1\nimplementation may be considered the more robust option at this point in time.\nIt is possible that a future version of `httpx` may enable HTTP/2 support by default.\n\nIf you're issuing highly concurrent requests you might want to consider\ntrying out our HTTP/2 support. You can do so by first making sure to install\nthe optional HTTP/2 dependencies...\n\n```shell\n$ pip install httpx[http2]\n```\n\nAnd then instantiating a client with HTTP/2 support enabled:\n\n```python\nclient = httpx.AsyncClient(http2=True)\n...\n```\n\nYou can also instantiate a client as a context manager, to ensure that all\nHTTP connections are nicely scoped, and will be closed once the context block\nis exited.\n\n```python\nasync with httpx.AsyncClient(http2=True) as client:\n    ...\n```\n\nHTTP/2 support is available on both `Client` and `AsyncClient`, although it's\ntypically more useful in async contexts if you're issuing lots of concurrent\nrequests.\n\n## Inspecting the HTTP version\n\nEnabling HTTP/2 support on the client does not *necessarily* mean that your\nrequests and responses will be transported over HTTP/2, since both the client\n*and* the server need to support HTTP/2. If you connect to a server that only\nsupports HTTP/1.1 the client will use a standard HTTP/1.1 connection instead.\n\nYou can determine which version of the HTTP protocol was used by examining\nthe `.http_version` property on the response.\n\n```python\nclient = httpx.AsyncClient(http2=True)\nresponse = await client.get(...)\nprint(response.http_version)  # \"HTTP/1.0\", \"HTTP/1.1\", or \"HTTP/2\".\n```\n"
  },
  {
    "path": "docs/index.md",
    "content": "<p align=\"center\" style=\"margin: 0 0 10px\">\n  <img width=\"350\" height=\"208\" src=\"https://raw.githubusercontent.com/encode/httpx/master/docs/img/butterfly.png\" alt='HTTPX'>\n</p>\n\n<h1 align=\"center\" style=\"font-size: 3rem; margin: -15px 0\">\nHTTPX\n</h1>\n\n---\n\n<div align=\"center\">\n<p>\n<a href=\"https://github.com/encode/httpx/actions\">\n    <img src=\"https://github.com/encode/httpx/workflows/Test%20Suite/badge.svg\" alt=\"Test Suite\">\n</a>\n<a href=\"https://pypi.org/project/httpx/\">\n    <img src=\"https://badge.fury.io/py/httpx.svg\" alt=\"Package version\">\n</a>\n</p>\n\n<em>A next-generation HTTP client for Python.</em>\n</div>\n\nHTTPX is a fully featured HTTP client for Python 3, which provides sync and async APIs, and support for both HTTP/1.1 and HTTP/2.\n\n---\n\nInstall HTTPX using pip:\n\n```shell\n$ pip install httpx\n```\n\nNow, let's get started:\n\n```pycon\n>>> import httpx\n>>> r = httpx.get('https://www.example.org/')\n>>> r\n<Response [200 OK]>\n>>> r.status_code\n200\n>>> r.headers['content-type']\n'text/html; charset=UTF-8'\n>>> r.text\n'<!doctype html>\\n<html>\\n<head>\\n<title>Example Domain</title>...'\n```\n\nOr, using the command-line client.\n\n```shell\n# The command line client is an optional dependency.\n$ pip install 'httpx[cli]'\n```\n\nWhich now allows us to use HTTPX directly from the command-line...\n\n![httpx --help](img/httpx-help.png)\n\nSending a request...\n\n![httpx http://httpbin.org/json](img/httpx-request.png)\n\n## Features\n\nHTTPX builds on the well-established usability of `requests`, and gives you:\n\n* A broadly [requests-compatible API](compatibility.md).\n* Standard synchronous interface, but with [async support if you need it](async.md).\n* HTTP/1.1 [and HTTP/2 support](http2.md).\n* Ability to make requests directly to [WSGI applications](advanced/transports.md#wsgi-transport) or [ASGI applications](advanced/transports.md#asgi-transport).\n* Strict timeouts everywhere.\n* Fully type annotated.\n* 100% test coverage.\n\nPlus all the standard features of `requests`...\n\n* International Domains and URLs\n* Keep-Alive & Connection Pooling\n* Sessions with Cookie Persistence\n* Browser-style SSL Verification\n* Basic/Digest Authentication\n* Elegant Key/Value Cookies\n* Automatic Decompression\n* Automatic Content Decoding\n* Unicode Response Bodies\n* Multipart File Uploads\n* HTTP(S) Proxy Support\n* Connection Timeouts\n* Streaming Downloads\n* .netrc Support\n* Chunked Requests\n\n## Documentation\n\nFor a run-through of all the basics, head over to the [QuickStart](quickstart.md).\n\nFor more advanced topics, see the **Advanced** section,\nthe [async support](async.md) section, or the [HTTP/2](http2.md) section.\n\nThe [Developer Interface](api.md) provides a comprehensive API reference.\n\nTo find out about tools that integrate with HTTPX, see [Third Party Packages](third_party_packages.md).\n\n## Dependencies\n\nThe HTTPX project relies on these excellent libraries:\n\n* `httpcore` - The underlying transport implementation for `httpx`.\n  * `h11` - HTTP/1.1 support.\n* `certifi` - SSL certificates.\n* `idna` - Internationalized domain name support.\n* `sniffio` - Async library autodetection.\n\nAs well as these optional installs:\n\n* `h2` - HTTP/2 support. *(Optional, with `httpx[http2]`)*\n* `socksio` - SOCKS proxy support. *(Optional, with `httpx[socks]`)*\n* `rich` - Rich terminal support. *(Optional, with `httpx[cli]`)*\n* `click` - Command line client support. *(Optional, with `httpx[cli]`)*\n* `brotli` or `brotlicffi` - Decoding for \"brotli\" compressed responses. *(Optional, with `httpx[brotli]`)*\n* `zstandard` - Decoding for \"zstd\" compressed responses. *(Optional, with `httpx[zstd]`)*\n\nA huge amount of credit is due to `requests` for the API layout that\nmuch of this work follows, as well as to `urllib3` for plenty of design\ninspiration around the lower-level networking details.\n\n## Installation\n\nInstall with pip:\n\n```shell\n$ pip install httpx\n```\n\nOr, to include the optional HTTP/2 support, use:\n\n```shell\n$ pip install httpx[http2]\n```\n\nTo include the optional brotli and zstandard decoders support, use:\n\n```shell\n$ pip install httpx[brotli,zstd]\n```\n\nHTTPX requires Python 3.9+\n\n[sync-support]: https://github.com/encode/httpx/issues/572\n"
  },
  {
    "path": "docs/logging.md",
    "content": "# Logging\n\nIf you need to inspect the internal behaviour of `httpx`, you can use Python's standard logging to output information about the underlying network behaviour.\n\nFor example, the following configuration...\n\n```python\nimport logging\nimport httpx\n\nlogging.basicConfig(\n    format=\"%(levelname)s [%(asctime)s] %(name)s - %(message)s\",\n    datefmt=\"%Y-%m-%d %H:%M:%S\",\n    level=logging.DEBUG\n)\n\nhttpx.get(\"https://www.example.com\")\n```\n\nWill send debug level output to the console, or wherever `stdout` is directed too...\n\n```\nDEBUG [2024-09-28 17:27:40] httpcore.connection - connect_tcp.started host='www.example.com' port=443 local_address=None timeout=5.0 socket_options=None\nDEBUG [2024-09-28 17:27:41] httpcore.connection - connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x101f1e8e0>\nDEBUG [2024-09-28 17:27:41] httpcore.connection - start_tls.started ssl_context=SSLContext(verify=True) server_hostname='www.example.com' timeout=5.0\nDEBUG [2024-09-28 17:27:41] httpcore.connection - start_tls.complete return_value=<httpcore._backends.sync.SyncStream object at 0x1020f49a0>\nDEBUG [2024-09-28 17:27:41] httpcore.http11 - send_request_headers.started request=<Request [b'GET']>\nDEBUG [2024-09-28 17:27:41] httpcore.http11 - send_request_headers.complete\nDEBUG [2024-09-28 17:27:41] httpcore.http11 - send_request_body.started request=<Request [b'GET']>\nDEBUG [2024-09-28 17:27:41] httpcore.http11 - send_request_body.complete\nDEBUG [2024-09-28 17:27:41] httpcore.http11 - receive_response_headers.started request=<Request [b'GET']>\nDEBUG [2024-09-28 17:27:41] httpcore.http11 - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'OK', [(b'Content-Encoding', b'gzip'), (b'Accept-Ranges', b'bytes'), (b'Age', b'407727'), (b'Cache-Control', b'max-age=604800'), (b'Content-Type', b'text/html; charset=UTF-8'), (b'Date', b'Sat, 28 Sep 2024 13:27:42 GMT'), (b'Etag', b'\"3147526947+gzip\"'), (b'Expires', b'Sat, 05 Oct 2024 13:27:42 GMT'), (b'Last-Modified', b'Thu, 17 Oct 2019 07:18:26 GMT'), (b'Server', b'ECAcc (dcd/7D43)'), (b'Vary', b'Accept-Encoding'), (b'X-Cache', b'HIT'), (b'Content-Length', b'648')])\nINFO [2024-09-28 17:27:41] httpx - HTTP Request: GET https://www.example.com \"HTTP/1.1 200 OK\"\nDEBUG [2024-09-28 17:27:41] httpcore.http11 - receive_response_body.started request=<Request [b'GET']>\nDEBUG [2024-09-28 17:27:41] httpcore.http11 - receive_response_body.complete\nDEBUG [2024-09-28 17:27:41] httpcore.http11 - response_closed.started\nDEBUG [2024-09-28 17:27:41] httpcore.http11 - response_closed.complete\nDEBUG [2024-09-28 17:27:41] httpcore.connection - close.started\nDEBUG [2024-09-28 17:27:41] httpcore.connection - close.complete\n```\n\nLogging output includes information from both the high-level `httpx` logger, and the network-level `httpcore` logger, which can be configured separately.\n\nFor handling more complex logging configurations you might want to use the dictionary configuration style...\n\n```python\nimport logging.config\nimport httpx\n\nLOGGING_CONFIG = {\n    \"version\": 1,\n    \"handlers\": {\n        \"default\": {\n            \"class\": \"logging.StreamHandler\",\n            \"formatter\": \"http\",\n            \"stream\": \"ext://sys.stderr\"\n        }\n    },\n    \"formatters\": {\n        \"http\": {\n            \"format\": \"%(levelname)s [%(asctime)s] %(name)s - %(message)s\",\n            \"datefmt\": \"%Y-%m-%d %H:%M:%S\",\n        }\n    },\n    'loggers': {\n        'httpx': {\n            'handlers': ['default'],\n            'level': 'DEBUG',\n        },\n        'httpcore': {\n            'handlers': ['default'],\n            'level': 'DEBUG',\n        },\n    }\n}\n\nlogging.config.dictConfig(LOGGING_CONFIG)\nhttpx.get('https://www.example.com')\n```\n\nThe exact formatting of the debug logging may be subject to change across different versions of `httpx` and `httpcore`. If you need to rely on a particular format it is recommended that you pin installation of these packages to fixed versions.\n"
  },
  {
    "path": "docs/overrides/partials/nav.html",
    "content": "{% import \"partials/nav-item.html\" as item with context %}\n\n<!-- Determine class according to configuration -->\n {% set class = \"md-nav md-nav--primary\" %}\n {% if \"navigation.tabs\" in features %}\n   {% set class = class ~ \" md-nav--lifted\" %}\n {% endif %}\n {% if \"toc.integrate\" in features %}\n   {% set class = class ~ \" md-nav--integrated\" %}\n {% endif %}\n\n <!-- Main navigation -->\n <nav\n   class=\"{{ class }}\"\n   aria-label=\"{{ lang.t('nav.title') }}\"\n   data-md-level=\"0\"\n >\n\n   <!-- Site title -->\n   <label class=\"md-nav__title\" for=\"__drawer\">\n     <a\n       href=\"{{ config.extra.homepage | d(nav.homepage.url, true) | url }}\"\n       title=\"{{ config.site_name | e }}\"\n       class=\"md-nav__button md-logo\"\n       aria-label=\"{{ config.site_name }}\"\n       data-md-component=\"logo\"\n     >\n       {% include \"partials/logo.html\" %}\n     </a>\n     {{ config.site_name }}\n   </label>\n\n   <!-- Repository information -->\n   {% if config.repo_url %}\n     <div class=\"md-nav__source\">\n       {% include \"partials/source.html\" %}\n     </div>\n   {% endif %}\n\n   <!-- Navigation list -->\n   <ul class=\"md-nav__list\" data-md-scrollfix>\n     {% for nav_item in nav %}\n       {% set path = \"__nav_\" ~ loop.index %}\n       {{ item.render(nav_item, path, 1) }}\n     {% endfor %}\n   </ul>\n\n   <ul class=\"md-nav__list\" data-md-scrollfix style=\"padding-top: 15px; padding-left: 10px\">\n     <div>\n       <a href=\"https://speakeasy.com\"><img src=\"/img/speakeasy.png\" width=150px style=></img></a>\n     </div>\n   </ul>\n </nav>\n "
  },
  {
    "path": "docs/quickstart.md",
    "content": "# QuickStart\n\nFirst, start by importing HTTPX:\n\n```pycon\n>>> import httpx\n```\n\nNow, let’s try to get a webpage.\n\n```pycon\n>>> r = httpx.get('https://httpbin.org/get')\n>>> r\n<Response [200 OK]>\n```\n\nSimilarly, to make an HTTP POST request:\n\n```pycon\n>>> r = httpx.post('https://httpbin.org/post', data={'key': 'value'})\n```\n\nThe PUT, DELETE, HEAD, and OPTIONS requests all follow the same style:\n\n```pycon\n>>> r = httpx.put('https://httpbin.org/put', data={'key': 'value'})\n>>> r = httpx.delete('https://httpbin.org/delete')\n>>> r = httpx.head('https://httpbin.org/get')\n>>> r = httpx.options('https://httpbin.org/get')\n```\n\n## Passing Parameters in URLs\n\nTo include URL query parameters in the request, use the `params` keyword:\n\n```pycon\n>>> params = {'key1': 'value1', 'key2': 'value2'}\n>>> r = httpx.get('https://httpbin.org/get', params=params)\n```\n\nTo see how the values get encoding into the URL string, we can inspect the\nresulting URL that was used to make the request:\n\n```pycon\n>>> r.url\nURL('https://httpbin.org/get?key2=value2&key1=value1')\n```\n\nYou can also pass a list of items as a value:\n\n```pycon\n>>> params = {'key1': 'value1', 'key2': ['value2', 'value3']}\n>>> r = httpx.get('https://httpbin.org/get', params=params)\n>>> r.url\nURL('https://httpbin.org/get?key1=value1&key2=value2&key2=value3')\n```\n\n## Response Content\n\nHTTPX will automatically handle decoding the response content into Unicode text.\n\n```pycon\n>>> r = httpx.get('https://www.example.org/')\n>>> r.text\n'<!doctype html>\\n<html>\\n<head>\\n<title>Example Domain</title>...'\n```\n\nYou can inspect what encoding will be used to decode the response.\n\n```pycon\n>>> r.encoding\n'UTF-8'\n```\n\nIn some cases the response may not contain an explicit encoding, in which case HTTPX\nwill attempt to automatically determine an encoding to use.\n\n```pycon\n>>> r.encoding\nNone\n>>> r.text\n'<!doctype html>\\n<html>\\n<head>\\n<title>Example Domain</title>...'\n```\n\nIf you need to override the standard behaviour and explicitly set the encoding to\nuse, then you can do that too.\n\n```pycon\n>>> r.encoding = 'ISO-8859-1'\n```\n\n## Binary Response Content\n\nThe response content can also be accessed as bytes, for non-text responses:\n\n```pycon\n>>> r.content\nb'<!doctype html>\\n<html>\\n<head>\\n<title>Example Domain</title>...'\n```\n\nAny `gzip` and `deflate` HTTP response encodings will automatically\nbe decoded for you. If `brotlipy` is installed, then the `brotli` response\nencoding will be supported. If `zstandard` is installed, then `zstd`\nresponse encodings will also be supported.\n\nFor example, to create an image from binary data returned by a request, you can use the following code:\n\n```pycon\n>>> from PIL import Image\n>>> from io import BytesIO\n>>> i = Image.open(BytesIO(r.content))\n```\n\n## JSON Response Content\n\nOften Web API responses will be encoded as JSON.\n\n```pycon\n>>> r = httpx.get('https://api.github.com/events')\n>>> r.json()\n[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...' ...  }}]\n```\n\n## Custom Headers\n\nTo include additional headers in the outgoing request, use the `headers` keyword argument:\n\n```pycon\n>>> url = 'https://httpbin.org/headers'\n>>> headers = {'user-agent': 'my-app/0.0.1'}\n>>> r = httpx.get(url, headers=headers)\n```\n\n## Sending Form Encoded Data\n\nSome types of HTTP requests, such as `POST` and `PUT` requests, can include data\nin the request body. One common way of including that is as form-encoded data,\nwhich is used for HTML forms.\n\n```pycon\n>>> data = {'key1': 'value1', 'key2': 'value2'}\n>>> r = httpx.post(\"https://httpbin.org/post\", data=data)\n>>> print(r.text)\n{\n  ...\n  \"form\": {\n    \"key2\": \"value2\",\n    \"key1\": \"value1\"\n  },\n  ...\n}\n```\n\nForm encoded data can also include multiple values from a given key.\n\n```pycon\n>>> data = {'key1': ['value1', 'value2']}\n>>> r = httpx.post(\"https://httpbin.org/post\", data=data)\n>>> print(r.text)\n{\n  ...\n  \"form\": {\n    \"key1\": [\n      \"value1\",\n      \"value2\"\n    ]\n  },\n  ...\n}\n```\n\n## Sending Multipart File Uploads\n\nYou can also upload files, using HTTP multipart encoding:\n\n```pycon\n>>> with open('report.xls', 'rb') as report_file:\n...     files = {'upload-file': report_file}\n...     r = httpx.post(\"https://httpbin.org/post\", files=files)\n>>> print(r.text)\n{\n  ...\n  \"files\": {\n    \"upload-file\": \"<... binary content ...>\"\n  },\n  ...\n}\n```\n\nYou can also explicitly set the filename and content type, by using a tuple\nof items for the file value:\n\n```pycon\n>>> with open('report.xls', 'rb') as report_file:\n...     files = {'upload-file': ('report.xls', report_file, 'application/vnd.ms-excel')}\n...     r = httpx.post(\"https://httpbin.org/post\", files=files)\n>>> print(r.text)\n{\n  ...\n  \"files\": {\n    \"upload-file\": \"<... binary content ...>\"\n  },\n  ...\n}\n```\n\nIf you need to include non-file data fields in the multipart form, use the `data=...` parameter:\n\n```pycon\n>>> data = {'message': 'Hello, world!'}\n>>> with open('report.xls', 'rb') as report_file:\n...     files = {'file': report_file}\n...     r = httpx.post(\"https://httpbin.org/post\", data=data, files=files)\n>>> print(r.text)\n{\n  ...\n  \"files\": {\n    \"file\": \"<... binary content ...>\"\n  },\n  \"form\": {\n    \"message\": \"Hello, world!\",\n  },\n  ...\n}\n```\n\n## Sending JSON Encoded Data\n\nForm encoded data is okay if all you need is a simple key-value data structure.\nFor more complicated data structures you'll often want to use JSON encoding instead.\n\n```pycon\n>>> data = {'integer': 123, 'boolean': True, 'list': ['a', 'b', 'c']}\n>>> r = httpx.post(\"https://httpbin.org/post\", json=data)\n>>> print(r.text)\n{\n  ...\n  \"json\": {\n    \"boolean\": true,\n    \"integer\": 123,\n    \"list\": [\n      \"a\",\n      \"b\",\n      \"c\"\n    ]\n  },\n  ...\n}\n```\n\n## Sending Binary Request Data\n\nFor other encodings, you should use the `content=...` parameter, passing\neither a `bytes` type or a generator that yields `bytes`.\n\n```pycon\n>>> content = b'Hello, world'\n>>> r = httpx.post(\"https://httpbin.org/post\", content=content)\n```\n\nYou may also want to set a custom `Content-Type` header when uploading\nbinary data.\n\n## Response Status Codes\n\nWe can inspect the HTTP status code of the response:\n\n```pycon\n>>> r = httpx.get('https://httpbin.org/get')\n>>> r.status_code\n200\n```\n\nHTTPX also includes an easy shortcut for accessing status codes by their text phrase.\n\n```pycon\n>>> r.status_code == httpx.codes.OK\nTrue\n```\n\nWe can raise an exception for any responses which are not a 2xx success code:\n\n```pycon\n>>> not_found = httpx.get('https://httpbin.org/status/404')\n>>> not_found.status_code\n404\n>>> not_found.raise_for_status()\nTraceback (most recent call last):\n  File \"/Users/tomchristie/GitHub/encode/httpcore/httpx/models.py\", line 837, in raise_for_status\n    raise HTTPStatusError(message, response=self)\nhttpx._exceptions.HTTPStatusError: 404 Client Error: Not Found for url: https://httpbin.org/status/404\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404\n```\n\nAny successful response codes will return the `Response` instance rather than raising an exception.\n\n```pycon\n>>> r.raise_for_status()\n```\n\nThe method returns the response instance, allowing you to use it inline. For example:\n\n```pycon\n>>> r = httpx.get('...').raise_for_status()\n>>> data = httpx.get('...').raise_for_status().json()\n```\n\n## Response Headers\n\nThe response headers are available as a dictionary-like interface.\n\n```pycon\n>>> r.headers\nHeaders({\n    'content-encoding': 'gzip',\n    'transfer-encoding': 'chunked',\n    'connection': 'close',\n    'server': 'nginx/1.0.4',\n    'x-runtime': '148ms',\n    'etag': '\"e1ca502697e5c9317743dc078f67693f\"',\n    'content-type': 'application/json'\n})\n```\n\nThe `Headers` data type is case-insensitive, so you can use any capitalization.\n\n```pycon\n>>> r.headers['Content-Type']\n'application/json'\n\n>>> r.headers.get('content-type')\n'application/json'\n```\n\nMultiple values for a single response header are represented as a single comma-separated value, as per [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2):\n\n> A recipient MAY combine multiple header fields with the same field name into one “field-name: field-value” pair, without changing the semantics of the message, by appending each subsequent field-value to the combined field value in order, separated by a comma.\n\n## Streaming Responses\n\nFor large downloads you may want to use streaming responses that do not load the entire response body into memory at once.\n\nYou can stream the binary content of the response...\n\n```pycon\n>>> with httpx.stream(\"GET\", \"https://www.example.com\") as r:\n...     for data in r.iter_bytes():\n...         print(data)\n```\n\nOr the text of the response...\n\n```pycon\n>>> with httpx.stream(\"GET\", \"https://www.example.com\") as r:\n...     for text in r.iter_text():\n...         print(text)\n```\n\nOr stream the text, on a line-by-line basis...\n\n```pycon\n>>> with httpx.stream(\"GET\", \"https://www.example.com\") as r:\n...     for line in r.iter_lines():\n...         print(line)\n```\n\nHTTPX will use universal line endings, normalising all cases to `\\n`.\n\nIn some cases you might want to access the raw bytes on the response without applying any HTTP content decoding. In this case any content encoding that the web server has applied such as `gzip`, `deflate`, `brotli`, or `zstd` will\nnot be automatically decoded.\n\n```pycon\n>>> with httpx.stream(\"GET\", \"https://www.example.com\") as r:\n...     for chunk in r.iter_raw():\n...         print(chunk)\n```\n\nIf you're using streaming responses in any of these ways then the `response.content` and `response.text` attributes will not be available, and will raise errors if accessed. However you can also use the response streaming functionality to conditionally load the response body:\n\n```pycon\n>>> with httpx.stream(\"GET\", \"https://www.example.com\") as r:\n...     if int(r.headers['Content-Length']) < TOO_LONG:\n...         r.read()\n...         print(r.text)\n```\n\n## Cookies\n\nAny cookies that are set on the response can be easily accessed:\n\n```pycon\n>>> r = httpx.get('https://httpbin.org/cookies/set?chocolate=chip')\n>>> r.cookies['chocolate']\n'chip'\n```\n\nTo include cookies in an outgoing request, use the `cookies` parameter:\n\n```pycon\n>>> cookies = {\"peanut\": \"butter\"}\n>>> r = httpx.get('https://httpbin.org/cookies', cookies=cookies)\n>>> r.json()\n{'cookies': {'peanut': 'butter'}}\n```\n\nCookies are returned in a `Cookies` instance, which is a dict-like data structure\nwith additional API for accessing cookies by their domain or path.\n\n```pycon\n>>> cookies = httpx.Cookies()\n>>> cookies.set('cookie_on_domain', 'hello, there!', domain='httpbin.org')\n>>> cookies.set('cookie_off_domain', 'nope.', domain='example.org')\n>>> r = httpx.get('http://httpbin.org/cookies', cookies=cookies)\n>>> r.json()\n{'cookies': {'cookie_on_domain': 'hello, there!'}}\n```\n\n## Redirection and History\n\nBy default, HTTPX will **not** follow redirects for all HTTP methods, although\nthis can be explicitly enabled.\n\nFor example, GitHub redirects all HTTP requests to HTTPS.\n\n```pycon\n>>> r = httpx.get('http://github.com/')\n>>> r.status_code\n301\n>>> r.history\n[]\n>>> r.next_request\n<Request('GET', 'https://github.com/')>\n```\n\nYou can modify the default redirection handling with the `follow_redirects` parameter:\n\n```pycon\n>>> r = httpx.get('http://github.com/', follow_redirects=True)\n>>> r.url\nURL('https://github.com/')\n>>> r.status_code\n200\n>>> r.history\n[<Response [301 Moved Permanently]>]\n```\n\nThe `history` property of the response can be used to inspect any followed redirects.\nIt contains a list of any redirect responses that were followed, in the order\nin which they were made.\n\n## Timeouts\n\nHTTPX defaults to including reasonable timeouts for all network operations,\nmeaning that if a connection is not properly established then it should always\nraise an error rather than hanging indefinitely.\n\nThe default timeout for network inactivity is five seconds. You can modify the\nvalue to be more or less strict:\n\n```pycon\n>>> httpx.get('https://github.com/', timeout=0.001)\n```\n\nYou can also disable the timeout behavior completely...\n\n```pycon\n>>> httpx.get('https://github.com/', timeout=None)\n```\n\nFor advanced timeout management, see [Timeout fine-tuning](advanced/timeouts.md#fine-tuning-the-configuration).\n\n## Authentication\n\nHTTPX supports Basic and Digest HTTP authentication.\n\nTo provide Basic authentication credentials, pass a 2-tuple of\nplaintext `str` or `bytes` objects as the `auth` argument to the request\nfunctions:\n\n```pycon\n>>> httpx.get(\"https://example.com\", auth=(\"my_user\", \"password123\"))\n```\n\nTo provide credentials for Digest authentication you'll need to instantiate\na `DigestAuth` object with the plaintext username and password as arguments.\nThis object can be then passed as the `auth` argument to the request methods\nas above:\n\n```pycon\n>>> auth = httpx.DigestAuth(\"my_user\", \"password123\")\n>>> httpx.get(\"https://example.com\", auth=auth)\n<Response [200 OK]>\n```\n\n## Exceptions\n\nHTTPX will raise exceptions if an error occurs.\n\nThe most important exception classes in HTTPX are `RequestError` and `HTTPStatusError`.\n\nThe `RequestError` class is a superclass that encompasses any exception that occurs\nwhile issuing an HTTP request. These exceptions include a `.request` attribute.\n\n```python\ntry:\n    response = httpx.get(\"https://www.example.com/\")\nexcept httpx.RequestError as exc:\n    print(f\"An error occurred while requesting {exc.request.url!r}.\")\n```\n\nThe `HTTPStatusError` class is raised by `response.raise_for_status()` on responses which are not a 2xx success code.\nThese exceptions include both a `.request` and a `.response` attribute.\n\n```python\nresponse = httpx.get(\"https://www.example.com/\")\ntry:\n    response.raise_for_status()\nexcept httpx.HTTPStatusError as exc:\n    print(f\"Error response {exc.response.status_code} while requesting {exc.request.url!r}.\")\n```\n\nThere is also a base class `HTTPError` that includes both of these categories, and can be used\nto catch either failed requests, or 4xx and 5xx responses.\n\nYou can either use this base class to catch both categories...\n\n```python\ntry:\n    response = httpx.get(\"https://www.example.com/\")\n    response.raise_for_status()\nexcept httpx.HTTPError as exc:\n    print(f\"Error while requesting {exc.request.url!r}.\")\n```\n\nOr handle each case explicitly...\n\n```python\ntry:\n    response = httpx.get(\"https://www.example.com/\")\n    response.raise_for_status()\nexcept httpx.RequestError as exc:\n    print(f\"An error occurred while requesting {exc.request.url!r}.\")\nexcept httpx.HTTPStatusError as exc:\n    print(f\"Error response {exc.response.status_code} while requesting {exc.request.url!r}.\")\n```\n\nFor a full list of available exceptions, see [Exceptions (API Reference)](exceptions.md).\n"
  },
  {
    "path": "docs/third_party_packages.md",
    "content": "# Third Party Packages\n\nAs HTTPX usage grows, there is an expanding community of developers building tools and libraries that integrate with HTTPX, or depend on HTTPX. Here are some of them.\n\n<!-- NOTE: Entries are alphabetised. -->\n\n## Plugins\n\n### Hishel\n\n[GitHub](https://github.com/karpetrosyan/hishel) - [Documentation](https://hishel.com/)\n\nAn elegant HTTP Cache implementation for HTTPX and HTTP Core.\n\n### HTTPX-Auth\n\n[GitHub](https://github.com/Colin-b/httpx_auth) - [Documentation](https://colin-b.github.io/httpx_auth/)\n\nProvides authentication classes to be used with HTTPX's [authentication parameter](advanced/authentication.md#customizing-authentication).\n\n### httpx-caching\n\n[Github](https://github.com/johtso/httpx-caching)\n\nThis package adds caching functionality to HTTPX\n\n### httpx-secure\n\n[GitHub](https://github.com/Zaczero/httpx-secure)\n\nDrop-in SSRF protection for httpx with DNS caching and custom validation support.\n\n### httpx-socks\n\n[GitHub](https://github.com/romis2012/httpx-socks)\n\nProxy (HTTP, SOCKS) transports for httpx.\n\n### httpx-sse\n\n[GitHub](https://github.com/florimondmanca/httpx-sse)\n\nAllows consuming Server-Sent Events (SSE) with HTTPX.\n\n### httpx-retries\n\n[GitHub](https://github.com/will-ockmore/httpx-retries) - [Documentation](https://will-ockmore.github.io/httpx-retries/)\n\nA retry layer for HTTPX.\n\n### httpx-ws\n\n[GitHub](https://github.com/frankie567/httpx-ws) - [Documentation](https://frankie567.github.io/httpx-ws/)\n\nWebSocket support for HTTPX.\n\n### pytest-HTTPX\n\n[GitHub](https://github.com/Colin-b/pytest_httpx) - [Documentation](https://colin-b.github.io/pytest_httpx/)\n\nProvides a [pytest](https://docs.pytest.org/en/latest/) fixture to mock HTTPX within test cases.\n\n### RESPX\n\n[GitHub](https://github.com/lundberg/respx) - [Documentation](https://lundberg.github.io/respx/)\n\nA utility for mocking out HTTPX.\n\n### rpc.py\n\n[Github](https://github.com/abersheeran/rpc.py) - [Documentation](https://github.com/abersheeran/rpc.py#rpcpy)\n\nA fast and powerful RPC framework based on ASGI/WSGI. Use HTTPX as the client of the RPC service.\n\n## Libraries with HTTPX support\n\n### Authlib\n\n[GitHub](https://github.com/lepture/authlib) - [Documentation](https://docs.authlib.org/en/latest/)\n\nA python library for building OAuth and OpenID Connect clients and servers. Includes an [OAuth HTTPX client](https://docs.authlib.org/en/latest/client/httpx.html).\n\n### Gidgethub\n\n[GitHub](https://github.com/brettcannon/gidgethub) - [Documentation](https://gidgethub.readthedocs.io/en/latest/index.html)\n\nAn asynchronous GitHub API library. Includes [HTTPX support](https://gidgethub.readthedocs.io/en/latest/httpx.html).\n\n### httpdbg\n\n[GitHub](https://github.com/cle-b/httpdbg) - [Documentation](https://httpdbg.readthedocs.io/)\n\nA tool for python developers to easily debug the HTTP(S) client requests in a python program.\n\n### VCR.py\n\n[GitHub](https://github.com/kevin1024/vcrpy) - [Documentation](https://vcrpy.readthedocs.io/)\n\nRecord and repeat requests.\n\n## Gists\n\n### urllib3-transport\n\n[GitHub](https://gist.github.com/florimondmanca/d56764d78d748eb9f73165da388e546e)\n\nThis public gist provides an example implementation for a [custom transport](advanced/transports.md#custom-transports) implementation on top of the battle-tested [`urllib3`](https://urllib3.readthedocs.io) library.\n"
  },
  {
    "path": "docs/troubleshooting.md",
    "content": "# Troubleshooting\n\nThis page lists some common problems or issues you could encounter while developing with HTTPX, as well as possible solutions.\n\n## Proxies\n\n---\n\n### \"`The handshake operation timed out`\" on HTTPS requests when using a proxy\n\n**Description**: When using a proxy and making an HTTPS request, you see an exception looking like this:\n\n```console\nhttpx.ProxyError: _ssl.c:1091: The handshake operation timed out\n```\n\n**Similar issues**: [encode/httpx#1412](https://github.com/encode/httpx/issues/1412), [encode/httpx#1433](https://github.com/encode/httpx/issues/1433)\n\n**Resolution**: it is likely that you've set up your proxies like this...\n\n```python\nmounts = {\n  \"http://\": httpx.HTTPTransport(proxy=\"http://myproxy.org\"),\n  \"https://\": httpx.HTTPTransport(proxy=\"https://myproxy.org\"),\n}\n```\n\nUsing this setup, you're telling HTTPX to connect to the proxy using HTTP for HTTP requests, and using HTTPS for HTTPS requests.\n\nBut if you get the error above, it is likely that your proxy doesn't support connecting via HTTPS. Don't worry: that's a [common gotcha](advanced/proxies.md#http-proxies).\n\nChange the scheme of your HTTPS proxy to `http://...` instead of `https://...`:\n\n```python\nmounts = {\n  \"http://\": httpx.HTTPTransport(proxy=\"http://myproxy.org\"),\n  \"https://\": httpx.HTTPTransport(proxy=\"http://myproxy.org\"),\n}\n```\n\nThis can be simplified to:\n\n```python\nproxy = \"http://myproxy.org\"\nwith httpx.Client(proxy=proxy) as client:\n  ...\n```\n\nFor more information, see [Proxies: FORWARD vs TUNNEL](advanced/proxies.md#forward-vs-tunnel).\n\n---\n\n### Error when making requests to an HTTPS proxy\n\n**Description**: your proxy _does_ support connecting via HTTPS, but you are seeing errors along the lines of...\n\n```console\nhttpx.ProxyError: [SSL: PRE_MAC_LENGTH_TOO_LONG] invalid alert (_ssl.c:1091)\n```\n\n**Similar issues**: [encode/httpx#1424](https://github.com/encode/httpx/issues/1424).\n\n**Resolution**: HTTPX does not properly support HTTPS proxies at this time. If that's something you're interested in having, please see [encode/httpx#1434](https://github.com/encode/httpx/issues/1434) and consider lending a hand there.\n"
  },
  {
    "path": "httpx/__init__.py",
    "content": "from .__version__ import __description__, __title__, __version__\nfrom ._api import *\nfrom ._auth import *\nfrom ._client import *\nfrom ._config import *\nfrom ._content import *\nfrom ._exceptions import *\nfrom ._models import *\nfrom ._status_codes import *\nfrom ._transports import *\nfrom ._types import *\nfrom ._urls import *\n\ntry:\n    from ._main import main\nexcept ImportError:  # pragma: no cover\n\n    def main() -> None:  # type: ignore\n        import sys\n\n        print(\n            \"The httpx command line client could not run because the required \"\n            \"dependencies were not installed.\\nMake sure you've installed \"\n            \"everything with: pip install 'httpx[cli]'\"\n        )\n        sys.exit(1)\n\n\n__all__ = [\n    \"__description__\",\n    \"__title__\",\n    \"__version__\",\n    \"ASGITransport\",\n    \"AsyncBaseTransport\",\n    \"AsyncByteStream\",\n    \"AsyncClient\",\n    \"AsyncHTTPTransport\",\n    \"Auth\",\n    \"BaseTransport\",\n    \"BasicAuth\",\n    \"ByteStream\",\n    \"Client\",\n    \"CloseError\",\n    \"codes\",\n    \"ConnectError\",\n    \"ConnectTimeout\",\n    \"CookieConflict\",\n    \"Cookies\",\n    \"create_ssl_context\",\n    \"DecodingError\",\n    \"delete\",\n    \"DigestAuth\",\n    \"FunctionAuth\",\n    \"get\",\n    \"head\",\n    \"Headers\",\n    \"HTTPError\",\n    \"HTTPStatusError\",\n    \"HTTPTransport\",\n    \"InvalidURL\",\n    \"Limits\",\n    \"LocalProtocolError\",\n    \"main\",\n    \"MockTransport\",\n    \"NetRCAuth\",\n    \"NetworkError\",\n    \"options\",\n    \"patch\",\n    \"PoolTimeout\",\n    \"post\",\n    \"ProtocolError\",\n    \"Proxy\",\n    \"ProxyError\",\n    \"put\",\n    \"QueryParams\",\n    \"ReadError\",\n    \"ReadTimeout\",\n    \"RemoteProtocolError\",\n    \"request\",\n    \"Request\",\n    \"RequestError\",\n    \"RequestNotRead\",\n    \"Response\",\n    \"ResponseNotRead\",\n    \"stream\",\n    \"StreamClosed\",\n    \"StreamConsumed\",\n    \"StreamError\",\n    \"SyncByteStream\",\n    \"Timeout\",\n    \"TimeoutException\",\n    \"TooManyRedirects\",\n    \"TransportError\",\n    \"UnsupportedProtocol\",\n    \"URL\",\n    \"USE_CLIENT_DEFAULT\",\n    \"WriteError\",\n    \"WriteTimeout\",\n    \"WSGITransport\",\n]\n\n\n__locals = locals()\nfor __name in __all__:\n    if not __name.startswith(\"__\"):\n        setattr(__locals[__name], \"__module__\", \"httpx\")  # noqa\n"
  },
  {
    "path": "httpx/__version__.py",
    "content": "__title__ = \"httpx\"\n__description__ = \"A next generation HTTP client, for Python 3.\"\n__version__ = \"0.28.1\"\n"
  },
  {
    "path": "httpx/_api.py",
    "content": "from __future__ import annotations\n\nimport typing\nfrom contextlib import contextmanager\n\nfrom ._client import Client\nfrom ._config import DEFAULT_TIMEOUT_CONFIG\nfrom ._models import Response\nfrom ._types import (\n    AuthTypes,\n    CookieTypes,\n    HeaderTypes,\n    ProxyTypes,\n    QueryParamTypes,\n    RequestContent,\n    RequestData,\n    RequestFiles,\n    TimeoutTypes,\n)\nfrom ._urls import URL\n\nif typing.TYPE_CHECKING:\n    import ssl  # pragma: no cover\n\n\n__all__ = [\n    \"delete\",\n    \"get\",\n    \"head\",\n    \"options\",\n    \"patch\",\n    \"post\",\n    \"put\",\n    \"request\",\n    \"stream\",\n]\n\n\ndef request(\n    method: str,\n    url: URL | str,\n    *,\n    params: QueryParamTypes | None = None,\n    content: RequestContent | None = None,\n    data: RequestData | None = None,\n    files: RequestFiles | None = None,\n    json: typing.Any | None = None,\n    headers: HeaderTypes | None = None,\n    cookies: CookieTypes | None = None,\n    auth: AuthTypes | None = None,\n    proxy: ProxyTypes | None = None,\n    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n    follow_redirects: bool = False,\n    verify: ssl.SSLContext | str | bool = True,\n    trust_env: bool = True,\n) -> Response:\n    \"\"\"\n    Sends an HTTP request.\n\n    **Parameters:**\n\n    * **method** - HTTP method for the new `Request` object: `GET`, `OPTIONS`,\n    `HEAD`, `POST`, `PUT`, `PATCH`, or `DELETE`.\n    * **url** - URL for the new `Request` object.\n    * **params** - *(optional)* Query parameters to include in the URL, as a\n    string, dictionary, or sequence of two-tuples.\n    * **content** - *(optional)* Binary content to include in the body of the\n    request, as bytes or a byte iterator.\n    * **data** - *(optional)* Form data to include in the body of the request,\n    as a dictionary.\n    * **files** - *(optional)* A dictionary of upload files to include in the\n    body of the request.\n    * **json** - *(optional)* A JSON serializable object to include in the body\n    of the request.\n    * **headers** - *(optional)* Dictionary of HTTP headers to include in the\n    request.\n    * **cookies** - *(optional)* Dictionary of Cookie items to include in the\n    request.\n    * **auth** - *(optional)* An authentication class to use when sending the\n    request.\n    * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.\n    * **timeout** - *(optional)* The timeout configuration to use when sending\n    the request.\n    * **follow_redirects** - *(optional)* Enables or disables HTTP redirects.\n    * **verify** - *(optional)* Either `True` to use an SSL context with the\n    default CA bundle, `False` to disable verification, or an instance of\n    `ssl.SSLContext` to use a custom context.\n    * **trust_env** - *(optional)* Enables or disables usage of environment\n    variables for configuration.\n\n    **Returns:** `Response`\n\n    Usage:\n\n    ```\n    >>> import httpx\n    >>> response = httpx.request('GET', 'https://httpbin.org/get')\n    >>> response\n    <Response [200 OK]>\n    ```\n    \"\"\"\n    with Client(\n        cookies=cookies,\n        proxy=proxy,\n        verify=verify,\n        timeout=timeout,\n        trust_env=trust_env,\n    ) as client:\n        return client.request(\n            method=method,\n            url=url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            auth=auth,\n            follow_redirects=follow_redirects,\n        )\n\n\n@contextmanager\ndef stream(\n    method: str,\n    url: URL | str,\n    *,\n    params: QueryParamTypes | None = None,\n    content: RequestContent | None = None,\n    data: RequestData | None = None,\n    files: RequestFiles | None = None,\n    json: typing.Any | None = None,\n    headers: HeaderTypes | None = None,\n    cookies: CookieTypes | None = None,\n    auth: AuthTypes | None = None,\n    proxy: ProxyTypes | None = None,\n    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n    follow_redirects: bool = False,\n    verify: ssl.SSLContext | str | bool = True,\n    trust_env: bool = True,\n) -> typing.Iterator[Response]:\n    \"\"\"\n    Alternative to `httpx.request()` that streams the response body\n    instead of loading it into memory at once.\n\n    **Parameters**: See `httpx.request`.\n\n    See also: [Streaming Responses][0]\n\n    [0]: /quickstart#streaming-responses\n    \"\"\"\n    with Client(\n        cookies=cookies,\n        proxy=proxy,\n        verify=verify,\n        timeout=timeout,\n        trust_env=trust_env,\n    ) as client:\n        with client.stream(\n            method=method,\n            url=url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            auth=auth,\n            follow_redirects=follow_redirects,\n        ) as response:\n            yield response\n\n\ndef get(\n    url: URL | str,\n    *,\n    params: QueryParamTypes | None = None,\n    headers: HeaderTypes | None = None,\n    cookies: CookieTypes | None = None,\n    auth: AuthTypes | None = None,\n    proxy: ProxyTypes | None = None,\n    follow_redirects: bool = False,\n    verify: ssl.SSLContext | str | bool = True,\n    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n    trust_env: bool = True,\n) -> Response:\n    \"\"\"\n    Sends a `GET` request.\n\n    **Parameters**: See `httpx.request`.\n\n    Note that the `data`, `files`, `json` and `content` parameters are not available\n    on this function, as `GET` requests should not include a request body.\n    \"\"\"\n    return request(\n        \"GET\",\n        url,\n        params=params,\n        headers=headers,\n        cookies=cookies,\n        auth=auth,\n        proxy=proxy,\n        follow_redirects=follow_redirects,\n        verify=verify,\n        timeout=timeout,\n        trust_env=trust_env,\n    )\n\n\ndef options(\n    url: URL | str,\n    *,\n    params: QueryParamTypes | None = None,\n    headers: HeaderTypes | None = None,\n    cookies: CookieTypes | None = None,\n    auth: AuthTypes | None = None,\n    proxy: ProxyTypes | None = None,\n    follow_redirects: bool = False,\n    verify: ssl.SSLContext | str | bool = True,\n    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n    trust_env: bool = True,\n) -> Response:\n    \"\"\"\n    Sends an `OPTIONS` request.\n\n    **Parameters**: See `httpx.request`.\n\n    Note that the `data`, `files`, `json` and `content` parameters are not available\n    on this function, as `OPTIONS` requests should not include a request body.\n    \"\"\"\n    return request(\n        \"OPTIONS\",\n        url,\n        params=params,\n        headers=headers,\n        cookies=cookies,\n        auth=auth,\n        proxy=proxy,\n        follow_redirects=follow_redirects,\n        verify=verify,\n        timeout=timeout,\n        trust_env=trust_env,\n    )\n\n\ndef head(\n    url: URL | str,\n    *,\n    params: QueryParamTypes | None = None,\n    headers: HeaderTypes | None = None,\n    cookies: CookieTypes | None = None,\n    auth: AuthTypes | None = None,\n    proxy: ProxyTypes | None = None,\n    follow_redirects: bool = False,\n    verify: ssl.SSLContext | str | bool = True,\n    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n    trust_env: bool = True,\n) -> Response:\n    \"\"\"\n    Sends a `HEAD` request.\n\n    **Parameters**: See `httpx.request`.\n\n    Note that the `data`, `files`, `json` and `content` parameters are not available\n    on this function, as `HEAD` requests should not include a request body.\n    \"\"\"\n    return request(\n        \"HEAD\",\n        url,\n        params=params,\n        headers=headers,\n        cookies=cookies,\n        auth=auth,\n        proxy=proxy,\n        follow_redirects=follow_redirects,\n        verify=verify,\n        timeout=timeout,\n        trust_env=trust_env,\n    )\n\n\ndef post(\n    url: URL | str,\n    *,\n    content: RequestContent | None = None,\n    data: RequestData | None = None,\n    files: RequestFiles | None = None,\n    json: typing.Any | None = None,\n    params: QueryParamTypes | None = None,\n    headers: HeaderTypes | None = None,\n    cookies: CookieTypes | None = None,\n    auth: AuthTypes | None = None,\n    proxy: ProxyTypes | None = None,\n    follow_redirects: bool = False,\n    verify: ssl.SSLContext | str | bool = True,\n    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n    trust_env: bool = True,\n) -> Response:\n    \"\"\"\n    Sends a `POST` request.\n\n    **Parameters**: See `httpx.request`.\n    \"\"\"\n    return request(\n        \"POST\",\n        url,\n        content=content,\n        data=data,\n        files=files,\n        json=json,\n        params=params,\n        headers=headers,\n        cookies=cookies,\n        auth=auth,\n        proxy=proxy,\n        follow_redirects=follow_redirects,\n        verify=verify,\n        timeout=timeout,\n        trust_env=trust_env,\n    )\n\n\ndef put(\n    url: URL | str,\n    *,\n    content: RequestContent | None = None,\n    data: RequestData | None = None,\n    files: RequestFiles | None = None,\n    json: typing.Any | None = None,\n    params: QueryParamTypes | None = None,\n    headers: HeaderTypes | None = None,\n    cookies: CookieTypes | None = None,\n    auth: AuthTypes | None = None,\n    proxy: ProxyTypes | None = None,\n    follow_redirects: bool = False,\n    verify: ssl.SSLContext | str | bool = True,\n    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n    trust_env: bool = True,\n) -> Response:\n    \"\"\"\n    Sends a `PUT` request.\n\n    **Parameters**: See `httpx.request`.\n    \"\"\"\n    return request(\n        \"PUT\",\n        url,\n        content=content,\n        data=data,\n        files=files,\n        json=json,\n        params=params,\n        headers=headers,\n        cookies=cookies,\n        auth=auth,\n        proxy=proxy,\n        follow_redirects=follow_redirects,\n        verify=verify,\n        timeout=timeout,\n        trust_env=trust_env,\n    )\n\n\ndef patch(\n    url: URL | str,\n    *,\n    content: RequestContent | None = None,\n    data: RequestData | None = None,\n    files: RequestFiles | None = None,\n    json: typing.Any | None = None,\n    params: QueryParamTypes | None = None,\n    headers: HeaderTypes | None = None,\n    cookies: CookieTypes | None = None,\n    auth: AuthTypes | None = None,\n    proxy: ProxyTypes | None = None,\n    follow_redirects: bool = False,\n    verify: ssl.SSLContext | str | bool = True,\n    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n    trust_env: bool = True,\n) -> Response:\n    \"\"\"\n    Sends a `PATCH` request.\n\n    **Parameters**: See `httpx.request`.\n    \"\"\"\n    return request(\n        \"PATCH\",\n        url,\n        content=content,\n        data=data,\n        files=files,\n        json=json,\n        params=params,\n        headers=headers,\n        cookies=cookies,\n        auth=auth,\n        proxy=proxy,\n        follow_redirects=follow_redirects,\n        verify=verify,\n        timeout=timeout,\n        trust_env=trust_env,\n    )\n\n\ndef delete(\n    url: URL | str,\n    *,\n    params: QueryParamTypes | None = None,\n    headers: HeaderTypes | None = None,\n    cookies: CookieTypes | None = None,\n    auth: AuthTypes | None = None,\n    proxy: ProxyTypes | None = None,\n    follow_redirects: bool = False,\n    timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n    verify: ssl.SSLContext | str | bool = True,\n    trust_env: bool = True,\n) -> Response:\n    \"\"\"\n    Sends a `DELETE` request.\n\n    **Parameters**: See `httpx.request`.\n\n    Note that the `data`, `files`, `json` and `content` parameters are not available\n    on this function, as `DELETE` requests should not include a request body.\n    \"\"\"\n    return request(\n        \"DELETE\",\n        url,\n        params=params,\n        headers=headers,\n        cookies=cookies,\n        auth=auth,\n        proxy=proxy,\n        follow_redirects=follow_redirects,\n        verify=verify,\n        timeout=timeout,\n        trust_env=trust_env,\n    )\n"
  },
  {
    "path": "httpx/_auth.py",
    "content": "from __future__ import annotations\n\nimport hashlib\nimport os\nimport re\nimport time\nimport typing\nfrom base64 import b64encode\nfrom urllib.request import parse_http_list\n\nfrom ._exceptions import ProtocolError\nfrom ._models import Cookies, Request, Response\nfrom ._utils import to_bytes, to_str, unquote\n\nif typing.TYPE_CHECKING:  # pragma: no cover\n    from hashlib import _Hash\n\n\n__all__ = [\"Auth\", \"BasicAuth\", \"DigestAuth\", \"FunctionAuth\", \"NetRCAuth\"]\n\n\nclass Auth:\n    \"\"\"\n    Base class for all authentication schemes.\n\n    To implement a custom authentication scheme, subclass `Auth` and override\n    the `.auth_flow()` method.\n\n    If the authentication scheme does I/O such as disk access or network calls, or uses\n    synchronization primitives such as locks, you should override `.sync_auth_flow()`\n    and/or `.async_auth_flow()` instead of `.auth_flow()` to provide specialized\n    implementations that will be used by `Client` and `AsyncClient` respectively.\n    \"\"\"\n\n    requires_request_body = False\n    requires_response_body = False\n\n    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:\n        \"\"\"\n        Execute the authentication flow.\n\n        To dispatch a request, `yield` it:\n\n        ```\n        yield request\n        ```\n\n        The client will `.send()` the response back into the flow generator. You can\n        access it like so:\n\n        ```\n        response = yield request\n        ```\n\n        A `return` (or reaching the end of the generator) will result in the\n        client returning the last response obtained from the server.\n\n        You can dispatch as many requests as is necessary.\n        \"\"\"\n        yield request\n\n    def sync_auth_flow(\n        self, request: Request\n    ) -> typing.Generator[Request, Response, None]:\n        \"\"\"\n        Execute the authentication flow synchronously.\n\n        By default, this defers to `.auth_flow()`. You should override this method\n        when the authentication scheme does I/O and/or uses concurrency primitives.\n        \"\"\"\n        if self.requires_request_body:\n            request.read()\n\n        flow = self.auth_flow(request)\n        request = next(flow)\n\n        while True:\n            response = yield request\n            if self.requires_response_body:\n                response.read()\n\n            try:\n                request = flow.send(response)\n            except StopIteration:\n                break\n\n    async def async_auth_flow(\n        self, request: Request\n    ) -> typing.AsyncGenerator[Request, Response]:\n        \"\"\"\n        Execute the authentication flow asynchronously.\n\n        By default, this defers to `.auth_flow()`. You should override this method\n        when the authentication scheme does I/O and/or uses concurrency primitives.\n        \"\"\"\n        if self.requires_request_body:\n            await request.aread()\n\n        flow = self.auth_flow(request)\n        request = next(flow)\n\n        while True:\n            response = yield request\n            if self.requires_response_body:\n                await response.aread()\n\n            try:\n                request = flow.send(response)\n            except StopIteration:\n                break\n\n\nclass FunctionAuth(Auth):\n    \"\"\"\n    Allows the 'auth' argument to be passed as a simple callable function,\n    that takes the request, and returns a new, modified request.\n    \"\"\"\n\n    def __init__(self, func: typing.Callable[[Request], Request]) -> None:\n        self._func = func\n\n    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:\n        yield self._func(request)\n\n\nclass BasicAuth(Auth):\n    \"\"\"\n    Allows the 'auth' argument to be passed as a (username, password) pair,\n    and uses HTTP Basic authentication.\n    \"\"\"\n\n    def __init__(self, username: str | bytes, password: str | bytes) -> None:\n        self._auth_header = self._build_auth_header(username, password)\n\n    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:\n        request.headers[\"Authorization\"] = self._auth_header\n        yield request\n\n    def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str:\n        userpass = b\":\".join((to_bytes(username), to_bytes(password)))\n        token = b64encode(userpass).decode()\n        return f\"Basic {token}\"\n\n\nclass NetRCAuth(Auth):\n    \"\"\"\n    Use a 'netrc' file to lookup basic auth credentials based on the url host.\n    \"\"\"\n\n    def __init__(self, file: str | None = None) -> None:\n        # Lazily import 'netrc'.\n        # There's no need for us to load this module unless 'NetRCAuth' is being used.\n        import netrc\n\n        self._netrc_info = netrc.netrc(file)\n\n    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:\n        auth_info = self._netrc_info.authenticators(request.url.host)\n        if auth_info is None or not auth_info[2]:\n            # The netrc file did not have authentication credentials for this host.\n            yield request\n        else:\n            # Build a basic auth header with credentials from the netrc file.\n            request.headers[\"Authorization\"] = self._build_auth_header(\n                username=auth_info[0], password=auth_info[2]\n            )\n            yield request\n\n    def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str:\n        userpass = b\":\".join((to_bytes(username), to_bytes(password)))\n        token = b64encode(userpass).decode()\n        return f\"Basic {token}\"\n\n\nclass DigestAuth(Auth):\n    _ALGORITHM_TO_HASH_FUNCTION: dict[str, typing.Callable[[bytes], _Hash]] = {\n        \"MD5\": hashlib.md5,\n        \"MD5-SESS\": hashlib.md5,\n        \"SHA\": hashlib.sha1,\n        \"SHA-SESS\": hashlib.sha1,\n        \"SHA-256\": hashlib.sha256,\n        \"SHA-256-SESS\": hashlib.sha256,\n        \"SHA-512\": hashlib.sha512,\n        \"SHA-512-SESS\": hashlib.sha512,\n    }\n\n    def __init__(self, username: str | bytes, password: str | bytes) -> None:\n        self._username = to_bytes(username)\n        self._password = to_bytes(password)\n        self._last_challenge: _DigestAuthChallenge | None = None\n        self._nonce_count = 1\n\n    def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]:\n        if self._last_challenge:\n            request.headers[\"Authorization\"] = self._build_auth_header(\n                request, self._last_challenge\n            )\n\n        response = yield request\n\n        if response.status_code != 401 or \"www-authenticate\" not in response.headers:\n            # If the response is not a 401 then we don't\n            # need to build an authenticated request.\n            return\n\n        for auth_header in response.headers.get_list(\"www-authenticate\"):\n            if auth_header.lower().startswith(\"digest \"):\n                break\n        else:\n            # If the response does not include a 'WWW-Authenticate: Digest ...'\n            # header, then we don't need to build an authenticated request.\n            return\n\n        self._last_challenge = self._parse_challenge(request, response, auth_header)\n        self._nonce_count = 1\n\n        request.headers[\"Authorization\"] = self._build_auth_header(\n            request, self._last_challenge\n        )\n        if response.cookies:\n            Cookies(response.cookies).set_cookie_header(request=request)\n        yield request\n\n    def _parse_challenge(\n        self, request: Request, response: Response, auth_header: str\n    ) -> _DigestAuthChallenge:\n        \"\"\"\n        Returns a challenge from a Digest WWW-Authenticate header.\n        These take the form of:\n        `Digest realm=\"realm@host.com\",qop=\"auth,auth-int\",nonce=\"abc\",opaque=\"xyz\"`\n        \"\"\"\n        scheme, _, fields = auth_header.partition(\" \")\n\n        # This method should only ever have been called with a Digest auth header.\n        assert scheme.lower() == \"digest\"\n\n        header_dict: dict[str, str] = {}\n        for field in parse_http_list(fields):\n            key, value = field.strip().split(\"=\", 1)\n            header_dict[key] = unquote(value)\n\n        try:\n            realm = header_dict[\"realm\"].encode()\n            nonce = header_dict[\"nonce\"].encode()\n            algorithm = header_dict.get(\"algorithm\", \"MD5\")\n            opaque = header_dict[\"opaque\"].encode() if \"opaque\" in header_dict else None\n            qop = header_dict[\"qop\"].encode() if \"qop\" in header_dict else None\n            return _DigestAuthChallenge(\n                realm=realm, nonce=nonce, algorithm=algorithm, opaque=opaque, qop=qop\n            )\n        except KeyError as exc:\n            message = \"Malformed Digest WWW-Authenticate header\"\n            raise ProtocolError(message, request=request) from exc\n\n    def _build_auth_header(\n        self, request: Request, challenge: _DigestAuthChallenge\n    ) -> str:\n        hash_func = self._ALGORITHM_TO_HASH_FUNCTION[challenge.algorithm.upper()]\n\n        def digest(data: bytes) -> bytes:\n            return hash_func(data).hexdigest().encode()\n\n        A1 = b\":\".join((self._username, challenge.realm, self._password))\n\n        path = request.url.raw_path\n        A2 = b\":\".join((request.method.encode(), path))\n        # TODO: implement auth-int\n        HA2 = digest(A2)\n\n        nc_value = b\"%08x\" % self._nonce_count\n        cnonce = self._get_client_nonce(self._nonce_count, challenge.nonce)\n        self._nonce_count += 1\n\n        HA1 = digest(A1)\n        if challenge.algorithm.lower().endswith(\"-sess\"):\n            HA1 = digest(b\":\".join((HA1, challenge.nonce, cnonce)))\n\n        qop = self._resolve_qop(challenge.qop, request=request)\n        if qop is None:\n            # Following RFC 2069\n            digest_data = [HA1, challenge.nonce, HA2]\n        else:\n            # Following RFC 2617/7616\n            digest_data = [HA1, challenge.nonce, nc_value, cnonce, qop, HA2]\n\n        format_args = {\n            \"username\": self._username,\n            \"realm\": challenge.realm,\n            \"nonce\": challenge.nonce,\n            \"uri\": path,\n            \"response\": digest(b\":\".join(digest_data)),\n            \"algorithm\": challenge.algorithm.encode(),\n        }\n        if challenge.opaque:\n            format_args[\"opaque\"] = challenge.opaque\n        if qop:\n            format_args[\"qop\"] = b\"auth\"\n            format_args[\"nc\"] = nc_value\n            format_args[\"cnonce\"] = cnonce\n\n        return \"Digest \" + self._get_header_value(format_args)\n\n    def _get_client_nonce(self, nonce_count: int, nonce: bytes) -> bytes:\n        s = str(nonce_count).encode()\n        s += nonce\n        s += time.ctime().encode()\n        s += os.urandom(8)\n\n        return hashlib.sha1(s).hexdigest()[:16].encode()\n\n    def _get_header_value(self, header_fields: dict[str, bytes]) -> str:\n        NON_QUOTED_FIELDS = (\"algorithm\", \"qop\", \"nc\")\n        QUOTED_TEMPLATE = '{}=\"{}\"'\n        NON_QUOTED_TEMPLATE = \"{}={}\"\n\n        header_value = \"\"\n        for i, (field, value) in enumerate(header_fields.items()):\n            if i > 0:\n                header_value += \", \"\n            template = (\n                QUOTED_TEMPLATE\n                if field not in NON_QUOTED_FIELDS\n                else NON_QUOTED_TEMPLATE\n            )\n            header_value += template.format(field, to_str(value))\n\n        return header_value\n\n    def _resolve_qop(self, qop: bytes | None, request: Request) -> bytes | None:\n        if qop is None:\n            return None\n        qops = re.split(b\", ?\", qop)\n        if b\"auth\" in qops:\n            return b\"auth\"\n\n        if qops == [b\"auth-int\"]:\n            raise NotImplementedError(\"Digest auth-int support is not yet implemented\")\n\n        message = f'Unexpected qop value \"{qop!r}\" in digest auth'\n        raise ProtocolError(message, request=request)\n\n\nclass _DigestAuthChallenge(typing.NamedTuple):\n    realm: bytes\n    nonce: bytes\n    algorithm: str\n    opaque: bytes | None\n    qop: bytes | None\n"
  },
  {
    "path": "httpx/_client.py",
    "content": "from __future__ import annotations\n\nimport datetime\nimport enum\nimport logging\nimport time\nimport typing\nimport warnings\nfrom contextlib import asynccontextmanager, contextmanager\nfrom types import TracebackType\n\nfrom .__version__ import __version__\nfrom ._auth import Auth, BasicAuth, FunctionAuth\nfrom ._config import (\n    DEFAULT_LIMITS,\n    DEFAULT_MAX_REDIRECTS,\n    DEFAULT_TIMEOUT_CONFIG,\n    Limits,\n    Proxy,\n    Timeout,\n)\nfrom ._decoders import SUPPORTED_DECODERS\nfrom ._exceptions import (\n    InvalidURL,\n    RemoteProtocolError,\n    TooManyRedirects,\n    request_context,\n)\nfrom ._models import Cookies, Headers, Request, Response\nfrom ._status_codes import codes\nfrom ._transports.base import AsyncBaseTransport, BaseTransport\nfrom ._transports.default import AsyncHTTPTransport, HTTPTransport\nfrom ._types import (\n    AsyncByteStream,\n    AuthTypes,\n    CertTypes,\n    CookieTypes,\n    HeaderTypes,\n    ProxyTypes,\n    QueryParamTypes,\n    RequestContent,\n    RequestData,\n    RequestExtensions,\n    RequestFiles,\n    SyncByteStream,\n    TimeoutTypes,\n)\nfrom ._urls import URL, QueryParams\nfrom ._utils import URLPattern, get_environment_proxies\n\nif typing.TYPE_CHECKING:\n    import ssl  # pragma: no cover\n\n__all__ = [\"USE_CLIENT_DEFAULT\", \"AsyncClient\", \"Client\"]\n\n# The type annotation for @classmethod and context managers here follows PEP 484\n# https://www.python.org/dev/peps/pep-0484/#annotating-instance-and-class-methods\nT = typing.TypeVar(\"T\", bound=\"Client\")\nU = typing.TypeVar(\"U\", bound=\"AsyncClient\")\n\n\ndef _is_https_redirect(url: URL, location: URL) -> bool:\n    \"\"\"\n    Return 'True' if 'location' is a HTTPS upgrade of 'url'\n    \"\"\"\n    if url.host != location.host:\n        return False\n\n    return (\n        url.scheme == \"http\"\n        and _port_or_default(url) == 80\n        and location.scheme == \"https\"\n        and _port_or_default(location) == 443\n    )\n\n\ndef _port_or_default(url: URL) -> int | None:\n    if url.port is not None:\n        return url.port\n    return {\"http\": 80, \"https\": 443}.get(url.scheme)\n\n\ndef _same_origin(url: URL, other: URL) -> bool:\n    \"\"\"\n    Return 'True' if the given URLs share the same origin.\n    \"\"\"\n    return (\n        url.scheme == other.scheme\n        and url.host == other.host\n        and _port_or_default(url) == _port_or_default(other)\n    )\n\n\nclass UseClientDefault:\n    \"\"\"\n    For some parameters such as `auth=...` and `timeout=...` we need to be able\n    to indicate the default \"unset\" state, in a way that is distinctly different\n    to using `None`.\n\n    The default \"unset\" state indicates that whatever default is set on the\n    client should be used. This is different to setting `None`, which\n    explicitly disables the parameter, possibly overriding a client default.\n\n    For example we use `timeout=USE_CLIENT_DEFAULT` in the `request()` signature.\n    Omitting the `timeout` parameter will send a request using whatever default\n    timeout has been configured on the client. Including `timeout=None` will\n    ensure no timeout is used.\n\n    Note that user code shouldn't need to use the `USE_CLIENT_DEFAULT` constant,\n    but it is used internally when a parameter is not included.\n    \"\"\"\n\n\nUSE_CLIENT_DEFAULT = UseClientDefault()\n\n\nlogger = logging.getLogger(\"httpx\")\n\nUSER_AGENT = f\"python-httpx/{__version__}\"\nACCEPT_ENCODING = \", \".join(\n    [key for key in SUPPORTED_DECODERS.keys() if key != \"identity\"]\n)\n\n\nclass ClientState(enum.Enum):\n    # UNOPENED:\n    #   The client has been instantiated, but has not been used to send a request,\n    #   or been opened by entering the context of a `with` block.\n    UNOPENED = 1\n    # OPENED:\n    #   The client has either sent a request, or is within a `with` block.\n    OPENED = 2\n    # CLOSED:\n    #   The client has either exited the `with` block, or `close()` has\n    #   been called explicitly.\n    CLOSED = 3\n\n\nclass BoundSyncStream(SyncByteStream):\n    \"\"\"\n    A byte stream that is bound to a given response instance, and that\n    ensures the `response.elapsed` is set once the response is closed.\n    \"\"\"\n\n    def __init__(\n        self, stream: SyncByteStream, response: Response, start: float\n    ) -> None:\n        self._stream = stream\n        self._response = response\n        self._start = start\n\n    def __iter__(self) -> typing.Iterator[bytes]:\n        for chunk in self._stream:\n            yield chunk\n\n    def close(self) -> None:\n        elapsed = time.perf_counter() - self._start\n        self._response.elapsed = datetime.timedelta(seconds=elapsed)\n        self._stream.close()\n\n\nclass BoundAsyncStream(AsyncByteStream):\n    \"\"\"\n    An async byte stream that is bound to a given response instance, and that\n    ensures the `response.elapsed` is set once the response is closed.\n    \"\"\"\n\n    def __init__(\n        self, stream: AsyncByteStream, response: Response, start: float\n    ) -> None:\n        self._stream = stream\n        self._response = response\n        self._start = start\n\n    async def __aiter__(self) -> typing.AsyncIterator[bytes]:\n        async for chunk in self._stream:\n            yield chunk\n\n    async def aclose(self) -> None:\n        elapsed = time.perf_counter() - self._start\n        self._response.elapsed = datetime.timedelta(seconds=elapsed)\n        await self._stream.aclose()\n\n\nEventHook = typing.Callable[..., typing.Any]\n\n\nclass BaseClient:\n    def __init__(\n        self,\n        *,\n        auth: AuthTypes | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n        follow_redirects: bool = False,\n        max_redirects: int = DEFAULT_MAX_REDIRECTS,\n        event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,\n        base_url: URL | str = \"\",\n        trust_env: bool = True,\n        default_encoding: str | typing.Callable[[bytes], str] = \"utf-8\",\n    ) -> None:\n        event_hooks = {} if event_hooks is None else event_hooks\n\n        self._base_url = self._enforce_trailing_slash(URL(base_url))\n\n        self._auth = self._build_auth(auth)\n        self._params = QueryParams(params)\n        self.headers = Headers(headers)\n        self._cookies = Cookies(cookies)\n        self._timeout = Timeout(timeout)\n        self.follow_redirects = follow_redirects\n        self.max_redirects = max_redirects\n        self._event_hooks = {\n            \"request\": list(event_hooks.get(\"request\", [])),\n            \"response\": list(event_hooks.get(\"response\", [])),\n        }\n        self._trust_env = trust_env\n        self._default_encoding = default_encoding\n        self._state = ClientState.UNOPENED\n\n    @property\n    def is_closed(self) -> bool:\n        \"\"\"\n        Check if the client being closed\n        \"\"\"\n        return self._state == ClientState.CLOSED\n\n    @property\n    def trust_env(self) -> bool:\n        return self._trust_env\n\n    def _enforce_trailing_slash(self, url: URL) -> URL:\n        if url.raw_path.endswith(b\"/\"):\n            return url\n        return url.copy_with(raw_path=url.raw_path + b\"/\")\n\n    def _get_proxy_map(\n        self, proxy: ProxyTypes | None, allow_env_proxies: bool\n    ) -> dict[str, Proxy | None]:\n        if proxy is None:\n            if allow_env_proxies:\n                return {\n                    key: None if url is None else Proxy(url=url)\n                    for key, url in get_environment_proxies().items()\n                }\n            return {}\n        else:\n            proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy\n            return {\"all://\": proxy}\n\n    @property\n    def timeout(self) -> Timeout:\n        return self._timeout\n\n    @timeout.setter\n    def timeout(self, timeout: TimeoutTypes) -> None:\n        self._timeout = Timeout(timeout)\n\n    @property\n    def event_hooks(self) -> dict[str, list[EventHook]]:\n        return self._event_hooks\n\n    @event_hooks.setter\n    def event_hooks(self, event_hooks: dict[str, list[EventHook]]) -> None:\n        self._event_hooks = {\n            \"request\": list(event_hooks.get(\"request\", [])),\n            \"response\": list(event_hooks.get(\"response\", [])),\n        }\n\n    @property\n    def auth(self) -> Auth | None:\n        \"\"\"\n        Authentication class used when none is passed at the request-level.\n\n        See also [Authentication][0].\n\n        [0]: /quickstart/#authentication\n        \"\"\"\n        return self._auth\n\n    @auth.setter\n    def auth(self, auth: AuthTypes) -> None:\n        self._auth = self._build_auth(auth)\n\n    @property\n    def base_url(self) -> URL:\n        \"\"\"\n        Base URL to use when sending requests with relative URLs.\n        \"\"\"\n        return self._base_url\n\n    @base_url.setter\n    def base_url(self, url: URL | str) -> None:\n        self._base_url = self._enforce_trailing_slash(URL(url))\n\n    @property\n    def headers(self) -> Headers:\n        \"\"\"\n        HTTP headers to include when sending requests.\n        \"\"\"\n        return self._headers\n\n    @headers.setter\n    def headers(self, headers: HeaderTypes) -> None:\n        client_headers = Headers(\n            {\n                b\"Accept\": b\"*/*\",\n                b\"Accept-Encoding\": ACCEPT_ENCODING.encode(\"ascii\"),\n                b\"Connection\": b\"keep-alive\",\n                b\"User-Agent\": USER_AGENT.encode(\"ascii\"),\n            }\n        )\n        client_headers.update(headers)\n        self._headers = client_headers\n\n    @property\n    def cookies(self) -> Cookies:\n        \"\"\"\n        Cookie values to include when sending requests.\n        \"\"\"\n        return self._cookies\n\n    @cookies.setter\n    def cookies(self, cookies: CookieTypes) -> None:\n        self._cookies = Cookies(cookies)\n\n    @property\n    def params(self) -> QueryParams:\n        \"\"\"\n        Query parameters to include in the URL when sending requests.\n        \"\"\"\n        return self._params\n\n    @params.setter\n    def params(self, params: QueryParamTypes) -> None:\n        self._params = QueryParams(params)\n\n    def build_request(\n        self,\n        method: str,\n        url: URL | str,\n        *,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Request:\n        \"\"\"\n        Build and return a request instance.\n\n        * The `params`, `headers` and `cookies` arguments\n        are merged with any values set on the client.\n        * The `url` argument is merged with any `base_url` set on the client.\n\n        See also: [Request instances][0]\n\n        [0]: /advanced/clients/#request-instances\n        \"\"\"\n        url = self._merge_url(url)\n        headers = self._merge_headers(headers)\n        cookies = self._merge_cookies(cookies)\n        params = self._merge_queryparams(params)\n        extensions = {} if extensions is None else extensions\n        if \"timeout\" not in extensions:\n            timeout = (\n                self.timeout\n                if isinstance(timeout, UseClientDefault)\n                else Timeout(timeout)\n            )\n            extensions = dict(**extensions, timeout=timeout.as_dict())\n        return Request(\n            method,\n            url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            extensions=extensions,\n        )\n\n    def _merge_url(self, url: URL | str) -> URL:\n        \"\"\"\n        Merge a URL argument together with any 'base_url' on the client,\n        to create the URL used for the outgoing request.\n        \"\"\"\n        merge_url = URL(url)\n        if merge_url.is_relative_url:\n            # To merge URLs we always append to the base URL. To get this\n            # behaviour correct we always ensure the base URL ends in a '/'\n            # separator, and strip any leading '/' from the merge URL.\n            #\n            # So, eg...\n            #\n            # >>> client = Client(base_url=\"https://www.example.com/subpath\")\n            # >>> client.base_url\n            # URL('https://www.example.com/subpath/')\n            # >>> client.build_request(\"GET\", \"/path\").url\n            # URL('https://www.example.com/subpath/path')\n            merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b\"/\")\n            return self.base_url.copy_with(raw_path=merge_raw_path)\n        return merge_url\n\n    def _merge_cookies(self, cookies: CookieTypes | None = None) -> CookieTypes | None:\n        \"\"\"\n        Merge a cookies argument together with any cookies on the client,\n        to create the cookies used for the outgoing request.\n        \"\"\"\n        if cookies or self.cookies:\n            merged_cookies = Cookies(self.cookies)\n            merged_cookies.update(cookies)\n            return merged_cookies\n        return cookies\n\n    def _merge_headers(self, headers: HeaderTypes | None = None) -> HeaderTypes | None:\n        \"\"\"\n        Merge a headers argument together with any headers on the client,\n        to create the headers used for the outgoing request.\n        \"\"\"\n        merged_headers = Headers(self.headers)\n        merged_headers.update(headers)\n        return merged_headers\n\n    def _merge_queryparams(\n        self, params: QueryParamTypes | None = None\n    ) -> QueryParamTypes | None:\n        \"\"\"\n        Merge a queryparams argument together with any queryparams on the client,\n        to create the queryparams used for the outgoing request.\n        \"\"\"\n        if params or self.params:\n            merged_queryparams = QueryParams(self.params)\n            return merged_queryparams.merge(params)\n        return params\n\n    def _build_auth(self, auth: AuthTypes | None) -> Auth | None:\n        if auth is None:\n            return None\n        elif isinstance(auth, tuple):\n            return BasicAuth(username=auth[0], password=auth[1])\n        elif isinstance(auth, Auth):\n            return auth\n        elif callable(auth):\n            return FunctionAuth(func=auth)\n        else:\n            raise TypeError(f'Invalid \"auth\" argument: {auth!r}')\n\n    def _build_request_auth(\n        self,\n        request: Request,\n        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,\n    ) -> Auth:\n        auth = (\n            self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth)\n        )\n\n        if auth is not None:\n            return auth\n\n        username, password = request.url.username, request.url.password\n        if username or password:\n            return BasicAuth(username=username, password=password)\n\n        return Auth()\n\n    def _build_redirect_request(self, request: Request, response: Response) -> Request:\n        \"\"\"\n        Given a request and a redirect response, return a new request that\n        should be used to effect the redirect.\n        \"\"\"\n        method = self._redirect_method(request, response)\n        url = self._redirect_url(request, response)\n        headers = self._redirect_headers(request, url, method)\n        stream = self._redirect_stream(request, method)\n        cookies = Cookies(self.cookies)\n        return Request(\n            method=method,\n            url=url,\n            headers=headers,\n            cookies=cookies,\n            stream=stream,\n            extensions=request.extensions,\n        )\n\n    def _redirect_method(self, request: Request, response: Response) -> str:\n        \"\"\"\n        When being redirected we may want to change the method of the request\n        based on certain specs or browser behavior.\n        \"\"\"\n        method = request.method\n\n        # https://tools.ietf.org/html/rfc7231#section-6.4.4\n        if response.status_code == codes.SEE_OTHER and method != \"HEAD\":\n            method = \"GET\"\n\n        # Do what the browsers do, despite standards...\n        # Turn 302s into GETs.\n        if response.status_code == codes.FOUND and method != \"HEAD\":\n            method = \"GET\"\n\n        # If a POST is responded to with a 301, turn it into a GET.\n        # This bizarre behaviour is explained in 'requests' issue 1704.\n        if response.status_code == codes.MOVED_PERMANENTLY and method == \"POST\":\n            method = \"GET\"\n\n        return method\n\n    def _redirect_url(self, request: Request, response: Response) -> URL:\n        \"\"\"\n        Return the URL for the redirect to follow.\n        \"\"\"\n        location = response.headers[\"Location\"]\n\n        try:\n            url = URL(location)\n        except InvalidURL as exc:\n            raise RemoteProtocolError(\n                f\"Invalid URL in location header: {exc}.\", request=request\n            ) from None\n\n        # Handle malformed 'Location' headers that are \"absolute\" form, have no host.\n        # See: https://github.com/encode/httpx/issues/771\n        if url.scheme and not url.host:\n            url = url.copy_with(host=request.url.host)\n\n        # Facilitate relative 'Location' headers, as allowed by RFC 7231.\n        # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')\n        if url.is_relative_url:\n            url = request.url.join(url)\n\n        # Attach previous fragment if needed (RFC 7231 7.1.2)\n        if request.url.fragment and not url.fragment:\n            url = url.copy_with(fragment=request.url.fragment)\n\n        return url\n\n    def _redirect_headers(self, request: Request, url: URL, method: str) -> Headers:\n        \"\"\"\n        Return the headers that should be used for the redirect request.\n        \"\"\"\n        headers = Headers(request.headers)\n\n        if not _same_origin(url, request.url):\n            if not _is_https_redirect(request.url, url):\n                # Strip Authorization headers when responses are redirected\n                # away from the origin. (Except for direct HTTP to HTTPS redirects.)\n                headers.pop(\"Authorization\", None)\n\n            # Update the Host header.\n            headers[\"Host\"] = url.netloc.decode(\"ascii\")\n\n        if method != request.method and method == \"GET\":\n            # If we've switch to a 'GET' request, then strip any headers which\n            # are only relevant to the request body.\n            headers.pop(\"Content-Length\", None)\n            headers.pop(\"Transfer-Encoding\", None)\n\n        # We should use the client cookie store to determine any cookie header,\n        # rather than whatever was on the original outgoing request.\n        headers.pop(\"Cookie\", None)\n\n        return headers\n\n    def _redirect_stream(\n        self, request: Request, method: str\n    ) -> SyncByteStream | AsyncByteStream | None:\n        \"\"\"\n        Return the body that should be used for the redirect request.\n        \"\"\"\n        if method != request.method and method == \"GET\":\n            return None\n\n        return request.stream\n\n    def _set_timeout(self, request: Request) -> None:\n        if \"timeout\" not in request.extensions:\n            timeout = (\n                self.timeout\n                if isinstance(self.timeout, UseClientDefault)\n                else Timeout(self.timeout)\n            )\n            request.extensions = dict(**request.extensions, timeout=timeout.as_dict())\n\n\nclass Client(BaseClient):\n    \"\"\"\n    An HTTP client, with connection pooling, HTTP/2, redirects, cookie persistence, etc.\n\n    It can be shared between threads.\n\n    Usage:\n\n    ```python\n    >>> client = httpx.Client()\n    >>> response = client.get('https://example.org')\n    ```\n\n    **Parameters:**\n\n    * **auth** - *(optional)* An authentication class to use when sending\n    requests.\n    * **params** - *(optional)* Query parameters to include in request URLs, as\n    a string, dictionary, or sequence of two-tuples.\n    * **headers** - *(optional)* Dictionary of HTTP headers to include when\n    sending requests.\n    * **cookies** - *(optional)* Dictionary of Cookie items to include when\n    sending requests.\n    * **verify** - *(optional)* Either `True` to use an SSL context with the\n    default CA bundle, `False` to disable verification, or an instance of\n    `ssl.SSLContext` to use a custom context.\n    * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be\n    enabled. Defaults to `False`.\n    * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.\n    * **timeout** - *(optional)* The timeout configuration to use when sending\n    requests.\n    * **limits** - *(optional)* The limits configuration to use.\n    * **max_redirects** - *(optional)* The maximum number of redirect responses\n    that should be followed.\n    * **base_url** - *(optional)* A URL to use as the base when building\n    request URLs.\n    * **transport** - *(optional)* A transport class to use for sending requests\n    over the network.\n    * **trust_env** - *(optional)* Enables or disables usage of environment\n    variables for configuration.\n    * **default_encoding** - *(optional)* The default encoding to use for decoding\n    response text, if no charset information is included in a response Content-Type\n    header. Set to a callable for automatic character set detection. Default: \"utf-8\".\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        auth: AuthTypes | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        verify: ssl.SSLContext | str | bool = True,\n        cert: CertTypes | None = None,\n        trust_env: bool = True,\n        http1: bool = True,\n        http2: bool = False,\n        proxy: ProxyTypes | None = None,\n        mounts: None | (typing.Mapping[str, BaseTransport | None]) = None,\n        timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n        follow_redirects: bool = False,\n        limits: Limits = DEFAULT_LIMITS,\n        max_redirects: int = DEFAULT_MAX_REDIRECTS,\n        event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,\n        base_url: URL | str = \"\",\n        transport: BaseTransport | None = None,\n        default_encoding: str | typing.Callable[[bytes], str] = \"utf-8\",\n    ) -> None:\n        super().__init__(\n            auth=auth,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            timeout=timeout,\n            follow_redirects=follow_redirects,\n            max_redirects=max_redirects,\n            event_hooks=event_hooks,\n            base_url=base_url,\n            trust_env=trust_env,\n            default_encoding=default_encoding,\n        )\n\n        if http2:\n            try:\n                import h2  # noqa\n            except ImportError:  # pragma: no cover\n                raise ImportError(\n                    \"Using http2=True, but the 'h2' package is not installed. \"\n                    \"Make sure to install httpx using `pip install httpx[http2]`.\"\n                ) from None\n\n        allow_env_proxies = trust_env and transport is None\n        proxy_map = self._get_proxy_map(proxy, allow_env_proxies)\n\n        self._transport = self._init_transport(\n            verify=verify,\n            cert=cert,\n            trust_env=trust_env,\n            http1=http1,\n            http2=http2,\n            limits=limits,\n            transport=transport,\n        )\n        self._mounts: dict[URLPattern, BaseTransport | None] = {\n            URLPattern(key): None\n            if proxy is None\n            else self._init_proxy_transport(\n                proxy,\n                verify=verify,\n                cert=cert,\n                trust_env=trust_env,\n                http1=http1,\n                http2=http2,\n                limits=limits,\n            )\n            for key, proxy in proxy_map.items()\n        }\n        if mounts is not None:\n            self._mounts.update(\n                {URLPattern(key): transport for key, transport in mounts.items()}\n            )\n\n        self._mounts = dict(sorted(self._mounts.items()))\n\n    def _init_transport(\n        self,\n        verify: ssl.SSLContext | str | bool = True,\n        cert: CertTypes | None = None,\n        trust_env: bool = True,\n        http1: bool = True,\n        http2: bool = False,\n        limits: Limits = DEFAULT_LIMITS,\n        transport: BaseTransport | None = None,\n    ) -> BaseTransport:\n        if transport is not None:\n            return transport\n\n        return HTTPTransport(\n            verify=verify,\n            cert=cert,\n            trust_env=trust_env,\n            http1=http1,\n            http2=http2,\n            limits=limits,\n        )\n\n    def _init_proxy_transport(\n        self,\n        proxy: Proxy,\n        verify: ssl.SSLContext | str | bool = True,\n        cert: CertTypes | None = None,\n        trust_env: bool = True,\n        http1: bool = True,\n        http2: bool = False,\n        limits: Limits = DEFAULT_LIMITS,\n    ) -> BaseTransport:\n        return HTTPTransport(\n            verify=verify,\n            cert=cert,\n            trust_env=trust_env,\n            http1=http1,\n            http2=http2,\n            limits=limits,\n            proxy=proxy,\n        )\n\n    def _transport_for_url(self, url: URL) -> BaseTransport:\n        \"\"\"\n        Returns the transport instance that should be used for a given URL.\n        This will either be the standard connection pool, or a proxy.\n        \"\"\"\n        for pattern, transport in self._mounts.items():\n            if pattern.matches(url):\n                return self._transport if transport is None else transport\n\n        return self._transport\n\n    def request(\n        self,\n        method: str,\n        url: URL | str,\n        *,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Build and send a request.\n\n        Equivalent to:\n\n        ```python\n        request = client.build_request(...)\n        response = client.send(request, ...)\n        ```\n\n        See `Client.build_request()`, `Client.send()` and\n        [Merging of configuration][0] for how the various parameters\n        are merged with client-level configuration.\n\n        [0]: /advanced/clients/#merging-of-configuration\n        \"\"\"\n        if cookies is not None:\n            message = (\n                \"Setting per-request cookies=<...> is being deprecated, because \"\n                \"the expected behaviour on cookie persistence is ambiguous. Set \"\n                \"cookies directly on the client instance instead.\"\n            )\n            warnings.warn(message, DeprecationWarning, stacklevel=2)\n\n        request = self.build_request(\n            method=method,\n            url=url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            timeout=timeout,\n            extensions=extensions,\n        )\n        return self.send(request, auth=auth, follow_redirects=follow_redirects)\n\n    @contextmanager\n    def stream(\n        self,\n        method: str,\n        url: URL | str,\n        *,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> typing.Iterator[Response]:\n        \"\"\"\n        Alternative to `httpx.request()` that streams the response body\n        instead of loading it into memory at once.\n\n        **Parameters**: See `httpx.request`.\n\n        See also: [Streaming Responses][0]\n\n        [0]: /quickstart#streaming-responses\n        \"\"\"\n        request = self.build_request(\n            method=method,\n            url=url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            timeout=timeout,\n            extensions=extensions,\n        )\n        response = self.send(\n            request=request,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            stream=True,\n        )\n        try:\n            yield response\n        finally:\n            response.close()\n\n    def send(\n        self,\n        request: Request,\n        *,\n        stream: bool = False,\n        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n    ) -> Response:\n        \"\"\"\n        Send a request.\n\n        The request is sent as-is, unmodified.\n\n        Typically you'll want to build one with `Client.build_request()`\n        so that any client-level configuration is merged into the request,\n        but passing an explicit `httpx.Request()` is supported as well.\n\n        See also: [Request instances][0]\n\n        [0]: /advanced/clients/#request-instances\n        \"\"\"\n        if self._state == ClientState.CLOSED:\n            raise RuntimeError(\"Cannot send a request, as the client has been closed.\")\n\n        self._state = ClientState.OPENED\n        follow_redirects = (\n            self.follow_redirects\n            if isinstance(follow_redirects, UseClientDefault)\n            else follow_redirects\n        )\n\n        self._set_timeout(request)\n\n        auth = self._build_request_auth(request, auth)\n\n        response = self._send_handling_auth(\n            request,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            history=[],\n        )\n        try:\n            if not stream:\n                response.read()\n\n            return response\n\n        except BaseException as exc:\n            response.close()\n            raise exc\n\n    def _send_handling_auth(\n        self,\n        request: Request,\n        auth: Auth,\n        follow_redirects: bool,\n        history: list[Response],\n    ) -> Response:\n        auth_flow = auth.sync_auth_flow(request)\n        try:\n            request = next(auth_flow)\n\n            while True:\n                response = self._send_handling_redirects(\n                    request,\n                    follow_redirects=follow_redirects,\n                    history=history,\n                )\n                try:\n                    try:\n                        next_request = auth_flow.send(response)\n                    except StopIteration:\n                        return response\n\n                    response.history = list(history)\n                    response.read()\n                    request = next_request\n                    history.append(response)\n\n                except BaseException as exc:\n                    response.close()\n                    raise exc\n        finally:\n            auth_flow.close()\n\n    def _send_handling_redirects(\n        self,\n        request: Request,\n        follow_redirects: bool,\n        history: list[Response],\n    ) -> Response:\n        while True:\n            if len(history) > self.max_redirects:\n                raise TooManyRedirects(\n                    \"Exceeded maximum allowed redirects.\", request=request\n                )\n\n            for hook in self._event_hooks[\"request\"]:\n                hook(request)\n\n            response = self._send_single_request(request)\n            try:\n                for hook in self._event_hooks[\"response\"]:\n                    hook(response)\n                response.history = list(history)\n\n                if not response.has_redirect_location:\n                    return response\n\n                request = self._build_redirect_request(request, response)\n                history = history + [response]\n\n                if follow_redirects:\n                    response.read()\n                else:\n                    response.next_request = request\n                    return response\n\n            except BaseException as exc:\n                response.close()\n                raise exc\n\n    def _send_single_request(self, request: Request) -> Response:\n        \"\"\"\n        Sends a single request, without handling any redirections.\n        \"\"\"\n        transport = self._transport_for_url(request.url)\n        start = time.perf_counter()\n\n        if not isinstance(request.stream, SyncByteStream):\n            raise RuntimeError(\n                \"Attempted to send an async request with a sync Client instance.\"\n            )\n\n        with request_context(request=request):\n            response = transport.handle_request(request)\n\n        assert isinstance(response.stream, SyncByteStream)\n\n        response.request = request\n        response.stream = BoundSyncStream(\n            response.stream, response=response, start=start\n        )\n        self.cookies.extract_cookies(response)\n        response.default_encoding = self._default_encoding\n\n        logger.info(\n            'HTTP Request: %s %s \"%s %d %s\"',\n            request.method,\n            request.url,\n            response.http_version,\n            response.status_code,\n            response.reason_phrase,\n        )\n\n        return response\n\n    def get(\n        self,\n        url: URL | str,\n        *,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `GET` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return self.request(\n            \"GET\",\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def options(\n        self,\n        url: URL | str,\n        *,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send an `OPTIONS` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return self.request(\n            \"OPTIONS\",\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def head(\n        self,\n        url: URL | str,\n        *,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `HEAD` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return self.request(\n            \"HEAD\",\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def post(\n        self,\n        url: URL | str,\n        *,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `POST` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return self.request(\n            \"POST\",\n            url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def put(\n        self,\n        url: URL | str,\n        *,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `PUT` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return self.request(\n            \"PUT\",\n            url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def patch(\n        self,\n        url: URL | str,\n        *,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `PATCH` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return self.request(\n            \"PATCH\",\n            url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def delete(\n        self,\n        url: URL | str,\n        *,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `DELETE` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return self.request(\n            \"DELETE\",\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    def close(self) -> None:\n        \"\"\"\n        Close transport and proxies.\n        \"\"\"\n        if self._state != ClientState.CLOSED:\n            self._state = ClientState.CLOSED\n\n            self._transport.close()\n            for transport in self._mounts.values():\n                if transport is not None:\n                    transport.close()\n\n    def __enter__(self: T) -> T:\n        if self._state != ClientState.UNOPENED:\n            msg = {\n                ClientState.OPENED: \"Cannot open a client instance more than once.\",\n                ClientState.CLOSED: (\n                    \"Cannot reopen a client instance, once it has been closed.\"\n                ),\n            }[self._state]\n            raise RuntimeError(msg)\n\n        self._state = ClientState.OPENED\n\n        self._transport.__enter__()\n        for transport in self._mounts.values():\n            if transport is not None:\n                transport.__enter__()\n        return self\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None = None,\n        exc_value: BaseException | None = None,\n        traceback: TracebackType | None = None,\n    ) -> None:\n        self._state = ClientState.CLOSED\n\n        self._transport.__exit__(exc_type, exc_value, traceback)\n        for transport in self._mounts.values():\n            if transport is not None:\n                transport.__exit__(exc_type, exc_value, traceback)\n\n\nclass AsyncClient(BaseClient):\n    \"\"\"\n    An asynchronous HTTP client, with connection pooling, HTTP/2, redirects,\n    cookie persistence, etc.\n\n    It can be shared between tasks.\n\n    Usage:\n\n    ```python\n    >>> async with httpx.AsyncClient() as client:\n    >>>     response = await client.get('https://example.org')\n    ```\n\n    **Parameters:**\n\n    * **auth** - *(optional)* An authentication class to use when sending\n    requests.\n    * **params** - *(optional)* Query parameters to include in request URLs, as\n    a string, dictionary, or sequence of two-tuples.\n    * **headers** - *(optional)* Dictionary of HTTP headers to include when\n    sending requests.\n    * **cookies** - *(optional)* Dictionary of Cookie items to include when\n    sending requests.\n    * **verify** - *(optional)* Either `True` to use an SSL context with the\n    default CA bundle, `False` to disable verification, or an instance of\n    `ssl.SSLContext` to use a custom context.\n    * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be\n    enabled. Defaults to `False`.\n    * **proxy** - *(optional)* A proxy URL where all the traffic should be routed.\n    * **timeout** - *(optional)* The timeout configuration to use when sending\n    requests.\n    * **limits** - *(optional)* The limits configuration to use.\n    * **max_redirects** - *(optional)* The maximum number of redirect responses\n    that should be followed.\n    * **base_url** - *(optional)* A URL to use as the base when building\n    request URLs.\n    * **transport** - *(optional)* A transport class to use for sending requests\n    over the network.\n    * **trust_env** - *(optional)* Enables or disables usage of environment\n    variables for configuration.\n    * **default_encoding** - *(optional)* The default encoding to use for decoding\n    response text, if no charset information is included in a response Content-Type\n    header. Set to a callable for automatic character set detection. Default: \"utf-8\".\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        auth: AuthTypes | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        verify: ssl.SSLContext | str | bool = True,\n        cert: CertTypes | None = None,\n        http1: bool = True,\n        http2: bool = False,\n        proxy: ProxyTypes | None = None,\n        mounts: None | (typing.Mapping[str, AsyncBaseTransport | None]) = None,\n        timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,\n        follow_redirects: bool = False,\n        limits: Limits = DEFAULT_LIMITS,\n        max_redirects: int = DEFAULT_MAX_REDIRECTS,\n        event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None,\n        base_url: URL | str = \"\",\n        transport: AsyncBaseTransport | None = None,\n        trust_env: bool = True,\n        default_encoding: str | typing.Callable[[bytes], str] = \"utf-8\",\n    ) -> None:\n        super().__init__(\n            auth=auth,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            timeout=timeout,\n            follow_redirects=follow_redirects,\n            max_redirects=max_redirects,\n            event_hooks=event_hooks,\n            base_url=base_url,\n            trust_env=trust_env,\n            default_encoding=default_encoding,\n        )\n\n        if http2:\n            try:\n                import h2  # noqa\n            except ImportError:  # pragma: no cover\n                raise ImportError(\n                    \"Using http2=True, but the 'h2' package is not installed. \"\n                    \"Make sure to install httpx using `pip install httpx[http2]`.\"\n                ) from None\n\n        allow_env_proxies = trust_env and transport is None\n        proxy_map = self._get_proxy_map(proxy, allow_env_proxies)\n\n        self._transport = self._init_transport(\n            verify=verify,\n            cert=cert,\n            trust_env=trust_env,\n            http1=http1,\n            http2=http2,\n            limits=limits,\n            transport=transport,\n        )\n\n        self._mounts: dict[URLPattern, AsyncBaseTransport | None] = {\n            URLPattern(key): None\n            if proxy is None\n            else self._init_proxy_transport(\n                proxy,\n                verify=verify,\n                cert=cert,\n                trust_env=trust_env,\n                http1=http1,\n                http2=http2,\n                limits=limits,\n            )\n            for key, proxy in proxy_map.items()\n        }\n        if mounts is not None:\n            self._mounts.update(\n                {URLPattern(key): transport for key, transport in mounts.items()}\n            )\n        self._mounts = dict(sorted(self._mounts.items()))\n\n    def _init_transport(\n        self,\n        verify: ssl.SSLContext | str | bool = True,\n        cert: CertTypes | None = None,\n        trust_env: bool = True,\n        http1: bool = True,\n        http2: bool = False,\n        limits: Limits = DEFAULT_LIMITS,\n        transport: AsyncBaseTransport | None = None,\n    ) -> AsyncBaseTransport:\n        if transport is not None:\n            return transport\n\n        return AsyncHTTPTransport(\n            verify=verify,\n            cert=cert,\n            trust_env=trust_env,\n            http1=http1,\n            http2=http2,\n            limits=limits,\n        )\n\n    def _init_proxy_transport(\n        self,\n        proxy: Proxy,\n        verify: ssl.SSLContext | str | bool = True,\n        cert: CertTypes | None = None,\n        trust_env: bool = True,\n        http1: bool = True,\n        http2: bool = False,\n        limits: Limits = DEFAULT_LIMITS,\n    ) -> AsyncBaseTransport:\n        return AsyncHTTPTransport(\n            verify=verify,\n            cert=cert,\n            trust_env=trust_env,\n            http1=http1,\n            http2=http2,\n            limits=limits,\n            proxy=proxy,\n        )\n\n    def _transport_for_url(self, url: URL) -> AsyncBaseTransport:\n        \"\"\"\n        Returns the transport instance that should be used for a given URL.\n        This will either be the standard connection pool, or a proxy.\n        \"\"\"\n        for pattern, transport in self._mounts.items():\n            if pattern.matches(url):\n                return self._transport if transport is None else transport\n\n        return self._transport\n\n    async def request(\n        self,\n        method: str,\n        url: URL | str,\n        *,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Build and send a request.\n\n        Equivalent to:\n\n        ```python\n        request = client.build_request(...)\n        response = await client.send(request, ...)\n        ```\n\n        See `AsyncClient.build_request()`, `AsyncClient.send()`\n        and [Merging of configuration][0] for how the various parameters\n        are merged with client-level configuration.\n\n        [0]: /advanced/clients/#merging-of-configuration\n        \"\"\"\n\n        if cookies is not None:  # pragma: no cover\n            message = (\n                \"Setting per-request cookies=<...> is being deprecated, because \"\n                \"the expected behaviour on cookie persistence is ambiguous. Set \"\n                \"cookies directly on the client instance instead.\"\n            )\n            warnings.warn(message, DeprecationWarning, stacklevel=2)\n\n        request = self.build_request(\n            method=method,\n            url=url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            timeout=timeout,\n            extensions=extensions,\n        )\n        return await self.send(request, auth=auth, follow_redirects=follow_redirects)\n\n    @asynccontextmanager\n    async def stream(\n        self,\n        method: str,\n        url: URL | str,\n        *,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> typing.AsyncIterator[Response]:\n        \"\"\"\n        Alternative to `httpx.request()` that streams the response body\n        instead of loading it into memory at once.\n\n        **Parameters**: See `httpx.request`.\n\n        See also: [Streaming Responses][0]\n\n        [0]: /quickstart#streaming-responses\n        \"\"\"\n        request = self.build_request(\n            method=method,\n            url=url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            timeout=timeout,\n            extensions=extensions,\n        )\n        response = await self.send(\n            request=request,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            stream=True,\n        )\n        try:\n            yield response\n        finally:\n            await response.aclose()\n\n    async def send(\n        self,\n        request: Request,\n        *,\n        stream: bool = False,\n        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n    ) -> Response:\n        \"\"\"\n        Send a request.\n\n        The request is sent as-is, unmodified.\n\n        Typically you'll want to build one with `AsyncClient.build_request()`\n        so that any client-level configuration is merged into the request,\n        but passing an explicit `httpx.Request()` is supported as well.\n\n        See also: [Request instances][0]\n\n        [0]: /advanced/clients/#request-instances\n        \"\"\"\n        if self._state == ClientState.CLOSED:\n            raise RuntimeError(\"Cannot send a request, as the client has been closed.\")\n\n        self._state = ClientState.OPENED\n        follow_redirects = (\n            self.follow_redirects\n            if isinstance(follow_redirects, UseClientDefault)\n            else follow_redirects\n        )\n\n        self._set_timeout(request)\n\n        auth = self._build_request_auth(request, auth)\n\n        response = await self._send_handling_auth(\n            request,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            history=[],\n        )\n        try:\n            if not stream:\n                await response.aread()\n\n            return response\n\n        except BaseException as exc:\n            await response.aclose()\n            raise exc\n\n    async def _send_handling_auth(\n        self,\n        request: Request,\n        auth: Auth,\n        follow_redirects: bool,\n        history: list[Response],\n    ) -> Response:\n        auth_flow = auth.async_auth_flow(request)\n        try:\n            request = await auth_flow.__anext__()\n\n            while True:\n                response = await self._send_handling_redirects(\n                    request,\n                    follow_redirects=follow_redirects,\n                    history=history,\n                )\n                try:\n                    try:\n                        next_request = await auth_flow.asend(response)\n                    except StopAsyncIteration:\n                        return response\n\n                    response.history = list(history)\n                    await response.aread()\n                    request = next_request\n                    history.append(response)\n\n                except BaseException as exc:\n                    await response.aclose()\n                    raise exc\n        finally:\n            await auth_flow.aclose()\n\n    async def _send_handling_redirects(\n        self,\n        request: Request,\n        follow_redirects: bool,\n        history: list[Response],\n    ) -> Response:\n        while True:\n            if len(history) > self.max_redirects:\n                raise TooManyRedirects(\n                    \"Exceeded maximum allowed redirects.\", request=request\n                )\n\n            for hook in self._event_hooks[\"request\"]:\n                await hook(request)\n\n            response = await self._send_single_request(request)\n            try:\n                for hook in self._event_hooks[\"response\"]:\n                    await hook(response)\n\n                response.history = list(history)\n\n                if not response.has_redirect_location:\n                    return response\n\n                request = self._build_redirect_request(request, response)\n                history = history + [response]\n\n                if follow_redirects:\n                    await response.aread()\n                else:\n                    response.next_request = request\n                    return response\n\n            except BaseException as exc:\n                await response.aclose()\n                raise exc\n\n    async def _send_single_request(self, request: Request) -> Response:\n        \"\"\"\n        Sends a single request, without handling any redirections.\n        \"\"\"\n        transport = self._transport_for_url(request.url)\n        start = time.perf_counter()\n\n        if not isinstance(request.stream, AsyncByteStream):\n            raise RuntimeError(\n                \"Attempted to send a sync request with an AsyncClient instance.\"\n            )\n\n        with request_context(request=request):\n            response = await transport.handle_async_request(request)\n\n        assert isinstance(response.stream, AsyncByteStream)\n        response.request = request\n        response.stream = BoundAsyncStream(\n            response.stream, response=response, start=start\n        )\n        self.cookies.extract_cookies(response)\n        response.default_encoding = self._default_encoding\n\n        logger.info(\n            'HTTP Request: %s %s \"%s %d %s\"',\n            request.method,\n            request.url,\n            response.http_version,\n            response.status_code,\n            response.reason_phrase,\n        )\n\n        return response\n\n    async def get(\n        self,\n        url: URL | str,\n        *,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `GET` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return await self.request(\n            \"GET\",\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    async def options(\n        self,\n        url: URL | str,\n        *,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send an `OPTIONS` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return await self.request(\n            \"OPTIONS\",\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    async def head(\n        self,\n        url: URL | str,\n        *,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `HEAD` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return await self.request(\n            \"HEAD\",\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    async def post(\n        self,\n        url: URL | str,\n        *,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `POST` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return await self.request(\n            \"POST\",\n            url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    async def put(\n        self,\n        url: URL | str,\n        *,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `PUT` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return await self.request(\n            \"PUT\",\n            url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    async def patch(\n        self,\n        url: URL | str,\n        *,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `PATCH` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return await self.request(\n            \"PATCH\",\n            url,\n            content=content,\n            data=data,\n            files=files,\n            json=json,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    async def delete(\n        self,\n        url: URL | str,\n        *,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,\n        timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,\n        extensions: RequestExtensions | None = None,\n    ) -> Response:\n        \"\"\"\n        Send a `DELETE` request.\n\n        **Parameters**: See `httpx.request`.\n        \"\"\"\n        return await self.request(\n            \"DELETE\",\n            url,\n            params=params,\n            headers=headers,\n            cookies=cookies,\n            auth=auth,\n            follow_redirects=follow_redirects,\n            timeout=timeout,\n            extensions=extensions,\n        )\n\n    async def aclose(self) -> None:\n        \"\"\"\n        Close transport and proxies.\n        \"\"\"\n        if self._state != ClientState.CLOSED:\n            self._state = ClientState.CLOSED\n\n            await self._transport.aclose()\n            for proxy in self._mounts.values():\n                if proxy is not None:\n                    await proxy.aclose()\n\n    async def __aenter__(self: U) -> U:\n        if self._state != ClientState.UNOPENED:\n            msg = {\n                ClientState.OPENED: \"Cannot open a client instance more than once.\",\n                ClientState.CLOSED: (\n                    \"Cannot reopen a client instance, once it has been closed.\"\n                ),\n            }[self._state]\n            raise RuntimeError(msg)\n\n        self._state = ClientState.OPENED\n\n        await self._transport.__aenter__()\n        for proxy in self._mounts.values():\n            if proxy is not None:\n                await proxy.__aenter__()\n        return self\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None = None,\n        exc_value: BaseException | None = None,\n        traceback: TracebackType | None = None,\n    ) -> None:\n        self._state = ClientState.CLOSED\n\n        await self._transport.__aexit__(exc_type, exc_value, traceback)\n        for proxy in self._mounts.values():\n            if proxy is not None:\n                await proxy.__aexit__(exc_type, exc_value, traceback)\n"
  },
  {
    "path": "httpx/_config.py",
    "content": "from __future__ import annotations\n\nimport os\nimport typing\n\nfrom ._models import Headers\nfrom ._types import CertTypes, HeaderTypes, TimeoutTypes\nfrom ._urls import URL\n\nif typing.TYPE_CHECKING:\n    import ssl  # pragma: no cover\n\n__all__ = [\"Limits\", \"Proxy\", \"Timeout\", \"create_ssl_context\"]\n\n\nclass UnsetType:\n    pass  # pragma: no cover\n\n\nUNSET = UnsetType()\n\n\ndef create_ssl_context(\n    verify: ssl.SSLContext | str | bool = True,\n    cert: CertTypes | None = None,\n    trust_env: bool = True,\n) -> ssl.SSLContext:\n    import ssl\n    import warnings\n\n    import certifi\n\n    if verify is True:\n        if trust_env and os.environ.get(\"SSL_CERT_FILE\"):  # pragma: nocover\n            ctx = ssl.create_default_context(cafile=os.environ[\"SSL_CERT_FILE\"])\n        elif trust_env and os.environ.get(\"SSL_CERT_DIR\"):  # pragma: nocover\n            ctx = ssl.create_default_context(capath=os.environ[\"SSL_CERT_DIR\"])\n        else:\n            # Default case...\n            ctx = ssl.create_default_context(cafile=certifi.where())\n    elif verify is False:\n        ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n        ctx.check_hostname = False\n        ctx.verify_mode = ssl.CERT_NONE\n    elif isinstance(verify, str):  # pragma: nocover\n        message = (\n            \"`verify=<str>` is deprecated. \"\n            \"Use `verify=ssl.create_default_context(cafile=...)` \"\n            \"or `verify=ssl.create_default_context(capath=...)` instead.\"\n        )\n        warnings.warn(message, DeprecationWarning)\n        if os.path.isdir(verify):\n            return ssl.create_default_context(capath=verify)\n        return ssl.create_default_context(cafile=verify)\n    else:\n        ctx = verify\n\n    if cert:  # pragma: nocover\n        message = (\n            \"`cert=...` is deprecated. Use `verify=<ssl_context>` instead,\"\n            \"with `.load_cert_chain()` to configure the certificate chain.\"\n        )\n        warnings.warn(message, DeprecationWarning)\n        if isinstance(cert, str):\n            ctx.load_cert_chain(cert)\n        else:\n            ctx.load_cert_chain(*cert)\n\n    return ctx\n\n\nclass Timeout:\n    \"\"\"\n    Timeout configuration.\n\n    **Usage**:\n\n    Timeout(None)               # No timeouts.\n    Timeout(5.0)                # 5s timeout on all operations.\n    Timeout(None, connect=5.0)  # 5s timeout on connect, no other timeouts.\n    Timeout(5.0, connect=10.0)  # 10s timeout on connect. 5s timeout elsewhere.\n    Timeout(5.0, pool=None)     # No timeout on acquiring connection from pool.\n                                # 5s timeout elsewhere.\n    \"\"\"\n\n    def __init__(\n        self,\n        timeout: TimeoutTypes | UnsetType = UNSET,\n        *,\n        connect: None | float | UnsetType = UNSET,\n        read: None | float | UnsetType = UNSET,\n        write: None | float | UnsetType = UNSET,\n        pool: None | float | UnsetType = UNSET,\n    ) -> None:\n        if isinstance(timeout, Timeout):\n            # Passed as a single explicit Timeout.\n            assert connect is UNSET\n            assert read is UNSET\n            assert write is UNSET\n            assert pool is UNSET\n            self.connect = timeout.connect  # type: typing.Optional[float]\n            self.read = timeout.read  # type: typing.Optional[float]\n            self.write = timeout.write  # type: typing.Optional[float]\n            self.pool = timeout.pool  # type: typing.Optional[float]\n        elif isinstance(timeout, tuple):\n            # Passed as a tuple.\n            self.connect = timeout[0]\n            self.read = timeout[1]\n            self.write = None if len(timeout) < 3 else timeout[2]\n            self.pool = None if len(timeout) < 4 else timeout[3]\n        elif not (\n            isinstance(connect, UnsetType)\n            or isinstance(read, UnsetType)\n            or isinstance(write, UnsetType)\n            or isinstance(pool, UnsetType)\n        ):\n            self.connect = connect\n            self.read = read\n            self.write = write\n            self.pool = pool\n        else:\n            if isinstance(timeout, UnsetType):\n                raise ValueError(\n                    \"httpx.Timeout must either include a default, or set all \"\n                    \"four parameters explicitly.\"\n                )\n            self.connect = timeout if isinstance(connect, UnsetType) else connect\n            self.read = timeout if isinstance(read, UnsetType) else read\n            self.write = timeout if isinstance(write, UnsetType) else write\n            self.pool = timeout if isinstance(pool, UnsetType) else pool\n\n    def as_dict(self) -> dict[str, float | None]:\n        return {\n            \"connect\": self.connect,\n            \"read\": self.read,\n            \"write\": self.write,\n            \"pool\": self.pool,\n        }\n\n    def __eq__(self, other: typing.Any) -> bool:\n        return (\n            isinstance(other, self.__class__)\n            and self.connect == other.connect\n            and self.read == other.read\n            and self.write == other.write\n            and self.pool == other.pool\n        )\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        if len({self.connect, self.read, self.write, self.pool}) == 1:\n            return f\"{class_name}(timeout={self.connect})\"\n        return (\n            f\"{class_name}(connect={self.connect}, \"\n            f\"read={self.read}, write={self.write}, pool={self.pool})\"\n        )\n\n\nclass Limits:\n    \"\"\"\n    Configuration for limits to various client behaviors.\n\n    **Parameters:**\n\n    * **max_connections** - The maximum number of concurrent connections that may be\n            established.\n    * **max_keepalive_connections** - Allow the connection pool to maintain\n            keep-alive connections below this point. Should be less than or equal\n            to `max_connections`.\n    * **keepalive_expiry** - Time limit on idle keep-alive connections in seconds.\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        max_connections: int | None = None,\n        max_keepalive_connections: int | None = None,\n        keepalive_expiry: float | None = 5.0,\n    ) -> None:\n        self.max_connections = max_connections\n        self.max_keepalive_connections = max_keepalive_connections\n        self.keepalive_expiry = keepalive_expiry\n\n    def __eq__(self, other: typing.Any) -> bool:\n        return (\n            isinstance(other, self.__class__)\n            and self.max_connections == other.max_connections\n            and self.max_keepalive_connections == other.max_keepalive_connections\n            and self.keepalive_expiry == other.keepalive_expiry\n        )\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        return (\n            f\"{class_name}(max_connections={self.max_connections}, \"\n            f\"max_keepalive_connections={self.max_keepalive_connections}, \"\n            f\"keepalive_expiry={self.keepalive_expiry})\"\n        )\n\n\nclass Proxy:\n    def __init__(\n        self,\n        url: URL | str,\n        *,\n        ssl_context: ssl.SSLContext | None = None,\n        auth: tuple[str, str] | None = None,\n        headers: HeaderTypes | None = None,\n    ) -> None:\n        url = URL(url)\n        headers = Headers(headers)\n\n        if url.scheme not in (\"http\", \"https\", \"socks5\", \"socks5h\"):\n            raise ValueError(f\"Unknown scheme for proxy URL {url!r}\")\n\n        if url.username or url.password:\n            # Remove any auth credentials from the URL.\n            auth = (url.username, url.password)\n            url = url.copy_with(username=None, password=None)\n\n        self.url = url\n        self.auth = auth\n        self.headers = headers\n        self.ssl_context = ssl_context\n\n    @property\n    def raw_auth(self) -> tuple[bytes, bytes] | None:\n        # The proxy authentication as raw bytes.\n        return (\n            None\n            if self.auth is None\n            else (self.auth[0].encode(\"utf-8\"), self.auth[1].encode(\"utf-8\"))\n        )\n\n    def __repr__(self) -> str:\n        # The authentication is represented with the password component masked.\n        auth = (self.auth[0], \"********\") if self.auth else None\n\n        # Build a nice concise representation.\n        url_str = f\"{str(self.url)!r}\"\n        auth_str = f\", auth={auth!r}\" if auth else \"\"\n        headers_str = f\", headers={dict(self.headers)!r}\" if self.headers else \"\"\n        return f\"Proxy({url_str}{auth_str}{headers_str})\"\n\n\nDEFAULT_TIMEOUT_CONFIG = Timeout(timeout=5.0)\nDEFAULT_LIMITS = Limits(max_connections=100, max_keepalive_connections=20)\nDEFAULT_MAX_REDIRECTS = 20\n"
  },
  {
    "path": "httpx/_content.py",
    "content": "from __future__ import annotations\n\nimport inspect\nimport warnings\nfrom json import dumps as json_dumps\nfrom typing import (\n    Any,\n    AsyncIterable,\n    AsyncIterator,\n    Iterable,\n    Iterator,\n    Mapping,\n)\nfrom urllib.parse import urlencode\n\nfrom ._exceptions import StreamClosed, StreamConsumed\nfrom ._multipart import MultipartStream\nfrom ._types import (\n    AsyncByteStream,\n    RequestContent,\n    RequestData,\n    RequestFiles,\n    ResponseContent,\n    SyncByteStream,\n)\nfrom ._utils import peek_filelike_length, primitive_value_to_str\n\n__all__ = [\"ByteStream\"]\n\n\nclass ByteStream(AsyncByteStream, SyncByteStream):\n    def __init__(self, stream: bytes) -> None:\n        self._stream = stream\n\n    def __iter__(self) -> Iterator[bytes]:\n        yield self._stream\n\n    async def __aiter__(self) -> AsyncIterator[bytes]:\n        yield self._stream\n\n\nclass IteratorByteStream(SyncByteStream):\n    CHUNK_SIZE = 65_536\n\n    def __init__(self, stream: Iterable[bytes]) -> None:\n        self._stream = stream\n        self._is_stream_consumed = False\n        self._is_generator = inspect.isgenerator(stream)\n\n    def __iter__(self) -> Iterator[bytes]:\n        if self._is_stream_consumed and self._is_generator:\n            raise StreamConsumed()\n\n        self._is_stream_consumed = True\n        if hasattr(self._stream, \"read\"):\n            # File-like interfaces should use 'read' directly.\n            chunk = self._stream.read(self.CHUNK_SIZE)\n            while chunk:\n                yield chunk\n                chunk = self._stream.read(self.CHUNK_SIZE)\n        else:\n            # Otherwise iterate.\n            for part in self._stream:\n                yield part\n\n\nclass AsyncIteratorByteStream(AsyncByteStream):\n    CHUNK_SIZE = 65_536\n\n    def __init__(self, stream: AsyncIterable[bytes]) -> None:\n        self._stream = stream\n        self._is_stream_consumed = False\n        self._is_generator = inspect.isasyncgen(stream)\n\n    async def __aiter__(self) -> AsyncIterator[bytes]:\n        if self._is_stream_consumed and self._is_generator:\n            raise StreamConsumed()\n\n        self._is_stream_consumed = True\n        if hasattr(self._stream, \"aread\"):\n            # File-like interfaces should use 'aread' directly.\n            chunk = await self._stream.aread(self.CHUNK_SIZE)\n            while chunk:\n                yield chunk\n                chunk = await self._stream.aread(self.CHUNK_SIZE)\n        else:\n            # Otherwise iterate.\n            async for part in self._stream:\n                yield part\n\n\nclass UnattachedStream(AsyncByteStream, SyncByteStream):\n    \"\"\"\n    If a request or response is serialized using pickle, then it is no longer\n    attached to a stream for I/O purposes. Any stream operations should result\n    in `httpx.StreamClosed`.\n    \"\"\"\n\n    def __iter__(self) -> Iterator[bytes]:\n        raise StreamClosed()\n\n    async def __aiter__(self) -> AsyncIterator[bytes]:\n        raise StreamClosed()\n        yield b\"\"  # pragma: no cover\n\n\ndef encode_content(\n    content: str | bytes | Iterable[bytes] | AsyncIterable[bytes],\n) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]:\n    if isinstance(content, (bytes, str)):\n        body = content.encode(\"utf-8\") if isinstance(content, str) else content\n        content_length = len(body)\n        headers = {\"Content-Length\": str(content_length)} if body else {}\n        return headers, ByteStream(body)\n\n    elif isinstance(content, Iterable) and not isinstance(content, dict):\n        # `not isinstance(content, dict)` is a bit oddly specific, but it\n        # catches a case that's easy for users to make in error, and would\n        # otherwise pass through here, like any other bytes-iterable,\n        # because `dict` happens to be iterable. See issue #2491.\n        content_length_or_none = peek_filelike_length(content)\n\n        if content_length_or_none is None:\n            headers = {\"Transfer-Encoding\": \"chunked\"}\n        else:\n            headers = {\"Content-Length\": str(content_length_or_none)}\n        return headers, IteratorByteStream(content)  # type: ignore\n\n    elif isinstance(content, AsyncIterable):\n        headers = {\"Transfer-Encoding\": \"chunked\"}\n        return headers, AsyncIteratorByteStream(content)\n\n    raise TypeError(f\"Unexpected type for 'content', {type(content)!r}\")\n\n\ndef encode_urlencoded_data(\n    data: RequestData,\n) -> tuple[dict[str, str], ByteStream]:\n    plain_data = []\n    for key, value in data.items():\n        if isinstance(value, (list, tuple)):\n            plain_data.extend([(key, primitive_value_to_str(item)) for item in value])\n        else:\n            plain_data.append((key, primitive_value_to_str(value)))\n    body = urlencode(plain_data, doseq=True).encode(\"utf-8\")\n    content_length = str(len(body))\n    content_type = \"application/x-www-form-urlencoded\"\n    headers = {\"Content-Length\": content_length, \"Content-Type\": content_type}\n    return headers, ByteStream(body)\n\n\ndef encode_multipart_data(\n    data: RequestData, files: RequestFiles, boundary: bytes | None\n) -> tuple[dict[str, str], MultipartStream]:\n    multipart = MultipartStream(data=data, files=files, boundary=boundary)\n    headers = multipart.get_headers()\n    return headers, multipart\n\n\ndef encode_text(text: str) -> tuple[dict[str, str], ByteStream]:\n    body = text.encode(\"utf-8\")\n    content_length = str(len(body))\n    content_type = \"text/plain; charset=utf-8\"\n    headers = {\"Content-Length\": content_length, \"Content-Type\": content_type}\n    return headers, ByteStream(body)\n\n\ndef encode_html(html: str) -> tuple[dict[str, str], ByteStream]:\n    body = html.encode(\"utf-8\")\n    content_length = str(len(body))\n    content_type = \"text/html; charset=utf-8\"\n    headers = {\"Content-Length\": content_length, \"Content-Type\": content_type}\n    return headers, ByteStream(body)\n\n\ndef encode_json(json: Any) -> tuple[dict[str, str], ByteStream]:\n    body = json_dumps(\n        json, ensure_ascii=False, separators=(\",\", \":\"), allow_nan=False\n    ).encode(\"utf-8\")\n    content_length = str(len(body))\n    content_type = \"application/json\"\n    headers = {\"Content-Length\": content_length, \"Content-Type\": content_type}\n    return headers, ByteStream(body)\n\n\ndef encode_request(\n    content: RequestContent | None = None,\n    data: RequestData | None = None,\n    files: RequestFiles | None = None,\n    json: Any | None = None,\n    boundary: bytes | None = None,\n) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]:\n    \"\"\"\n    Handles encoding the given `content`, `data`, `files`, and `json`,\n    returning a two-tuple of (<headers>, <stream>).\n    \"\"\"\n    if data is not None and not isinstance(data, Mapping):\n        # We prefer to separate `content=<bytes|str|byte iterator|bytes aiterator>`\n        # for raw request content, and `data=<form data>` for url encoded or\n        # multipart form content.\n        #\n        # However for compat with requests, we *do* still support\n        # `data=<bytes...>` usages. We deal with that case here, treating it\n        # as if `content=<...>` had been supplied instead.\n        message = \"Use 'content=<...>' to upload raw bytes/text content.\"\n        warnings.warn(message, DeprecationWarning, stacklevel=2)\n        return encode_content(data)\n\n    if content is not None:\n        return encode_content(content)\n    elif files:\n        return encode_multipart_data(data or {}, files, boundary)\n    elif data:\n        return encode_urlencoded_data(data)\n    elif json is not None:\n        return encode_json(json)\n\n    return {}, ByteStream(b\"\")\n\n\ndef encode_response(\n    content: ResponseContent | None = None,\n    text: str | None = None,\n    html: str | None = None,\n    json: Any | None = None,\n) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]:\n    \"\"\"\n    Handles encoding the given `content`, returning a two-tuple of\n    (<headers>, <stream>).\n    \"\"\"\n    if content is not None:\n        return encode_content(content)\n    elif text is not None:\n        return encode_text(text)\n    elif html is not None:\n        return encode_html(html)\n    elif json is not None:\n        return encode_json(json)\n\n    return {}, ByteStream(b\"\")\n"
  },
  {
    "path": "httpx/_decoders.py",
    "content": "\"\"\"\nHandlers for Content-Encoding.\n\nSee: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding\n\"\"\"\n\nfrom __future__ import annotations\n\nimport codecs\nimport io\nimport typing\nimport zlib\n\nfrom ._exceptions import DecodingError\n\n# Brotli support is optional\ntry:\n    # The C bindings in `brotli` are recommended for CPython.\n    import brotli\nexcept ImportError:  # pragma: no cover\n    try:\n        # The CFFI bindings in `brotlicffi` are recommended for PyPy\n        # and other environments.\n        import brotlicffi as brotli\n    except ImportError:\n        brotli = None\n\n\n# Zstandard support is optional\ntry:\n    import zstandard\nexcept ImportError:  # pragma: no cover\n    zstandard = None  # type: ignore\n\n\nclass ContentDecoder:\n    def decode(self, data: bytes) -> bytes:\n        raise NotImplementedError()  # pragma: no cover\n\n    def flush(self) -> bytes:\n        raise NotImplementedError()  # pragma: no cover\n\n\nclass IdentityDecoder(ContentDecoder):\n    \"\"\"\n    Handle unencoded data.\n    \"\"\"\n\n    def decode(self, data: bytes) -> bytes:\n        return data\n\n    def flush(self) -> bytes:\n        return b\"\"\n\n\nclass DeflateDecoder(ContentDecoder):\n    \"\"\"\n    Handle 'deflate' decoding.\n\n    See: https://stackoverflow.com/questions/1838699\n    \"\"\"\n\n    def __init__(self) -> None:\n        self.first_attempt = True\n        self.decompressor = zlib.decompressobj()\n\n    def decode(self, data: bytes) -> bytes:\n        was_first_attempt = self.first_attempt\n        self.first_attempt = False\n        try:\n            return self.decompressor.decompress(data)\n        except zlib.error as exc:\n            if was_first_attempt:\n                self.decompressor = zlib.decompressobj(-zlib.MAX_WBITS)\n                return self.decode(data)\n            raise DecodingError(str(exc)) from exc\n\n    def flush(self) -> bytes:\n        try:\n            return self.decompressor.flush()\n        except zlib.error as exc:  # pragma: no cover\n            raise DecodingError(str(exc)) from exc\n\n\nclass GZipDecoder(ContentDecoder):\n    \"\"\"\n    Handle 'gzip' decoding.\n\n    See: https://stackoverflow.com/questions/1838699\n    \"\"\"\n\n    def __init__(self) -> None:\n        self.decompressor = zlib.decompressobj(zlib.MAX_WBITS | 16)\n\n    def decode(self, data: bytes) -> bytes:\n        try:\n            return self.decompressor.decompress(data)\n        except zlib.error as exc:\n            raise DecodingError(str(exc)) from exc\n\n    def flush(self) -> bytes:\n        try:\n            return self.decompressor.flush()\n        except zlib.error as exc:  # pragma: no cover\n            raise DecodingError(str(exc)) from exc\n\n\nclass BrotliDecoder(ContentDecoder):\n    \"\"\"\n    Handle 'brotli' decoding.\n\n    Requires `pip install brotlipy`. See: https://brotlipy.readthedocs.io/\n        or   `pip install brotli`. See https://github.com/google/brotli\n    Supports both 'brotlipy' and 'Brotli' packages since they share an import\n    name. The top branches are for 'brotlipy' and bottom branches for 'Brotli'\n    \"\"\"\n\n    def __init__(self) -> None:\n        if brotli is None:  # pragma: no cover\n            raise ImportError(\n                \"Using 'BrotliDecoder', but neither of the 'brotlicffi' or 'brotli' \"\n                \"packages have been installed. \"\n                \"Make sure to install httpx using `pip install httpx[brotli]`.\"\n            ) from None\n\n        self.decompressor = brotli.Decompressor()\n        self.seen_data = False\n        self._decompress: typing.Callable[[bytes], bytes]\n        if hasattr(self.decompressor, \"decompress\"):\n            # The 'brotlicffi' package.\n            self._decompress = self.decompressor.decompress  # pragma: no cover\n        else:\n            # The 'brotli' package.\n            self._decompress = self.decompressor.process  # pragma: no cover\n\n    def decode(self, data: bytes) -> bytes:\n        if not data:\n            return b\"\"\n        self.seen_data = True\n        try:\n            return self._decompress(data)\n        except brotli.error as exc:\n            raise DecodingError(str(exc)) from exc\n\n    def flush(self) -> bytes:\n        if not self.seen_data:\n            return b\"\"\n        try:\n            if hasattr(self.decompressor, \"finish\"):\n                # Only available in the 'brotlicffi' package.\n\n                # As the decompressor decompresses eagerly, this\n                # will never actually emit any data. However, it will potentially throw\n                # errors if a truncated or damaged data stream has been used.\n                self.decompressor.finish()  # pragma: no cover\n            return b\"\"\n        except brotli.error as exc:  # pragma: no cover\n            raise DecodingError(str(exc)) from exc\n\n\nclass ZStandardDecoder(ContentDecoder):\n    \"\"\"\n    Handle 'zstd' RFC 8878 decoding.\n\n    Requires `pip install zstandard`.\n    Can be installed as a dependency of httpx using `pip install httpx[zstd]`.\n    \"\"\"\n\n    # inspired by the ZstdDecoder implementation in urllib3\n    def __init__(self) -> None:\n        if zstandard is None:  # pragma: no cover\n            raise ImportError(\n                \"Using 'ZStandardDecoder', ...\"\n                \"Make sure to install httpx using `pip install httpx[zstd]`.\"\n            ) from None\n\n        self.decompressor = zstandard.ZstdDecompressor().decompressobj()\n        self.seen_data = False\n\n    def decode(self, data: bytes) -> bytes:\n        assert zstandard is not None\n        self.seen_data = True\n        output = io.BytesIO()\n        try:\n            output.write(self.decompressor.decompress(data))\n            while self.decompressor.eof and self.decompressor.unused_data:\n                unused_data = self.decompressor.unused_data\n                self.decompressor = zstandard.ZstdDecompressor().decompressobj()\n                output.write(self.decompressor.decompress(unused_data))\n        except zstandard.ZstdError as exc:\n            raise DecodingError(str(exc)) from exc\n        return output.getvalue()\n\n    def flush(self) -> bytes:\n        if not self.seen_data:\n            return b\"\"\n        ret = self.decompressor.flush()  # note: this is a no-op\n        if not self.decompressor.eof:\n            raise DecodingError(\"Zstandard data is incomplete\")  # pragma: no cover\n        return bytes(ret)\n\n\nclass MultiDecoder(ContentDecoder):\n    \"\"\"\n    Handle the case where multiple encodings have been applied.\n    \"\"\"\n\n    def __init__(self, children: typing.Sequence[ContentDecoder]) -> None:\n        \"\"\"\n        'children' should be a sequence of decoders in the order in which\n        each was applied.\n        \"\"\"\n        # Note that we reverse the order for decoding.\n        self.children = list(reversed(children))\n\n    def decode(self, data: bytes) -> bytes:\n        for child in self.children:\n            data = child.decode(data)\n        return data\n\n    def flush(self) -> bytes:\n        data = b\"\"\n        for child in self.children:\n            data = child.decode(data) + child.flush()\n        return data\n\n\nclass ByteChunker:\n    \"\"\"\n    Handles returning byte content in fixed-size chunks.\n    \"\"\"\n\n    def __init__(self, chunk_size: int | None = None) -> None:\n        self._buffer = io.BytesIO()\n        self._chunk_size = chunk_size\n\n    def decode(self, content: bytes) -> list[bytes]:\n        if self._chunk_size is None:\n            return [content] if content else []\n\n        self._buffer.write(content)\n        if self._buffer.tell() >= self._chunk_size:\n            value = self._buffer.getvalue()\n            chunks = [\n                value[i : i + self._chunk_size]\n                for i in range(0, len(value), self._chunk_size)\n            ]\n            if len(chunks[-1]) == self._chunk_size:\n                self._buffer.seek(0)\n                self._buffer.truncate()\n                return chunks\n            else:\n                self._buffer.seek(0)\n                self._buffer.write(chunks[-1])\n                self._buffer.truncate()\n                return chunks[:-1]\n        else:\n            return []\n\n    def flush(self) -> list[bytes]:\n        value = self._buffer.getvalue()\n        self._buffer.seek(0)\n        self._buffer.truncate()\n        return [value] if value else []\n\n\nclass TextChunker:\n    \"\"\"\n    Handles returning text content in fixed-size chunks.\n    \"\"\"\n\n    def __init__(self, chunk_size: int | None = None) -> None:\n        self._buffer = io.StringIO()\n        self._chunk_size = chunk_size\n\n    def decode(self, content: str) -> list[str]:\n        if self._chunk_size is None:\n            return [content] if content else []\n\n        self._buffer.write(content)\n        if self._buffer.tell() >= self._chunk_size:\n            value = self._buffer.getvalue()\n            chunks = [\n                value[i : i + self._chunk_size]\n                for i in range(0, len(value), self._chunk_size)\n            ]\n            if len(chunks[-1]) == self._chunk_size:\n                self._buffer.seek(0)\n                self._buffer.truncate()\n                return chunks\n            else:\n                self._buffer.seek(0)\n                self._buffer.write(chunks[-1])\n                self._buffer.truncate()\n                return chunks[:-1]\n        else:\n            return []\n\n    def flush(self) -> list[str]:\n        value = self._buffer.getvalue()\n        self._buffer.seek(0)\n        self._buffer.truncate()\n        return [value] if value else []\n\n\nclass TextDecoder:\n    \"\"\"\n    Handles incrementally decoding bytes into text\n    \"\"\"\n\n    def __init__(self, encoding: str = \"utf-8\") -> None:\n        self.decoder = codecs.getincrementaldecoder(encoding)(errors=\"replace\")\n\n    def decode(self, data: bytes) -> str:\n        return self.decoder.decode(data)\n\n    def flush(self) -> str:\n        return self.decoder.decode(b\"\", True)\n\n\nclass LineDecoder:\n    \"\"\"\n    Handles incrementally reading lines from text.\n\n    Has the same behaviour as the stdllib splitlines,\n    but handling the input iteratively.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self.buffer: list[str] = []\n        self.trailing_cr: bool = False\n\n    def decode(self, text: str) -> list[str]:\n        # See https://docs.python.org/3/library/stdtypes.html#str.splitlines\n        NEWLINE_CHARS = \"\\n\\r\\x0b\\x0c\\x1c\\x1d\\x1e\\x85\\u2028\\u2029\"\n\n        # We always push a trailing `\\r` into the next decode iteration.\n        if self.trailing_cr:\n            text = \"\\r\" + text\n            self.trailing_cr = False\n        if text.endswith(\"\\r\"):\n            self.trailing_cr = True\n            text = text[:-1]\n\n        if not text:\n            # NOTE: the edge case input of empty text doesn't occur in practice,\n            # because other httpx internals filter out this value\n            return []  # pragma: no cover\n\n        trailing_newline = text[-1] in NEWLINE_CHARS\n        lines = text.splitlines()\n\n        if len(lines) == 1 and not trailing_newline:\n            # No new lines, buffer the input and continue.\n            self.buffer.append(lines[0])\n            return []\n\n        if self.buffer:\n            # Include any existing buffer in the first portion of the\n            # splitlines result.\n            lines = [\"\".join(self.buffer) + lines[0]] + lines[1:]\n            self.buffer = []\n\n        if not trailing_newline:\n            # If the last segment of splitlines is not newline terminated,\n            # then drop it from our output and start a new buffer.\n            self.buffer = [lines.pop()]\n\n        return lines\n\n    def flush(self) -> list[str]:\n        if not self.buffer and not self.trailing_cr:\n            return []\n\n        lines = [\"\".join(self.buffer)]\n        self.buffer = []\n        self.trailing_cr = False\n        return lines\n\n\nSUPPORTED_DECODERS = {\n    \"identity\": IdentityDecoder,\n    \"gzip\": GZipDecoder,\n    \"deflate\": DeflateDecoder,\n    \"br\": BrotliDecoder,\n    \"zstd\": ZStandardDecoder,\n}\n\n\nif brotli is None:\n    SUPPORTED_DECODERS.pop(\"br\")  # pragma: no cover\nif zstandard is None:\n    SUPPORTED_DECODERS.pop(\"zstd\")  # pragma: no cover\n"
  },
  {
    "path": "httpx/_exceptions.py",
    "content": "\"\"\"\nOur exception hierarchy:\n\n* HTTPError\n  x RequestError\n    + TransportError\n      - TimeoutException\n        · ConnectTimeout\n        · ReadTimeout\n        · WriteTimeout\n        · PoolTimeout\n      - NetworkError\n        · ConnectError\n        · ReadError\n        · WriteError\n        · CloseError\n      - ProtocolError\n        · LocalProtocolError\n        · RemoteProtocolError\n      - ProxyError\n      - UnsupportedProtocol\n    + DecodingError\n    + TooManyRedirects\n  x HTTPStatusError\n* InvalidURL\n* CookieConflict\n* StreamError\n  x StreamConsumed\n  x StreamClosed\n  x ResponseNotRead\n  x RequestNotRead\n\"\"\"\n\nfrom __future__ import annotations\n\nimport contextlib\nimport typing\n\nif typing.TYPE_CHECKING:\n    from ._models import Request, Response  # pragma: no cover\n\n__all__ = [\n    \"CloseError\",\n    \"ConnectError\",\n    \"ConnectTimeout\",\n    \"CookieConflict\",\n    \"DecodingError\",\n    \"HTTPError\",\n    \"HTTPStatusError\",\n    \"InvalidURL\",\n    \"LocalProtocolError\",\n    \"NetworkError\",\n    \"PoolTimeout\",\n    \"ProtocolError\",\n    \"ProxyError\",\n    \"ReadError\",\n    \"ReadTimeout\",\n    \"RemoteProtocolError\",\n    \"RequestError\",\n    \"RequestNotRead\",\n    \"ResponseNotRead\",\n    \"StreamClosed\",\n    \"StreamConsumed\",\n    \"StreamError\",\n    \"TimeoutException\",\n    \"TooManyRedirects\",\n    \"TransportError\",\n    \"UnsupportedProtocol\",\n    \"WriteError\",\n    \"WriteTimeout\",\n]\n\n\nclass HTTPError(Exception):\n    \"\"\"\n    Base class for `RequestError` and `HTTPStatusError`.\n\n    Useful for `try...except` blocks when issuing a request,\n    and then calling `.raise_for_status()`.\n\n    For example:\n\n    ```\n    try:\n        response = httpx.get(\"https://www.example.com\")\n        response.raise_for_status()\n    except httpx.HTTPError as exc:\n        print(f\"HTTP Exception for {exc.request.url} - {exc}\")\n    ```\n    \"\"\"\n\n    def __init__(self, message: str) -> None:\n        super().__init__(message)\n        self._request: Request | None = None\n\n    @property\n    def request(self) -> Request:\n        if self._request is None:\n            raise RuntimeError(\"The .request property has not been set.\")\n        return self._request\n\n    @request.setter\n    def request(self, request: Request) -> None:\n        self._request = request\n\n\nclass RequestError(HTTPError):\n    \"\"\"\n    Base class for all exceptions that may occur when issuing a `.request()`.\n    \"\"\"\n\n    def __init__(self, message: str, *, request: Request | None = None) -> None:\n        super().__init__(message)\n        # At the point an exception is raised we won't typically have a request\n        # instance to associate it with.\n        #\n        # The 'request_context' context manager is used within the Client and\n        # Response methods in order to ensure that any raised exceptions\n        # have a `.request` property set on them.\n        self._request = request\n\n\nclass TransportError(RequestError):\n    \"\"\"\n    Base class for all exceptions that occur at the level of the Transport API.\n    \"\"\"\n\n\n# Timeout exceptions...\n\n\nclass TimeoutException(TransportError):\n    \"\"\"\n    The base class for timeout errors.\n\n    An operation has timed out.\n    \"\"\"\n\n\nclass ConnectTimeout(TimeoutException):\n    \"\"\"\n    Timed out while connecting to the host.\n    \"\"\"\n\n\nclass ReadTimeout(TimeoutException):\n    \"\"\"\n    Timed out while receiving data from the host.\n    \"\"\"\n\n\nclass WriteTimeout(TimeoutException):\n    \"\"\"\n    Timed out while sending data to the host.\n    \"\"\"\n\n\nclass PoolTimeout(TimeoutException):\n    \"\"\"\n    Timed out waiting to acquire a connection from the pool.\n    \"\"\"\n\n\n# Core networking exceptions...\n\n\nclass NetworkError(TransportError):\n    \"\"\"\n    The base class for network-related errors.\n\n    An error occurred while interacting with the network.\n    \"\"\"\n\n\nclass ReadError(NetworkError):\n    \"\"\"\n    Failed to receive data from the network.\n    \"\"\"\n\n\nclass WriteError(NetworkError):\n    \"\"\"\n    Failed to send data through the network.\n    \"\"\"\n\n\nclass ConnectError(NetworkError):\n    \"\"\"\n    Failed to establish a connection.\n    \"\"\"\n\n\nclass CloseError(NetworkError):\n    \"\"\"\n    Failed to close a connection.\n    \"\"\"\n\n\n# Other transport exceptions...\n\n\nclass ProxyError(TransportError):\n    \"\"\"\n    An error occurred while establishing a proxy connection.\n    \"\"\"\n\n\nclass UnsupportedProtocol(TransportError):\n    \"\"\"\n    Attempted to make a request to an unsupported protocol.\n\n    For example issuing a request to `ftp://www.example.com`.\n    \"\"\"\n\n\nclass ProtocolError(TransportError):\n    \"\"\"\n    The protocol was violated.\n    \"\"\"\n\n\nclass LocalProtocolError(ProtocolError):\n    \"\"\"\n    A protocol was violated by the client.\n\n    For example if the user instantiated a `Request` instance explicitly,\n    failed to include the mandatory `Host:` header, and then issued it directly\n    using `client.send()`.\n    \"\"\"\n\n\nclass RemoteProtocolError(ProtocolError):\n    \"\"\"\n    The protocol was violated by the server.\n\n    For example, returning malformed HTTP.\n    \"\"\"\n\n\n# Other request exceptions...\n\n\nclass DecodingError(RequestError):\n    \"\"\"\n    Decoding of the response failed, due to a malformed encoding.\n    \"\"\"\n\n\nclass TooManyRedirects(RequestError):\n    \"\"\"\n    Too many redirects.\n    \"\"\"\n\n\n# Client errors\n\n\nclass HTTPStatusError(HTTPError):\n    \"\"\"\n    The response had an error HTTP status of 4xx or 5xx.\n\n    May be raised when calling `response.raise_for_status()`\n    \"\"\"\n\n    def __init__(self, message: str, *, request: Request, response: Response) -> None:\n        super().__init__(message)\n        self.request = request\n        self.response = response\n\n\nclass InvalidURL(Exception):\n    \"\"\"\n    URL is improperly formed or cannot be parsed.\n    \"\"\"\n\n    def __init__(self, message: str) -> None:\n        super().__init__(message)\n\n\nclass CookieConflict(Exception):\n    \"\"\"\n    Attempted to lookup a cookie by name, but multiple cookies existed.\n\n    Can occur when calling `response.cookies.get(...)`.\n    \"\"\"\n\n    def __init__(self, message: str) -> None:\n        super().__init__(message)\n\n\n# Stream exceptions...\n\n# These may occur as the result of a programming error, by accessing\n# the request/response stream in an invalid manner.\n\n\nclass StreamError(RuntimeError):\n    \"\"\"\n    The base class for stream exceptions.\n\n    The developer made an error in accessing the request stream in\n    an invalid way.\n    \"\"\"\n\n    def __init__(self, message: str) -> None:\n        super().__init__(message)\n\n\nclass StreamConsumed(StreamError):\n    \"\"\"\n    Attempted to read or stream content, but the content has already\n    been streamed.\n    \"\"\"\n\n    def __init__(self) -> None:\n        message = (\n            \"Attempted to read or stream some content, but the content has \"\n            \"already been streamed. For requests, this could be due to passing \"\n            \"a generator as request content, and then receiving a redirect \"\n            \"response or a secondary request as part of an authentication flow.\"\n            \"For responses, this could be due to attempting to stream the response \"\n            \"content more than once.\"\n        )\n        super().__init__(message)\n\n\nclass StreamClosed(StreamError):\n    \"\"\"\n    Attempted to read or stream response content, but the request has been\n    closed.\n    \"\"\"\n\n    def __init__(self) -> None:\n        message = \"Attempted to read or stream content, but the stream has been closed.\"\n        super().__init__(message)\n\n\nclass ResponseNotRead(StreamError):\n    \"\"\"\n    Attempted to access streaming response content, without having called `read()`.\n    \"\"\"\n\n    def __init__(self) -> None:\n        message = (\n            \"Attempted to access streaming response content,\"\n            \" without having called `read()`.\"\n        )\n        super().__init__(message)\n\n\nclass RequestNotRead(StreamError):\n    \"\"\"\n    Attempted to access streaming request content, without having called `read()`.\n    \"\"\"\n\n    def __init__(self) -> None:\n        message = (\n            \"Attempted to access streaming request content,\"\n            \" without having called `read()`.\"\n        )\n        super().__init__(message)\n\n\n@contextlib.contextmanager\ndef request_context(\n    request: Request | None = None,\n) -> typing.Iterator[None]:\n    \"\"\"\n    A context manager that can be used to attach the given request context\n    to any `RequestError` exceptions that are raised within the block.\n    \"\"\"\n    try:\n        yield\n    except RequestError as exc:\n        if request is not None:\n            exc.request = request\n        raise exc\n"
  },
  {
    "path": "httpx/_main.py",
    "content": "from __future__ import annotations\n\nimport functools\nimport json\nimport sys\nimport typing\n\nimport click\nimport pygments.lexers\nimport pygments.util\nimport rich.console\nimport rich.markup\nimport rich.progress\nimport rich.syntax\nimport rich.table\n\nfrom ._client import Client\nfrom ._exceptions import RequestError\nfrom ._models import Response\nfrom ._status_codes import codes\n\nif typing.TYPE_CHECKING:\n    import httpcore  # pragma: no cover\n\n\ndef print_help() -> None:\n    console = rich.console.Console()\n\n    console.print(\"[bold]HTTPX :butterfly:\", justify=\"center\")\n    console.print()\n    console.print(\"A next generation HTTP client.\", justify=\"center\")\n    console.print()\n    console.print(\n        \"Usage: [bold]httpx[/bold] [cyan]<URL> [OPTIONS][/cyan] \", justify=\"left\"\n    )\n    console.print()\n\n    table = rich.table.Table.grid(padding=1, pad_edge=True)\n    table.add_column(\"Parameter\", no_wrap=True, justify=\"left\", style=\"bold\")\n    table.add_column(\"Description\")\n    table.add_row(\n        \"-m, --method [cyan]METHOD\",\n        \"Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD.\\n\"\n        \"[Default: GET, or POST if a request body is included]\",\n    )\n    table.add_row(\n        \"-p, --params [cyan]<NAME VALUE> ...\",\n        \"Query parameters to include in the request URL.\",\n    )\n    table.add_row(\n        \"-c, --content [cyan]TEXT\", \"Byte content to include in the request body.\"\n    )\n    table.add_row(\n        \"-d, --data [cyan]<NAME VALUE> ...\", \"Form data to include in the request body.\"\n    )\n    table.add_row(\n        \"-f, --files [cyan]<NAME FILENAME> ...\",\n        \"Form files to include in the request body.\",\n    )\n    table.add_row(\"-j, --json [cyan]TEXT\", \"JSON data to include in the request body.\")\n    table.add_row(\n        \"-h, --headers [cyan]<NAME VALUE> ...\",\n        \"Include additional HTTP headers in the request.\",\n    )\n    table.add_row(\n        \"--cookies [cyan]<NAME VALUE> ...\", \"Cookies to include in the request.\"\n    )\n    table.add_row(\n        \"--auth [cyan]<USER PASS>\",\n        \"Username and password to include in the request. Specify '-' for the password\"\n        \" to use a password prompt. Note that using --verbose/-v will expose\"\n        \" the Authorization header, including the password encoding\"\n        \" in a trivially reversible format.\",\n    )\n\n    table.add_row(\n        \"--proxy [cyan]URL\",\n        \"Send the request via a proxy. Should be the URL giving the proxy address.\",\n    )\n\n    table.add_row(\n        \"--timeout [cyan]FLOAT\",\n        \"Timeout value to use for network operations, such as establishing the\"\n        \" connection, reading some data, etc... [Default: 5.0]\",\n    )\n\n    table.add_row(\"--follow-redirects\", \"Automatically follow redirects.\")\n    table.add_row(\"--no-verify\", \"Disable SSL verification.\")\n    table.add_row(\n        \"--http2\", \"Send the request using HTTP/2, if the remote server supports it.\"\n    )\n\n    table.add_row(\n        \"--download [cyan]FILE\",\n        \"Save the response content as a file, rather than displaying it.\",\n    )\n\n    table.add_row(\"-v, --verbose\", \"Verbose output. Show request as well as response.\")\n    table.add_row(\"--help\", \"Show this message and exit.\")\n    console.print(table)\n\n\ndef get_lexer_for_response(response: Response) -> str:\n    content_type = response.headers.get(\"Content-Type\")\n    if content_type is not None:\n        mime_type, _, _ = content_type.partition(\";\")\n        try:\n            return typing.cast(\n                str, pygments.lexers.get_lexer_for_mimetype(mime_type.strip()).name\n            )\n        except pygments.util.ClassNotFound:  # pragma: no cover\n            pass\n    return \"\"  # pragma: no cover\n\n\ndef format_request_headers(request: httpcore.Request, http2: bool = False) -> str:\n    version = \"HTTP/2\" if http2 else \"HTTP/1.1\"\n    headers = [\n        (name.lower() if http2 else name, value) for name, value in request.headers\n    ]\n    method = request.method.decode(\"ascii\")\n    target = request.url.target.decode(\"ascii\")\n    lines = [f\"{method} {target} {version}\"] + [\n        f\"{name.decode('ascii')}: {value.decode('ascii')}\" for name, value in headers\n    ]\n    return \"\\n\".join(lines)\n\n\ndef format_response_headers(\n    http_version: bytes,\n    status: int,\n    reason_phrase: bytes | None,\n    headers: list[tuple[bytes, bytes]],\n) -> str:\n    version = http_version.decode(\"ascii\")\n    reason = (\n        codes.get_reason_phrase(status)\n        if reason_phrase is None\n        else reason_phrase.decode(\"ascii\")\n    )\n    lines = [f\"{version} {status} {reason}\"] + [\n        f\"{name.decode('ascii')}: {value.decode('ascii')}\" for name, value in headers\n    ]\n    return \"\\n\".join(lines)\n\n\ndef print_request_headers(request: httpcore.Request, http2: bool = False) -> None:\n    console = rich.console.Console()\n    http_text = format_request_headers(request, http2=http2)\n    syntax = rich.syntax.Syntax(http_text, \"http\", theme=\"ansi_dark\", word_wrap=True)\n    console.print(syntax)\n    syntax = rich.syntax.Syntax(\"\", \"http\", theme=\"ansi_dark\", word_wrap=True)\n    console.print(syntax)\n\n\ndef print_response_headers(\n    http_version: bytes,\n    status: int,\n    reason_phrase: bytes | None,\n    headers: list[tuple[bytes, bytes]],\n) -> None:\n    console = rich.console.Console()\n    http_text = format_response_headers(http_version, status, reason_phrase, headers)\n    syntax = rich.syntax.Syntax(http_text, \"http\", theme=\"ansi_dark\", word_wrap=True)\n    console.print(syntax)\n    syntax = rich.syntax.Syntax(\"\", \"http\", theme=\"ansi_dark\", word_wrap=True)\n    console.print(syntax)\n\n\ndef print_response(response: Response) -> None:\n    console = rich.console.Console()\n    lexer_name = get_lexer_for_response(response)\n    if lexer_name:\n        if lexer_name.lower() == \"json\":\n            try:\n                data = response.json()\n                text = json.dumps(data, indent=4)\n            except ValueError:  # pragma: no cover\n                text = response.text\n        else:\n            text = response.text\n\n        syntax = rich.syntax.Syntax(text, lexer_name, theme=\"ansi_dark\", word_wrap=True)\n        console.print(syntax)\n    else:\n        console.print(f\"<{len(response.content)} bytes of binary data>\")\n\n\n_PCTRTT = typing.Tuple[typing.Tuple[str, str], ...]\n_PCTRTTT = typing.Tuple[_PCTRTT, ...]\n_PeerCertRetDictType = typing.Dict[str, typing.Union[str, _PCTRTTT, _PCTRTT]]\n\n\ndef format_certificate(cert: _PeerCertRetDictType) -> str:  # pragma: no cover\n    lines = []\n    for key, value in cert.items():\n        if isinstance(value, (list, tuple)):\n            lines.append(f\"*   {key}:\")\n            for item in value:\n                if key in (\"subject\", \"issuer\"):\n                    for sub_item in item:\n                        lines.append(f\"*     {sub_item[0]}: {sub_item[1]!r}\")\n                elif isinstance(item, tuple) and len(item) == 2:\n                    lines.append(f\"*     {item[0]}: {item[1]!r}\")\n                else:\n                    lines.append(f\"*     {item!r}\")\n        else:\n            lines.append(f\"*   {key}: {value!r}\")\n    return \"\\n\".join(lines)\n\n\ndef trace(\n    name: str, info: typing.Mapping[str, typing.Any], verbose: bool = False\n) -> None:\n    console = rich.console.Console()\n    if name == \"connection.connect_tcp.started\" and verbose:\n        host = info[\"host\"]\n        console.print(f\"* Connecting to {host!r}\")\n    elif name == \"connection.connect_tcp.complete\" and verbose:\n        stream = info[\"return_value\"]\n        server_addr = stream.get_extra_info(\"server_addr\")\n        console.print(f\"* Connected to {server_addr[0]!r} on port {server_addr[1]}\")\n    elif name == \"connection.start_tls.complete\" and verbose:  # pragma: no cover\n        stream = info[\"return_value\"]\n        ssl_object = stream.get_extra_info(\"ssl_object\")\n        version = ssl_object.version()\n        cipher = ssl_object.cipher()\n        server_cert = ssl_object.getpeercert()\n        alpn = ssl_object.selected_alpn_protocol()\n        console.print(f\"* SSL established using {version!r} / {cipher[0]!r}\")\n        console.print(f\"* Selected ALPN protocol: {alpn!r}\")\n        if server_cert:\n            console.print(\"* Server certificate:\")\n            console.print(format_certificate(server_cert))\n    elif name == \"http11.send_request_headers.started\" and verbose:\n        request = info[\"request\"]\n        print_request_headers(request, http2=False)\n    elif name == \"http2.send_request_headers.started\" and verbose:  # pragma: no cover\n        request = info[\"request\"]\n        print_request_headers(request, http2=True)\n    elif name == \"http11.receive_response_headers.complete\":\n        http_version, status, reason_phrase, headers = info[\"return_value\"]\n        print_response_headers(http_version, status, reason_phrase, headers)\n    elif name == \"http2.receive_response_headers.complete\":  # pragma: no cover\n        status, headers = info[\"return_value\"]\n        http_version = b\"HTTP/2\"\n        reason_phrase = None\n        print_response_headers(http_version, status, reason_phrase, headers)\n\n\ndef download_response(response: Response, download: typing.BinaryIO) -> None:\n    console = rich.console.Console()\n    console.print()\n    content_length = response.headers.get(\"Content-Length\")\n    with rich.progress.Progress(\n        \"[progress.description]{task.description}\",\n        \"[progress.percentage]{task.percentage:>3.0f}%\",\n        rich.progress.BarColumn(bar_width=None),\n        rich.progress.DownloadColumn(),\n        rich.progress.TransferSpeedColumn(),\n    ) as progress:\n        description = f\"Downloading [bold]{rich.markup.escape(download.name)}\"\n        download_task = progress.add_task(\n            description,\n            total=int(content_length or 0),\n            start=content_length is not None,\n        )\n        for chunk in response.iter_bytes():\n            download.write(chunk)\n            progress.update(download_task, completed=response.num_bytes_downloaded)\n\n\ndef validate_json(\n    ctx: click.Context,\n    param: click.Option | click.Parameter,\n    value: typing.Any,\n) -> typing.Any:\n    if value is None:\n        return None\n\n    try:\n        return json.loads(value)\n    except json.JSONDecodeError:  # pragma: no cover\n        raise click.BadParameter(\"Not valid JSON\")\n\n\ndef validate_auth(\n    ctx: click.Context,\n    param: click.Option | click.Parameter,\n    value: typing.Any,\n) -> typing.Any:\n    if value == (None, None):\n        return None\n\n    username, password = value\n    if password == \"-\":  # pragma: no cover\n        password = click.prompt(\"Password\", hide_input=True)\n    return (username, password)\n\n\ndef handle_help(\n    ctx: click.Context,\n    param: click.Option | click.Parameter,\n    value: typing.Any,\n) -> None:\n    if not value or ctx.resilient_parsing:\n        return\n\n    print_help()\n    ctx.exit()\n\n\n@click.command(add_help_option=False)\n@click.argument(\"url\", type=str)\n@click.option(\n    \"--method\",\n    \"-m\",\n    \"method\",\n    type=str,\n    help=(\n        \"Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD. \"\n        \"[Default: GET, or POST if a request body is included]\"\n    ),\n)\n@click.option(\n    \"--params\",\n    \"-p\",\n    \"params\",\n    type=(str, str),\n    multiple=True,\n    help=\"Query parameters to include in the request URL.\",\n)\n@click.option(\n    \"--content\",\n    \"-c\",\n    \"content\",\n    type=str,\n    help=\"Byte content to include in the request body.\",\n)\n@click.option(\n    \"--data\",\n    \"-d\",\n    \"data\",\n    type=(str, str),\n    multiple=True,\n    help=\"Form data to include in the request body.\",\n)\n@click.option(\n    \"--files\",\n    \"-f\",\n    \"files\",\n    type=(str, click.File(mode=\"rb\")),\n    multiple=True,\n    help=\"Form files to include in the request body.\",\n)\n@click.option(\n    \"--json\",\n    \"-j\",\n    \"json\",\n    type=str,\n    callback=validate_json,\n    help=\"JSON data to include in the request body.\",\n)\n@click.option(\n    \"--headers\",\n    \"-h\",\n    \"headers\",\n    type=(str, str),\n    multiple=True,\n    help=\"Include additional HTTP headers in the request.\",\n)\n@click.option(\n    \"--cookies\",\n    \"cookies\",\n    type=(str, str),\n    multiple=True,\n    help=\"Cookies to include in the request.\",\n)\n@click.option(\n    \"--auth\",\n    \"auth\",\n    type=(str, str),\n    default=(None, None),\n    callback=validate_auth,\n    help=(\n        \"Username and password to include in the request. \"\n        \"Specify '-' for the password to use a password prompt. \"\n        \"Note that using --verbose/-v will expose the Authorization header, \"\n        \"including the password encoding in a trivially reversible format.\"\n    ),\n)\n@click.option(\n    \"--proxy\",\n    \"proxy\",\n    type=str,\n    default=None,\n    help=\"Send the request via a proxy. Should be the URL giving the proxy address.\",\n)\n@click.option(\n    \"--timeout\",\n    \"timeout\",\n    type=float,\n    default=5.0,\n    help=(\n        \"Timeout value to use for network operations, such as establishing the \"\n        \"connection, reading some data, etc... [Default: 5.0]\"\n    ),\n)\n@click.option(\n    \"--follow-redirects\",\n    \"follow_redirects\",\n    is_flag=True,\n    default=False,\n    help=\"Automatically follow redirects.\",\n)\n@click.option(\n    \"--no-verify\",\n    \"verify\",\n    is_flag=True,\n    default=True,\n    help=\"Disable SSL verification.\",\n)\n@click.option(\n    \"--http2\",\n    \"http2\",\n    type=bool,\n    is_flag=True,\n    default=False,\n    help=\"Send the request using HTTP/2, if the remote server supports it.\",\n)\n@click.option(\n    \"--download\",\n    type=click.File(\"wb\"),\n    help=\"Save the response content as a file, rather than displaying it.\",\n)\n@click.option(\n    \"--verbose\",\n    \"-v\",\n    type=bool,\n    is_flag=True,\n    default=False,\n    help=\"Verbose. Show request as well as response.\",\n)\n@click.option(\n    \"--help\",\n    is_flag=True,\n    is_eager=True,\n    expose_value=False,\n    callback=handle_help,\n    help=\"Show this message and exit.\",\n)\ndef main(\n    url: str,\n    method: str,\n    params: list[tuple[str, str]],\n    content: str,\n    data: list[tuple[str, str]],\n    files: list[tuple[str, click.File]],\n    json: str,\n    headers: list[tuple[str, str]],\n    cookies: list[tuple[str, str]],\n    auth: tuple[str, str] | None,\n    proxy: str,\n    timeout: float,\n    follow_redirects: bool,\n    verify: bool,\n    http2: bool,\n    download: typing.BinaryIO | None,\n    verbose: bool,\n) -> None:\n    \"\"\"\n    An HTTP command line client.\n    Sends a request and displays the response.\n    \"\"\"\n    if not method:\n        method = \"POST\" if content or data or files or json else \"GET\"\n\n    try:\n        with Client(proxy=proxy, timeout=timeout, http2=http2, verify=verify) as client:\n            with client.stream(\n                method,\n                url,\n                params=list(params),\n                content=content,\n                data=dict(data),\n                files=files,  # type: ignore\n                json=json,\n                headers=headers,\n                cookies=dict(cookies),\n                auth=auth,\n                follow_redirects=follow_redirects,\n                extensions={\"trace\": functools.partial(trace, verbose=verbose)},\n            ) as response:\n                if download is not None:\n                    download_response(response, download)\n                else:\n                    response.read()\n                    if response.content:\n                        print_response(response)\n\n    except RequestError as exc:\n        console = rich.console.Console()\n        console.print(f\"[red]{type(exc).__name__}[/red]: {exc}\")\n        sys.exit(1)\n\n    sys.exit(0 if response.is_success else 1)\n"
  },
  {
    "path": "httpx/_models.py",
    "content": "from __future__ import annotations\n\nimport codecs\nimport datetime\nimport email.message\nimport json as jsonlib\nimport re\nimport typing\nimport urllib.request\nfrom collections.abc import Mapping\nfrom http.cookiejar import Cookie, CookieJar\n\nfrom ._content import ByteStream, UnattachedStream, encode_request, encode_response\nfrom ._decoders import (\n    SUPPORTED_DECODERS,\n    ByteChunker,\n    ContentDecoder,\n    IdentityDecoder,\n    LineDecoder,\n    MultiDecoder,\n    TextChunker,\n    TextDecoder,\n)\nfrom ._exceptions import (\n    CookieConflict,\n    HTTPStatusError,\n    RequestNotRead,\n    ResponseNotRead,\n    StreamClosed,\n    StreamConsumed,\n    request_context,\n)\nfrom ._multipart import get_multipart_boundary_from_content_type\nfrom ._status_codes import codes\nfrom ._types import (\n    AsyncByteStream,\n    CookieTypes,\n    HeaderTypes,\n    QueryParamTypes,\n    RequestContent,\n    RequestData,\n    RequestExtensions,\n    RequestFiles,\n    ResponseContent,\n    ResponseExtensions,\n    SyncByteStream,\n)\nfrom ._urls import URL\nfrom ._utils import to_bytes_or_str, to_str\n\n__all__ = [\"Cookies\", \"Headers\", \"Request\", \"Response\"]\n\nSENSITIVE_HEADERS = {\"authorization\", \"proxy-authorization\"}\n\n\ndef _is_known_encoding(encoding: str) -> bool:\n    \"\"\"\n    Return `True` if `encoding` is a known codec.\n    \"\"\"\n    try:\n        codecs.lookup(encoding)\n    except LookupError:\n        return False\n    return True\n\n\ndef _normalize_header_key(key: str | bytes, encoding: str | None = None) -> bytes:\n    \"\"\"\n    Coerce str/bytes into a strictly byte-wise HTTP header key.\n    \"\"\"\n    return key if isinstance(key, bytes) else key.encode(encoding or \"ascii\")\n\n\ndef _normalize_header_value(value: str | bytes, encoding: str | None = None) -> bytes:\n    \"\"\"\n    Coerce str/bytes into a strictly byte-wise HTTP header value.\n    \"\"\"\n    if isinstance(value, bytes):\n        return value\n    if not isinstance(value, str):\n        raise TypeError(f\"Header value must be str or bytes, not {type(value)}\")\n    return value.encode(encoding or \"ascii\")\n\n\ndef _parse_content_type_charset(content_type: str) -> str | None:\n    # We used to use `cgi.parse_header()` here, but `cgi` became a dead battery.\n    # See: https://peps.python.org/pep-0594/#cgi\n    msg = email.message.Message()\n    msg[\"content-type\"] = content_type\n    return msg.get_content_charset(failobj=None)\n\n\ndef _parse_header_links(value: str) -> list[dict[str, str]]:\n    \"\"\"\n    Returns a list of parsed link headers, for more info see:\n    https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link\n    The generic syntax of those is:\n    Link: < uri-reference >; param1=value1; param2=\"value2\"\n    So for instance:\n    Link; '<http:/.../front.jpeg>; type=\"image/jpeg\",<http://.../back.jpeg>;'\n    would return\n        [\n            {\"url\": \"http:/.../front.jpeg\", \"type\": \"image/jpeg\"},\n            {\"url\": \"http://.../back.jpeg\"},\n        ]\n    :param value: HTTP Link entity-header field\n    :return: list of parsed link headers\n    \"\"\"\n    links: list[dict[str, str]] = []\n    replace_chars = \" '\\\"\"\n    value = value.strip(replace_chars)\n    if not value:\n        return links\n    for val in re.split(\", *<\", value):\n        try:\n            url, params = val.split(\";\", 1)\n        except ValueError:\n            url, params = val, \"\"\n        link = {\"url\": url.strip(\"<> '\\\"\")}\n        for param in params.split(\";\"):\n            try:\n                key, value = param.split(\"=\")\n            except ValueError:\n                break\n            link[key.strip(replace_chars)] = value.strip(replace_chars)\n        links.append(link)\n    return links\n\n\ndef _obfuscate_sensitive_headers(\n    items: typing.Iterable[tuple[typing.AnyStr, typing.AnyStr]],\n) -> typing.Iterator[tuple[typing.AnyStr, typing.AnyStr]]:\n    for k, v in items:\n        if to_str(k.lower()) in SENSITIVE_HEADERS:\n            v = to_bytes_or_str(\"[secure]\", match_type_of=v)\n        yield k, v\n\n\nclass Headers(typing.MutableMapping[str, str]):\n    \"\"\"\n    HTTP headers, as a case-insensitive multi-dict.\n    \"\"\"\n\n    def __init__(\n        self,\n        headers: HeaderTypes | None = None,\n        encoding: str | None = None,\n    ) -> None:\n        self._list = []  # type: typing.List[typing.Tuple[bytes, bytes, bytes]]\n\n        if isinstance(headers, Headers):\n            self._list = list(headers._list)\n        elif isinstance(headers, Mapping):\n            for k, v in headers.items():\n                bytes_key = _normalize_header_key(k, encoding)\n                bytes_value = _normalize_header_value(v, encoding)\n                self._list.append((bytes_key, bytes_key.lower(), bytes_value))\n        elif headers is not None:\n            for k, v in headers:\n                bytes_key = _normalize_header_key(k, encoding)\n                bytes_value = _normalize_header_value(v, encoding)\n                self._list.append((bytes_key, bytes_key.lower(), bytes_value))\n\n        self._encoding = encoding\n\n    @property\n    def encoding(self) -> str:\n        \"\"\"\n        Header encoding is mandated as ascii, but we allow fallbacks to utf-8\n        or iso-8859-1.\n        \"\"\"\n        if self._encoding is None:\n            for encoding in [\"ascii\", \"utf-8\"]:\n                for key, value in self.raw:\n                    try:\n                        key.decode(encoding)\n                        value.decode(encoding)\n                    except UnicodeDecodeError:\n                        break\n                else:\n                    # The else block runs if 'break' did not occur, meaning\n                    # all values fitted the encoding.\n                    self._encoding = encoding\n                    break\n            else:\n                # The ISO-8859-1 encoding covers all 256 code points in a byte,\n                # so will never raise decode errors.\n                self._encoding = \"iso-8859-1\"\n        return self._encoding\n\n    @encoding.setter\n    def encoding(self, value: str) -> None:\n        self._encoding = value\n\n    @property\n    def raw(self) -> list[tuple[bytes, bytes]]:\n        \"\"\"\n        Returns a list of the raw header items, as byte pairs.\n        \"\"\"\n        return [(raw_key, value) for raw_key, _, value in self._list]\n\n    def keys(self) -> typing.KeysView[str]:\n        return {key.decode(self.encoding): None for _, key, value in self._list}.keys()\n\n    def values(self) -> typing.ValuesView[str]:\n        values_dict: dict[str, str] = {}\n        for _, key, value in self._list:\n            str_key = key.decode(self.encoding)\n            str_value = value.decode(self.encoding)\n            if str_key in values_dict:\n                values_dict[str_key] += f\", {str_value}\"\n            else:\n                values_dict[str_key] = str_value\n        return values_dict.values()\n\n    def items(self) -> typing.ItemsView[str, str]:\n        \"\"\"\n        Return `(key, value)` items of headers. Concatenate headers\n        into a single comma separated value when a key occurs multiple times.\n        \"\"\"\n        values_dict: dict[str, str] = {}\n        for _, key, value in self._list:\n            str_key = key.decode(self.encoding)\n            str_value = value.decode(self.encoding)\n            if str_key in values_dict:\n                values_dict[str_key] += f\", {str_value}\"\n            else:\n                values_dict[str_key] = str_value\n        return values_dict.items()\n\n    def multi_items(self) -> list[tuple[str, str]]:\n        \"\"\"\n        Return a list of `(key, value)` pairs of headers. Allow multiple\n        occurrences of the same key without concatenating into a single\n        comma separated value.\n        \"\"\"\n        return [\n            (key.decode(self.encoding), value.decode(self.encoding))\n            for _, key, value in self._list\n        ]\n\n    def get(self, key: str, default: typing.Any = None) -> typing.Any:\n        \"\"\"\n        Return a header value. If multiple occurrences of the header occur\n        then concatenate them together with commas.\n        \"\"\"\n        try:\n            return self[key]\n        except KeyError:\n            return default\n\n    def get_list(self, key: str, split_commas: bool = False) -> list[str]:\n        \"\"\"\n        Return a list of all header values for a given key.\n        If `split_commas=True` is passed, then any comma separated header\n        values are split into multiple return strings.\n        \"\"\"\n        get_header_key = key.lower().encode(self.encoding)\n\n        values = [\n            item_value.decode(self.encoding)\n            for _, item_key, item_value in self._list\n            if item_key.lower() == get_header_key\n        ]\n\n        if not split_commas:\n            return values\n\n        split_values = []\n        for value in values:\n            split_values.extend([item.strip() for item in value.split(\",\")])\n        return split_values\n\n    def update(self, headers: HeaderTypes | None = None) -> None:  # type: ignore\n        headers = Headers(headers)\n        for key in headers.keys():\n            if key in self:\n                self.pop(key)\n        self._list.extend(headers._list)\n\n    def copy(self) -> Headers:\n        return Headers(self, encoding=self.encoding)\n\n    def __getitem__(self, key: str) -> str:\n        \"\"\"\n        Return a single header value.\n\n        If there are multiple headers with the same key, then we concatenate\n        them with commas. See: https://tools.ietf.org/html/rfc7230#section-3.2.2\n        \"\"\"\n        normalized_key = key.lower().encode(self.encoding)\n\n        items = [\n            header_value.decode(self.encoding)\n            for _, header_key, header_value in self._list\n            if header_key == normalized_key\n        ]\n\n        if items:\n            return \", \".join(items)\n\n        raise KeyError(key)\n\n    def __setitem__(self, key: str, value: str) -> None:\n        \"\"\"\n        Set the header `key` to `value`, removing any duplicate entries.\n        Retains insertion order.\n        \"\"\"\n        set_key = key.encode(self._encoding or \"utf-8\")\n        set_value = value.encode(self._encoding or \"utf-8\")\n        lookup_key = set_key.lower()\n\n        found_indexes = [\n            idx\n            for idx, (_, item_key, _) in enumerate(self._list)\n            if item_key == lookup_key\n        ]\n\n        for idx in reversed(found_indexes[1:]):\n            del self._list[idx]\n\n        if found_indexes:\n            idx = found_indexes[0]\n            self._list[idx] = (set_key, lookup_key, set_value)\n        else:\n            self._list.append((set_key, lookup_key, set_value))\n\n    def __delitem__(self, key: str) -> None:\n        \"\"\"\n        Remove the header `key`.\n        \"\"\"\n        del_key = key.lower().encode(self.encoding)\n\n        pop_indexes = [\n            idx\n            for idx, (_, item_key, _) in enumerate(self._list)\n            if item_key.lower() == del_key\n        ]\n\n        if not pop_indexes:\n            raise KeyError(key)\n\n        for idx in reversed(pop_indexes):\n            del self._list[idx]\n\n    def __contains__(self, key: typing.Any) -> bool:\n        header_key = key.lower().encode(self.encoding)\n        return header_key in [key for _, key, _ in self._list]\n\n    def __iter__(self) -> typing.Iterator[typing.Any]:\n        return iter(self.keys())\n\n    def __len__(self) -> int:\n        return len(self._list)\n\n    def __eq__(self, other: typing.Any) -> bool:\n        try:\n            other_headers = Headers(other)\n        except ValueError:\n            return False\n\n        self_list = [(key, value) for _, key, value in self._list]\n        other_list = [(key, value) for _, key, value in other_headers._list]\n        return sorted(self_list) == sorted(other_list)\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n\n        encoding_str = \"\"\n        if self.encoding != \"ascii\":\n            encoding_str = f\", encoding={self.encoding!r}\"\n\n        as_list = list(_obfuscate_sensitive_headers(self.multi_items()))\n        as_dict = dict(as_list)\n\n        no_duplicate_keys = len(as_dict) == len(as_list)\n        if no_duplicate_keys:\n            return f\"{class_name}({as_dict!r}{encoding_str})\"\n        return f\"{class_name}({as_list!r}{encoding_str})\"\n\n\nclass Request:\n    def __init__(\n        self,\n        method: str,\n        url: URL | str,\n        *,\n        params: QueryParamTypes | None = None,\n        headers: HeaderTypes | None = None,\n        cookies: CookieTypes | None = None,\n        content: RequestContent | None = None,\n        data: RequestData | None = None,\n        files: RequestFiles | None = None,\n        json: typing.Any | None = None,\n        stream: SyncByteStream | AsyncByteStream | None = None,\n        extensions: RequestExtensions | None = None,\n    ) -> None:\n        self.method = method.upper()\n        self.url = URL(url) if params is None else URL(url, params=params)\n        self.headers = Headers(headers)\n        self.extensions = {} if extensions is None else dict(extensions)\n\n        if cookies:\n            Cookies(cookies).set_cookie_header(self)\n\n        if stream is None:\n            content_type: str | None = self.headers.get(\"content-type\")\n            headers, stream = encode_request(\n                content=content,\n                data=data,\n                files=files,\n                json=json,\n                boundary=get_multipart_boundary_from_content_type(\n                    content_type=content_type.encode(self.headers.encoding)\n                    if content_type\n                    else None\n                ),\n            )\n            self._prepare(headers)\n            self.stream = stream\n            # Load the request body, except for streaming content.\n            if isinstance(stream, ByteStream):\n                self.read()\n        else:\n            # There's an important distinction between `Request(content=...)`,\n            # and `Request(stream=...)`.\n            #\n            # Using `content=...` implies automatically populated `Host` and content\n            # headers, of either `Content-Length: ...` or `Transfer-Encoding: chunked`.\n            #\n            # Using `stream=...` will not automatically include *any*\n            # auto-populated headers.\n            #\n            # As an end-user you don't really need `stream=...`. It's only\n            # useful when:\n            #\n            # * Preserving the request stream when copying requests, eg for redirects.\n            # * Creating request instances on the *server-side* of the transport API.\n            self.stream = stream\n\n    def _prepare(self, default_headers: dict[str, str]) -> None:\n        for key, value in default_headers.items():\n            # Ignore Transfer-Encoding if the Content-Length has been set explicitly.\n            if key.lower() == \"transfer-encoding\" and \"Content-Length\" in self.headers:\n                continue\n            self.headers.setdefault(key, value)\n\n        auto_headers: list[tuple[bytes, bytes]] = []\n\n        has_host = \"Host\" in self.headers\n        has_content_length = (\n            \"Content-Length\" in self.headers or \"Transfer-Encoding\" in self.headers\n        )\n\n        if not has_host and self.url.host:\n            auto_headers.append((b\"Host\", self.url.netloc))\n        if not has_content_length and self.method in (\"POST\", \"PUT\", \"PATCH\"):\n            auto_headers.append((b\"Content-Length\", b\"0\"))\n\n        self.headers = Headers(auto_headers + self.headers.raw)\n\n    @property\n    def content(self) -> bytes:\n        if not hasattr(self, \"_content\"):\n            raise RequestNotRead()\n        return self._content\n\n    def read(self) -> bytes:\n        \"\"\"\n        Read and return the request content.\n        \"\"\"\n        if not hasattr(self, \"_content\"):\n            assert isinstance(self.stream, typing.Iterable)\n            self._content = b\"\".join(self.stream)\n            if not isinstance(self.stream, ByteStream):\n                # If a streaming request has been read entirely into memory, then\n                # we can replace the stream with a raw bytes implementation,\n                # to ensure that any non-replayable streams can still be used.\n                self.stream = ByteStream(self._content)\n        return self._content\n\n    async def aread(self) -> bytes:\n        \"\"\"\n        Read and return the request content.\n        \"\"\"\n        if not hasattr(self, \"_content\"):\n            assert isinstance(self.stream, typing.AsyncIterable)\n            self._content = b\"\".join([part async for part in self.stream])\n            if not isinstance(self.stream, ByteStream):\n                # If a streaming request has been read entirely into memory, then\n                # we can replace the stream with a raw bytes implementation,\n                # to ensure that any non-replayable streams can still be used.\n                self.stream = ByteStream(self._content)\n        return self._content\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        url = str(self.url)\n        return f\"<{class_name}({self.method!r}, {url!r})>\"\n\n    def __getstate__(self) -> dict[str, typing.Any]:\n        return {\n            name: value\n            for name, value in self.__dict__.items()\n            if name not in [\"extensions\", \"stream\"]\n        }\n\n    def __setstate__(self, state: dict[str, typing.Any]) -> None:\n        for name, value in state.items():\n            setattr(self, name, value)\n        self.extensions = {}\n        self.stream = UnattachedStream()\n\n\nclass Response:\n    def __init__(\n        self,\n        status_code: int,\n        *,\n        headers: HeaderTypes | None = None,\n        content: ResponseContent | None = None,\n        text: str | None = None,\n        html: str | None = None,\n        json: typing.Any = None,\n        stream: SyncByteStream | AsyncByteStream | None = None,\n        request: Request | None = None,\n        extensions: ResponseExtensions | None = None,\n        history: list[Response] | None = None,\n        default_encoding: str | typing.Callable[[bytes], str] = \"utf-8\",\n    ) -> None:\n        self.status_code = status_code\n        self.headers = Headers(headers)\n\n        self._request: Request | None = request\n\n        # When follow_redirects=False and a redirect is received,\n        # the client will set `response.next_request`.\n        self.next_request: Request | None = None\n\n        self.extensions = {} if extensions is None else dict(extensions)\n        self.history = [] if history is None else list(history)\n\n        self.is_closed = False\n        self.is_stream_consumed = False\n\n        self.default_encoding = default_encoding\n\n        if stream is None:\n            headers, stream = encode_response(content, text, html, json)\n            self._prepare(headers)\n            self.stream = stream\n            if isinstance(stream, ByteStream):\n                # Load the response body, except for streaming content.\n                self.read()\n        else:\n            # There's an important distinction between `Response(content=...)`,\n            # and `Response(stream=...)`.\n            #\n            # Using `content=...` implies automatically populated content headers,\n            # of either `Content-Length: ...` or `Transfer-Encoding: chunked`.\n            #\n            # Using `stream=...` will not automatically include any content headers.\n            #\n            # As an end-user you don't really need `stream=...`. It's only\n            # useful when creating response instances having received a stream\n            # from the transport API.\n            self.stream = stream\n\n        self._num_bytes_downloaded = 0\n\n    def _prepare(self, default_headers: dict[str, str]) -> None:\n        for key, value in default_headers.items():\n            # Ignore Transfer-Encoding if the Content-Length has been set explicitly.\n            if key.lower() == \"transfer-encoding\" and \"content-length\" in self.headers:\n                continue\n            self.headers.setdefault(key, value)\n\n    @property\n    def elapsed(self) -> datetime.timedelta:\n        \"\"\"\n        Returns the time taken for the complete request/response\n        cycle to complete.\n        \"\"\"\n        if not hasattr(self, \"_elapsed\"):\n            raise RuntimeError(\n                \"'.elapsed' may only be accessed after the response \"\n                \"has been read or closed.\"\n            )\n        return self._elapsed\n\n    @elapsed.setter\n    def elapsed(self, elapsed: datetime.timedelta) -> None:\n        self._elapsed = elapsed\n\n    @property\n    def request(self) -> Request:\n        \"\"\"\n        Returns the request instance associated to the current response.\n        \"\"\"\n        if self._request is None:\n            raise RuntimeError(\n                \"The request instance has not been set on this response.\"\n            )\n        return self._request\n\n    @request.setter\n    def request(self, value: Request) -> None:\n        self._request = value\n\n    @property\n    def http_version(self) -> str:\n        try:\n            http_version: bytes = self.extensions[\"http_version\"]\n        except KeyError:\n            return \"HTTP/1.1\"\n        else:\n            return http_version.decode(\"ascii\", errors=\"ignore\")\n\n    @property\n    def reason_phrase(self) -> str:\n        try:\n            reason_phrase: bytes = self.extensions[\"reason_phrase\"]\n        except KeyError:\n            return codes.get_reason_phrase(self.status_code)\n        else:\n            return reason_phrase.decode(\"ascii\", errors=\"ignore\")\n\n    @property\n    def url(self) -> URL:\n        \"\"\"\n        Returns the URL for which the request was made.\n        \"\"\"\n        return self.request.url\n\n    @property\n    def content(self) -> bytes:\n        if not hasattr(self, \"_content\"):\n            raise ResponseNotRead()\n        return self._content\n\n    @property\n    def text(self) -> str:\n        if not hasattr(self, \"_text\"):\n            content = self.content\n            if not content:\n                self._text = \"\"\n            else:\n                decoder = TextDecoder(encoding=self.encoding or \"utf-8\")\n                self._text = \"\".join([decoder.decode(self.content), decoder.flush()])\n        return self._text\n\n    @property\n    def encoding(self) -> str | None:\n        \"\"\"\n        Return an encoding to use for decoding the byte content into text.\n        The priority for determining this is given by...\n\n        * `.encoding = <>` has been set explicitly.\n        * The encoding as specified by the charset parameter in the Content-Type header.\n        * The encoding as determined by `default_encoding`, which may either be\n          a string like \"utf-8\" indicating the encoding to use, or may be a callable\n          which enables charset autodetection.\n        \"\"\"\n        if not hasattr(self, \"_encoding\"):\n            encoding = self.charset_encoding\n            if encoding is None or not _is_known_encoding(encoding):\n                if isinstance(self.default_encoding, str):\n                    encoding = self.default_encoding\n                elif hasattr(self, \"_content\"):\n                    encoding = self.default_encoding(self._content)\n            self._encoding = encoding or \"utf-8\"\n        return self._encoding\n\n    @encoding.setter\n    def encoding(self, value: str) -> None:\n        \"\"\"\n        Set the encoding to use for decoding the byte content into text.\n\n        If the `text` attribute has been accessed, attempting to set the\n        encoding will throw a ValueError.\n        \"\"\"\n        if hasattr(self, \"_text\"):\n            raise ValueError(\n                \"Setting encoding after `text` has been accessed is not allowed.\"\n            )\n        self._encoding = value\n\n    @property\n    def charset_encoding(self) -> str | None:\n        \"\"\"\n        Return the encoding, as specified by the Content-Type header.\n        \"\"\"\n        content_type = self.headers.get(\"Content-Type\")\n        if content_type is None:\n            return None\n\n        return _parse_content_type_charset(content_type)\n\n    def _get_content_decoder(self) -> ContentDecoder:\n        \"\"\"\n        Returns a decoder instance which can be used to decode the raw byte\n        content, depending on the Content-Encoding used in the response.\n        \"\"\"\n        if not hasattr(self, \"_decoder\"):\n            decoders: list[ContentDecoder] = []\n            values = self.headers.get_list(\"content-encoding\", split_commas=True)\n            for value in values:\n                value = value.strip().lower()\n                try:\n                    decoder_cls = SUPPORTED_DECODERS[value]\n                    decoders.append(decoder_cls())\n                except KeyError:\n                    continue\n\n            if len(decoders) == 1:\n                self._decoder = decoders[0]\n            elif len(decoders) > 1:\n                self._decoder = MultiDecoder(children=decoders)\n            else:\n                self._decoder = IdentityDecoder()\n\n        return self._decoder\n\n    @property\n    def is_informational(self) -> bool:\n        \"\"\"\n        A property which is `True` for 1xx status codes, `False` otherwise.\n        \"\"\"\n        return codes.is_informational(self.status_code)\n\n    @property\n    def is_success(self) -> bool:\n        \"\"\"\n        A property which is `True` for 2xx status codes, `False` otherwise.\n        \"\"\"\n        return codes.is_success(self.status_code)\n\n    @property\n    def is_redirect(self) -> bool:\n        \"\"\"\n        A property which is `True` for 3xx status codes, `False` otherwise.\n\n        Note that not all responses with a 3xx status code indicate a URL redirect.\n\n        Use `response.has_redirect_location` to determine responses with a properly\n        formed URL redirection.\n        \"\"\"\n        return codes.is_redirect(self.status_code)\n\n    @property\n    def is_client_error(self) -> bool:\n        \"\"\"\n        A property which is `True` for 4xx status codes, `False` otherwise.\n        \"\"\"\n        return codes.is_client_error(self.status_code)\n\n    @property\n    def is_server_error(self) -> bool:\n        \"\"\"\n        A property which is `True` for 5xx status codes, `False` otherwise.\n        \"\"\"\n        return codes.is_server_error(self.status_code)\n\n    @property\n    def is_error(self) -> bool:\n        \"\"\"\n        A property which is `True` for 4xx and 5xx status codes, `False` otherwise.\n        \"\"\"\n        return codes.is_error(self.status_code)\n\n    @property\n    def has_redirect_location(self) -> bool:\n        \"\"\"\n        Returns True for 3xx responses with a properly formed URL redirection,\n        `False` otherwise.\n        \"\"\"\n        return (\n            self.status_code\n            in (\n                # 301 (Cacheable redirect. Method may change to GET.)\n                codes.MOVED_PERMANENTLY,\n                # 302 (Uncacheable redirect. Method may change to GET.)\n                codes.FOUND,\n                # 303 (Client should make a GET or HEAD request.)\n                codes.SEE_OTHER,\n                # 307 (Equiv. 302, but retain method)\n                codes.TEMPORARY_REDIRECT,\n                # 308 (Equiv. 301, but retain method)\n                codes.PERMANENT_REDIRECT,\n            )\n            and \"Location\" in self.headers\n        )\n\n    def raise_for_status(self) -> Response:\n        \"\"\"\n        Raise the `HTTPStatusError` if one occurred.\n        \"\"\"\n        request = self._request\n        if request is None:\n            raise RuntimeError(\n                \"Cannot call `raise_for_status` as the request \"\n                \"instance has not been set on this response.\"\n            )\n\n        if self.is_success:\n            return self\n\n        if self.has_redirect_location:\n            message = (\n                \"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\\n\"\n                \"Redirect location: '{0.headers[location]}'\\n\"\n                \"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}\"\n            )\n        else:\n            message = (\n                \"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\\n\"\n                \"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}\"\n            )\n\n        status_class = self.status_code // 100\n        error_types = {\n            1: \"Informational response\",\n            3: \"Redirect response\",\n            4: \"Client error\",\n            5: \"Server error\",\n        }\n        error_type = error_types.get(status_class, \"Invalid status code\")\n        message = message.format(self, error_type=error_type)\n        raise HTTPStatusError(message, request=request, response=self)\n\n    def json(self, **kwargs: typing.Any) -> typing.Any:\n        return jsonlib.loads(self.content, **kwargs)\n\n    @property\n    def cookies(self) -> Cookies:\n        if not hasattr(self, \"_cookies\"):\n            self._cookies = Cookies()\n            self._cookies.extract_cookies(self)\n        return self._cookies\n\n    @property\n    def links(self) -> dict[str | None, dict[str, str]]:\n        \"\"\"\n        Returns the parsed header links of the response, if any\n        \"\"\"\n        header = self.headers.get(\"link\")\n        if header is None:\n            return {}\n\n        return {\n            (link.get(\"rel\") or link.get(\"url\")): link\n            for link in _parse_header_links(header)\n        }\n\n    @property\n    def num_bytes_downloaded(self) -> int:\n        return self._num_bytes_downloaded\n\n    def __repr__(self) -> str:\n        return f\"<Response [{self.status_code} {self.reason_phrase}]>\"\n\n    def __getstate__(self) -> dict[str, typing.Any]:\n        return {\n            name: value\n            for name, value in self.__dict__.items()\n            if name not in [\"extensions\", \"stream\", \"is_closed\", \"_decoder\"]\n        }\n\n    def __setstate__(self, state: dict[str, typing.Any]) -> None:\n        for name, value in state.items():\n            setattr(self, name, value)\n        self.is_closed = True\n        self.extensions = {}\n        self.stream = UnattachedStream()\n\n    def read(self) -> bytes:\n        \"\"\"\n        Read and return the response content.\n        \"\"\"\n        if not hasattr(self, \"_content\"):\n            self._content = b\"\".join(self.iter_bytes())\n        return self._content\n\n    def iter_bytes(self, chunk_size: int | None = None) -> typing.Iterator[bytes]:\n        \"\"\"\n        A byte-iterator over the decoded response content.\n        This allows us to handle gzip, deflate, brotli, and zstd encoded responses.\n        \"\"\"\n        if hasattr(self, \"_content\"):\n            chunk_size = len(self._content) if chunk_size is None else chunk_size\n            for i in range(0, len(self._content), max(chunk_size, 1)):\n                yield self._content[i : i + chunk_size]\n        else:\n            decoder = self._get_content_decoder()\n            chunker = ByteChunker(chunk_size=chunk_size)\n            with request_context(request=self._request):\n                for raw_bytes in self.iter_raw():\n                    decoded = decoder.decode(raw_bytes)\n                    for chunk in chunker.decode(decoded):\n                        yield chunk\n                decoded = decoder.flush()\n                for chunk in chunker.decode(decoded):\n                    yield chunk  # pragma: no cover\n                for chunk in chunker.flush():\n                    yield chunk\n\n    def iter_text(self, chunk_size: int | None = None) -> typing.Iterator[str]:\n        \"\"\"\n        A str-iterator over the decoded response content\n        that handles both gzip, deflate, etc but also detects the content's\n        string encoding.\n        \"\"\"\n        decoder = TextDecoder(encoding=self.encoding or \"utf-8\")\n        chunker = TextChunker(chunk_size=chunk_size)\n        with request_context(request=self._request):\n            for byte_content in self.iter_bytes():\n                text_content = decoder.decode(byte_content)\n                for chunk in chunker.decode(text_content):\n                    yield chunk\n            text_content = decoder.flush()\n            for chunk in chunker.decode(text_content):\n                yield chunk  # pragma: no cover\n            for chunk in chunker.flush():\n                yield chunk\n\n    def iter_lines(self) -> typing.Iterator[str]:\n        decoder = LineDecoder()\n        with request_context(request=self._request):\n            for text in self.iter_text():\n                for line in decoder.decode(text):\n                    yield line\n            for line in decoder.flush():\n                yield line\n\n    def iter_raw(self, chunk_size: int | None = None) -> typing.Iterator[bytes]:\n        \"\"\"\n        A byte-iterator over the raw response content.\n        \"\"\"\n        if self.is_stream_consumed:\n            raise StreamConsumed()\n        if self.is_closed:\n            raise StreamClosed()\n        if not isinstance(self.stream, SyncByteStream):\n            raise RuntimeError(\"Attempted to call a sync iterator on an async stream.\")\n\n        self.is_stream_consumed = True\n        self._num_bytes_downloaded = 0\n        chunker = ByteChunker(chunk_size=chunk_size)\n\n        with request_context(request=self._request):\n            for raw_stream_bytes in self.stream:\n                self._num_bytes_downloaded += len(raw_stream_bytes)\n                for chunk in chunker.decode(raw_stream_bytes):\n                    yield chunk\n\n        for chunk in chunker.flush():\n            yield chunk\n\n        self.close()\n\n    def close(self) -> None:\n        \"\"\"\n        Close the response and release the connection.\n        Automatically called if the response body is read to completion.\n        \"\"\"\n        if not isinstance(self.stream, SyncByteStream):\n            raise RuntimeError(\"Attempted to call a sync close on an async stream.\")\n\n        if not self.is_closed:\n            self.is_closed = True\n            with request_context(request=self._request):\n                self.stream.close()\n\n    async def aread(self) -> bytes:\n        \"\"\"\n        Read and return the response content.\n        \"\"\"\n        if not hasattr(self, \"_content\"):\n            self._content = b\"\".join([part async for part in self.aiter_bytes()])\n        return self._content\n\n    async def aiter_bytes(\n        self, chunk_size: int | None = None\n    ) -> typing.AsyncIterator[bytes]:\n        \"\"\"\n        A byte-iterator over the decoded response content.\n        This allows us to handle gzip, deflate, brotli, and zstd encoded responses.\n        \"\"\"\n        if hasattr(self, \"_content\"):\n            chunk_size = len(self._content) if chunk_size is None else chunk_size\n            for i in range(0, len(self._content), max(chunk_size, 1)):\n                yield self._content[i : i + chunk_size]\n        else:\n            decoder = self._get_content_decoder()\n            chunker = ByteChunker(chunk_size=chunk_size)\n            with request_context(request=self._request):\n                async for raw_bytes in self.aiter_raw():\n                    decoded = decoder.decode(raw_bytes)\n                    for chunk in chunker.decode(decoded):\n                        yield chunk\n                decoded = decoder.flush()\n                for chunk in chunker.decode(decoded):\n                    yield chunk  # pragma: no cover\n                for chunk in chunker.flush():\n                    yield chunk\n\n    async def aiter_text(\n        self, chunk_size: int | None = None\n    ) -> typing.AsyncIterator[str]:\n        \"\"\"\n        A str-iterator over the decoded response content\n        that handles both gzip, deflate, etc but also detects the content's\n        string encoding.\n        \"\"\"\n        decoder = TextDecoder(encoding=self.encoding or \"utf-8\")\n        chunker = TextChunker(chunk_size=chunk_size)\n        with request_context(request=self._request):\n            async for byte_content in self.aiter_bytes():\n                text_content = decoder.decode(byte_content)\n                for chunk in chunker.decode(text_content):\n                    yield chunk\n            text_content = decoder.flush()\n            for chunk in chunker.decode(text_content):\n                yield chunk  # pragma: no cover\n            for chunk in chunker.flush():\n                yield chunk\n\n    async def aiter_lines(self) -> typing.AsyncIterator[str]:\n        decoder = LineDecoder()\n        with request_context(request=self._request):\n            async for text in self.aiter_text():\n                for line in decoder.decode(text):\n                    yield line\n            for line in decoder.flush():\n                yield line\n\n    async def aiter_raw(\n        self, chunk_size: int | None = None\n    ) -> typing.AsyncIterator[bytes]:\n        \"\"\"\n        A byte-iterator over the raw response content.\n        \"\"\"\n        if self.is_stream_consumed:\n            raise StreamConsumed()\n        if self.is_closed:\n            raise StreamClosed()\n        if not isinstance(self.stream, AsyncByteStream):\n            raise RuntimeError(\"Attempted to call an async iterator on a sync stream.\")\n\n        self.is_stream_consumed = True\n        self._num_bytes_downloaded = 0\n        chunker = ByteChunker(chunk_size=chunk_size)\n\n        with request_context(request=self._request):\n            async for raw_stream_bytes in self.stream:\n                self._num_bytes_downloaded += len(raw_stream_bytes)\n                for chunk in chunker.decode(raw_stream_bytes):\n                    yield chunk\n\n        for chunk in chunker.flush():\n            yield chunk\n\n        await self.aclose()\n\n    async def aclose(self) -> None:\n        \"\"\"\n        Close the response and release the connection.\n        Automatically called if the response body is read to completion.\n        \"\"\"\n        if not isinstance(self.stream, AsyncByteStream):\n            raise RuntimeError(\"Attempted to call an async close on a sync stream.\")\n\n        if not self.is_closed:\n            self.is_closed = True\n            with request_context(request=self._request):\n                await self.stream.aclose()\n\n\nclass Cookies(typing.MutableMapping[str, str]):\n    \"\"\"\n    HTTP Cookies, as a mutable mapping.\n    \"\"\"\n\n    def __init__(self, cookies: CookieTypes | None = None) -> None:\n        if cookies is None or isinstance(cookies, dict):\n            self.jar = CookieJar()\n            if isinstance(cookies, dict):\n                for key, value in cookies.items():\n                    self.set(key, value)\n        elif isinstance(cookies, list):\n            self.jar = CookieJar()\n            for key, value in cookies:\n                self.set(key, value)\n        elif isinstance(cookies, Cookies):\n            self.jar = CookieJar()\n            for cookie in cookies.jar:\n                self.jar.set_cookie(cookie)\n        else:\n            self.jar = cookies\n\n    def extract_cookies(self, response: Response) -> None:\n        \"\"\"\n        Loads any cookies based on the response `Set-Cookie` headers.\n        \"\"\"\n        urllib_response = self._CookieCompatResponse(response)\n        urllib_request = self._CookieCompatRequest(response.request)\n\n        self.jar.extract_cookies(urllib_response, urllib_request)  # type: ignore\n\n    def set_cookie_header(self, request: Request) -> None:\n        \"\"\"\n        Sets an appropriate 'Cookie:' HTTP header on the `Request`.\n        \"\"\"\n        urllib_request = self._CookieCompatRequest(request)\n        self.jar.add_cookie_header(urllib_request)\n\n    def set(self, name: str, value: str, domain: str = \"\", path: str = \"/\") -> None:\n        \"\"\"\n        Set a cookie value by name. May optionally include domain and path.\n        \"\"\"\n        kwargs = {\n            \"version\": 0,\n            \"name\": name,\n            \"value\": value,\n            \"port\": None,\n            \"port_specified\": False,\n            \"domain\": domain,\n            \"domain_specified\": bool(domain),\n            \"domain_initial_dot\": domain.startswith(\".\"),\n            \"path\": path,\n            \"path_specified\": bool(path),\n            \"secure\": False,\n            \"expires\": None,\n            \"discard\": True,\n            \"comment\": None,\n            \"comment_url\": None,\n            \"rest\": {\"HttpOnly\": None},\n            \"rfc2109\": False,\n        }\n        cookie = Cookie(**kwargs)  # type: ignore\n        self.jar.set_cookie(cookie)\n\n    def get(  # type: ignore\n        self,\n        name: str,\n        default: str | None = None,\n        domain: str | None = None,\n        path: str | None = None,\n    ) -> str | None:\n        \"\"\"\n        Get a cookie by name. May optionally include domain and path\n        in order to specify exactly which cookie to retrieve.\n        \"\"\"\n        value = None\n        for cookie in self.jar:\n            if cookie.name == name:\n                if domain is None or cookie.domain == domain:\n                    if path is None or cookie.path == path:\n                        if value is not None:\n                            message = f\"Multiple cookies exist with name={name}\"\n                            raise CookieConflict(message)\n                        value = cookie.value\n\n        if value is None:\n            return default\n        return value\n\n    def delete(\n        self,\n        name: str,\n        domain: str | None = None,\n        path: str | None = None,\n    ) -> None:\n        \"\"\"\n        Delete a cookie by name. May optionally include domain and path\n        in order to specify exactly which cookie to delete.\n        \"\"\"\n        if domain is not None and path is not None:\n            return self.jar.clear(domain, path, name)\n\n        remove = [\n            cookie\n            for cookie in self.jar\n            if cookie.name == name\n            and (domain is None or cookie.domain == domain)\n            and (path is None or cookie.path == path)\n        ]\n\n        for cookie in remove:\n            self.jar.clear(cookie.domain, cookie.path, cookie.name)\n\n    def clear(self, domain: str | None = None, path: str | None = None) -> None:\n        \"\"\"\n        Delete all cookies. Optionally include a domain and path in\n        order to only delete a subset of all the cookies.\n        \"\"\"\n        args = []\n        if domain is not None:\n            args.append(domain)\n        if path is not None:\n            assert domain is not None\n            args.append(path)\n        self.jar.clear(*args)\n\n    def update(self, cookies: CookieTypes | None = None) -> None:  # type: ignore\n        cookies = Cookies(cookies)\n        for cookie in cookies.jar:\n            self.jar.set_cookie(cookie)\n\n    def __setitem__(self, name: str, value: str) -> None:\n        return self.set(name, value)\n\n    def __getitem__(self, name: str) -> str:\n        value = self.get(name)\n        if value is None:\n            raise KeyError(name)\n        return value\n\n    def __delitem__(self, name: str) -> None:\n        return self.delete(name)\n\n    def __len__(self) -> int:\n        return len(self.jar)\n\n    def __iter__(self) -> typing.Iterator[str]:\n        return (cookie.name for cookie in self.jar)\n\n    def __bool__(self) -> bool:\n        for _ in self.jar:\n            return True\n        return False\n\n    def __repr__(self) -> str:\n        cookies_repr = \", \".join(\n            [\n                f\"<Cookie {cookie.name}={cookie.value} for {cookie.domain} />\"\n                for cookie in self.jar\n            ]\n        )\n\n        return f\"<Cookies[{cookies_repr}]>\"\n\n    class _CookieCompatRequest(urllib.request.Request):\n        \"\"\"\n        Wraps a `Request` instance up in a compatibility interface suitable\n        for use with `CookieJar` operations.\n        \"\"\"\n\n        def __init__(self, request: Request) -> None:\n            super().__init__(\n                url=str(request.url),\n                headers=dict(request.headers),\n                method=request.method,\n            )\n            self.request = request\n\n        def add_unredirected_header(self, key: str, value: str) -> None:\n            super().add_unredirected_header(key, value)\n            self.request.headers[key] = value\n\n    class _CookieCompatResponse:\n        \"\"\"\n        Wraps a `Request` instance up in a compatibility interface suitable\n        for use with `CookieJar` operations.\n        \"\"\"\n\n        def __init__(self, response: Response) -> None:\n            self.response = response\n\n        def info(self) -> email.message.Message:\n            info = email.message.Message()\n            for key, value in self.response.headers.multi_items():\n                # Note that setting `info[key]` here is an \"append\" operation,\n                # not a \"replace\" operation.\n                # https://docs.python.org/3/library/email.compat32-message.html#email.message.Message.__setitem__\n                info[key] = value\n            return info\n"
  },
  {
    "path": "httpx/_multipart.py",
    "content": "from __future__ import annotations\n\nimport io\nimport mimetypes\nimport os\nimport re\nimport typing\nfrom pathlib import Path\n\nfrom ._types import (\n    AsyncByteStream,\n    FileContent,\n    FileTypes,\n    RequestData,\n    RequestFiles,\n    SyncByteStream,\n)\nfrom ._utils import (\n    peek_filelike_length,\n    primitive_value_to_str,\n    to_bytes,\n)\n\n_HTML5_FORM_ENCODING_REPLACEMENTS = {'\"': \"%22\", \"\\\\\": \"\\\\\\\\\"}\n_HTML5_FORM_ENCODING_REPLACEMENTS.update(\n    {chr(c): \"%{:02X}\".format(c) for c in range(0x1F + 1) if c != 0x1B}\n)\n_HTML5_FORM_ENCODING_RE = re.compile(\n    r\"|\".join([re.escape(c) for c in _HTML5_FORM_ENCODING_REPLACEMENTS.keys()])\n)\n\n\ndef _format_form_param(name: str, value: str) -> bytes:\n    \"\"\"\n    Encode a name/value pair within a multipart form.\n    \"\"\"\n\n    def replacer(match: typing.Match[str]) -> str:\n        return _HTML5_FORM_ENCODING_REPLACEMENTS[match.group(0)]\n\n    value = _HTML5_FORM_ENCODING_RE.sub(replacer, value)\n    return f'{name}=\"{value}\"'.encode()\n\n\ndef _guess_content_type(filename: str | None) -> str | None:\n    \"\"\"\n    Guesses the mimetype based on a filename. Defaults to `application/octet-stream`.\n\n    Returns `None` if `filename` is `None` or empty.\n    \"\"\"\n    if filename:\n        return mimetypes.guess_type(filename)[0] or \"application/octet-stream\"\n    return None\n\n\ndef get_multipart_boundary_from_content_type(\n    content_type: bytes | None,\n) -> bytes | None:\n    if not content_type or not content_type.startswith(b\"multipart/form-data\"):\n        return None\n    # parse boundary according to\n    # https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1\n    if b\";\" in content_type:\n        for section in content_type.split(b\";\"):\n            if section.strip().lower().startswith(b\"boundary=\"):\n                return section.strip()[len(b\"boundary=\") :].strip(b'\"')\n    return None\n\n\nclass DataField:\n    \"\"\"\n    A single form field item, within a multipart form field.\n    \"\"\"\n\n    def __init__(self, name: str, value: str | bytes | int | float | None) -> None:\n        if not isinstance(name, str):\n            raise TypeError(\n                f\"Invalid type for name. Expected str, got {type(name)}: {name!r}\"\n            )\n        if value is not None and not isinstance(value, (str, bytes, int, float)):\n            raise TypeError(\n                \"Invalid type for value. Expected primitive type,\"\n                f\" got {type(value)}: {value!r}\"\n            )\n        self.name = name\n        self.value: str | bytes = (\n            value if isinstance(value, bytes) else primitive_value_to_str(value)\n        )\n\n    def render_headers(self) -> bytes:\n        if not hasattr(self, \"_headers\"):\n            name = _format_form_param(\"name\", self.name)\n            self._headers = b\"\".join(\n                [b\"Content-Disposition: form-data; \", name, b\"\\r\\n\\r\\n\"]\n            )\n\n        return self._headers\n\n    def render_data(self) -> bytes:\n        if not hasattr(self, \"_data\"):\n            self._data = to_bytes(self.value)\n\n        return self._data\n\n    def get_length(self) -> int:\n        headers = self.render_headers()\n        data = self.render_data()\n        return len(headers) + len(data)\n\n    def render(self) -> typing.Iterator[bytes]:\n        yield self.render_headers()\n        yield self.render_data()\n\n\nclass FileField:\n    \"\"\"\n    A single file field item, within a multipart form field.\n    \"\"\"\n\n    CHUNK_SIZE = 64 * 1024\n\n    def __init__(self, name: str, value: FileTypes) -> None:\n        self.name = name\n\n        fileobj: FileContent\n\n        headers: dict[str, str] = {}\n        content_type: str | None = None\n\n        # This large tuple based API largely mirror's requests' API\n        # It would be good to think of better APIs for this that we could\n        # include in httpx 2.0 since variable length tuples(especially of 4 elements)\n        # are quite unwieldly\n        if isinstance(value, tuple):\n            if len(value) == 2:\n                # neither the 3rd parameter (content_type) nor the 4th (headers)\n                # was included\n                filename, fileobj = value\n            elif len(value) == 3:\n                filename, fileobj, content_type = value\n            else:\n                # all 4 parameters included\n                filename, fileobj, content_type, headers = value  # type: ignore\n        else:\n            filename = Path(str(getattr(value, \"name\", \"upload\"))).name\n            fileobj = value\n\n        if content_type is None:\n            content_type = _guess_content_type(filename)\n\n        has_content_type_header = any(\"content-type\" in key.lower() for key in headers)\n        if content_type is not None and not has_content_type_header:\n            # note that unlike requests, we ignore the content_type provided in the 3rd\n            # tuple element if it is also included in the headers requests does\n            # the opposite (it overwrites the headerwith the 3rd tuple element)\n            headers[\"Content-Type\"] = content_type\n\n        if isinstance(fileobj, io.StringIO):\n            raise TypeError(\n                \"Multipart file uploads require 'io.BytesIO', not 'io.StringIO'.\"\n            )\n        if isinstance(fileobj, io.TextIOBase):\n            raise TypeError(\n                \"Multipart file uploads must be opened in binary mode, not text mode.\"\n            )\n\n        self.filename = filename\n        self.file = fileobj\n        self.headers = headers\n\n    def get_length(self) -> int | None:\n        headers = self.render_headers()\n\n        if isinstance(self.file, (str, bytes)):\n            return len(headers) + len(to_bytes(self.file))\n\n        file_length = peek_filelike_length(self.file)\n\n        # If we can't determine the filesize without reading it into memory,\n        # then return `None` here, to indicate an unknown file length.\n        if file_length is None:\n            return None\n\n        return len(headers) + file_length\n\n    def render_headers(self) -> bytes:\n        if not hasattr(self, \"_headers\"):\n            parts = [\n                b\"Content-Disposition: form-data; \",\n                _format_form_param(\"name\", self.name),\n            ]\n            if self.filename:\n                filename = _format_form_param(\"filename\", self.filename)\n                parts.extend([b\"; \", filename])\n            for header_name, header_value in self.headers.items():\n                key, val = f\"\\r\\n{header_name}: \".encode(), header_value.encode()\n                parts.extend([key, val])\n            parts.append(b\"\\r\\n\\r\\n\")\n            self._headers = b\"\".join(parts)\n\n        return self._headers\n\n    def render_data(self) -> typing.Iterator[bytes]:\n        if isinstance(self.file, (str, bytes)):\n            yield to_bytes(self.file)\n            return\n\n        if hasattr(self.file, \"seek\"):\n            try:\n                self.file.seek(0)\n            except io.UnsupportedOperation:\n                pass\n\n        chunk = self.file.read(self.CHUNK_SIZE)\n        while chunk:\n            yield to_bytes(chunk)\n            chunk = self.file.read(self.CHUNK_SIZE)\n\n    def render(self) -> typing.Iterator[bytes]:\n        yield self.render_headers()\n        yield from self.render_data()\n\n\nclass MultipartStream(SyncByteStream, AsyncByteStream):\n    \"\"\"\n    Request content as streaming multipart encoded form data.\n    \"\"\"\n\n    def __init__(\n        self,\n        data: RequestData,\n        files: RequestFiles,\n        boundary: bytes | None = None,\n    ) -> None:\n        if boundary is None:\n            boundary = os.urandom(16).hex().encode(\"ascii\")\n\n        self.boundary = boundary\n        self.content_type = \"multipart/form-data; boundary=%s\" % boundary.decode(\n            \"ascii\"\n        )\n        self.fields = list(self._iter_fields(data, files))\n\n    def _iter_fields(\n        self, data: RequestData, files: RequestFiles\n    ) -> typing.Iterator[FileField | DataField]:\n        for name, value in data.items():\n            if isinstance(value, (tuple, list)):\n                for item in value:\n                    yield DataField(name=name, value=item)\n            else:\n                yield DataField(name=name, value=value)\n\n        file_items = files.items() if isinstance(files, typing.Mapping) else files\n        for name, value in file_items:\n            yield FileField(name=name, value=value)\n\n    def iter_chunks(self) -> typing.Iterator[bytes]:\n        for field in self.fields:\n            yield b\"--%s\\r\\n\" % self.boundary\n            yield from field.render()\n            yield b\"\\r\\n\"\n        yield b\"--%s--\\r\\n\" % self.boundary\n\n    def get_content_length(self) -> int | None:\n        \"\"\"\n        Return the length of the multipart encoded content, or `None` if\n        any of the files have a length that cannot be determined upfront.\n        \"\"\"\n        boundary_length = len(self.boundary)\n        length = 0\n\n        for field in self.fields:\n            field_length = field.get_length()\n            if field_length is None:\n                return None\n\n            length += 2 + boundary_length + 2  # b\"--{boundary}\\r\\n\"\n            length += field_length\n            length += 2  # b\"\\r\\n\"\n\n        length += 2 + boundary_length + 4  # b\"--{boundary}--\\r\\n\"\n        return length\n\n    # Content stream interface.\n\n    def get_headers(self) -> dict[str, str]:\n        content_length = self.get_content_length()\n        content_type = self.content_type\n        if content_length is None:\n            return {\"Transfer-Encoding\": \"chunked\", \"Content-Type\": content_type}\n        return {\"Content-Length\": str(content_length), \"Content-Type\": content_type}\n\n    def __iter__(self) -> typing.Iterator[bytes]:\n        for chunk in self.iter_chunks():\n            yield chunk\n\n    async def __aiter__(self) -> typing.AsyncIterator[bytes]:\n        for chunk in self.iter_chunks():\n            yield chunk\n"
  },
  {
    "path": "httpx/_status_codes.py",
    "content": "from __future__ import annotations\n\nfrom enum import IntEnum\n\n__all__ = [\"codes\"]\n\n\nclass codes(IntEnum):\n    \"\"\"HTTP status codes and reason phrases\n\n    Status codes from the following RFCs are all observed:\n\n        * RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616\n        * RFC 6585: Additional HTTP Status Codes\n        * RFC 3229: Delta encoding in HTTP\n        * RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518\n        * RFC 5842: Binding Extensions to WebDAV\n        * RFC 7238: Permanent Redirect\n        * RFC 2295: Transparent Content Negotiation in HTTP\n        * RFC 2774: An HTTP Extension Framework\n        * RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2)\n        * RFC 2324: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0)\n        * RFC 7725: An HTTP Status Code to Report Legal Obstacles\n        * RFC 8297: An HTTP Status Code for Indicating Hints\n        * RFC 8470: Using Early Data in HTTP\n    \"\"\"\n\n    def __new__(cls, value: int, phrase: str = \"\") -> codes:\n        obj = int.__new__(cls, value)\n        obj._value_ = value\n\n        obj.phrase = phrase  # type: ignore[attr-defined]\n        return obj\n\n    def __str__(self) -> str:\n        return str(self.value)\n\n    @classmethod\n    def get_reason_phrase(cls, value: int) -> str:\n        try:\n            return codes(value).phrase  # type: ignore\n        except ValueError:\n            return \"\"\n\n    @classmethod\n    def is_informational(cls, value: int) -> bool:\n        \"\"\"\n        Returns `True` for 1xx status codes, `False` otherwise.\n        \"\"\"\n        return 100 <= value <= 199\n\n    @classmethod\n    def is_success(cls, value: int) -> bool:\n        \"\"\"\n        Returns `True` for 2xx status codes, `False` otherwise.\n        \"\"\"\n        return 200 <= value <= 299\n\n    @classmethod\n    def is_redirect(cls, value: int) -> bool:\n        \"\"\"\n        Returns `True` for 3xx status codes, `False` otherwise.\n        \"\"\"\n        return 300 <= value <= 399\n\n    @classmethod\n    def is_client_error(cls, value: int) -> bool:\n        \"\"\"\n        Returns `True` for 4xx status codes, `False` otherwise.\n        \"\"\"\n        return 400 <= value <= 499\n\n    @classmethod\n    def is_server_error(cls, value: int) -> bool:\n        \"\"\"\n        Returns `True` for 5xx status codes, `False` otherwise.\n        \"\"\"\n        return 500 <= value <= 599\n\n    @classmethod\n    def is_error(cls, value: int) -> bool:\n        \"\"\"\n        Returns `True` for 4xx or 5xx status codes, `False` otherwise.\n        \"\"\"\n        return 400 <= value <= 599\n\n    # informational\n    CONTINUE = 100, \"Continue\"\n    SWITCHING_PROTOCOLS = 101, \"Switching Protocols\"\n    PROCESSING = 102, \"Processing\"\n    EARLY_HINTS = 103, \"Early Hints\"\n\n    # success\n    OK = 200, \"OK\"\n    CREATED = 201, \"Created\"\n    ACCEPTED = 202, \"Accepted\"\n    NON_AUTHORITATIVE_INFORMATION = 203, \"Non-Authoritative Information\"\n    NO_CONTENT = 204, \"No Content\"\n    RESET_CONTENT = 205, \"Reset Content\"\n    PARTIAL_CONTENT = 206, \"Partial Content\"\n    MULTI_STATUS = 207, \"Multi-Status\"\n    ALREADY_REPORTED = 208, \"Already Reported\"\n    IM_USED = 226, \"IM Used\"\n\n    # redirection\n    MULTIPLE_CHOICES = 300, \"Multiple Choices\"\n    MOVED_PERMANENTLY = 301, \"Moved Permanently\"\n    FOUND = 302, \"Found\"\n    SEE_OTHER = 303, \"See Other\"\n    NOT_MODIFIED = 304, \"Not Modified\"\n    USE_PROXY = 305, \"Use Proxy\"\n    TEMPORARY_REDIRECT = 307, \"Temporary Redirect\"\n    PERMANENT_REDIRECT = 308, \"Permanent Redirect\"\n\n    # client error\n    BAD_REQUEST = 400, \"Bad Request\"\n    UNAUTHORIZED = 401, \"Unauthorized\"\n    PAYMENT_REQUIRED = 402, \"Payment Required\"\n    FORBIDDEN = 403, \"Forbidden\"\n    NOT_FOUND = 404, \"Not Found\"\n    METHOD_NOT_ALLOWED = 405, \"Method Not Allowed\"\n    NOT_ACCEPTABLE = 406, \"Not Acceptable\"\n    PROXY_AUTHENTICATION_REQUIRED = 407, \"Proxy Authentication Required\"\n    REQUEST_TIMEOUT = 408, \"Request Timeout\"\n    CONFLICT = 409, \"Conflict\"\n    GONE = 410, \"Gone\"\n    LENGTH_REQUIRED = 411, \"Length Required\"\n    PRECONDITION_FAILED = 412, \"Precondition Failed\"\n    REQUEST_ENTITY_TOO_LARGE = 413, \"Request Entity Too Large\"\n    REQUEST_URI_TOO_LONG = 414, \"Request-URI Too Long\"\n    UNSUPPORTED_MEDIA_TYPE = 415, \"Unsupported Media Type\"\n    REQUESTED_RANGE_NOT_SATISFIABLE = 416, \"Requested Range Not Satisfiable\"\n    EXPECTATION_FAILED = 417, \"Expectation Failed\"\n    IM_A_TEAPOT = 418, \"I'm a teapot\"\n    MISDIRECTED_REQUEST = 421, \"Misdirected Request\"\n    UNPROCESSABLE_ENTITY = 422, \"Unprocessable Entity\"\n    LOCKED = 423, \"Locked\"\n    FAILED_DEPENDENCY = 424, \"Failed Dependency\"\n    TOO_EARLY = 425, \"Too Early\"\n    UPGRADE_REQUIRED = 426, \"Upgrade Required\"\n    PRECONDITION_REQUIRED = 428, \"Precondition Required\"\n    TOO_MANY_REQUESTS = 429, \"Too Many Requests\"\n    REQUEST_HEADER_FIELDS_TOO_LARGE = 431, \"Request Header Fields Too Large\"\n    UNAVAILABLE_FOR_LEGAL_REASONS = 451, \"Unavailable For Legal Reasons\"\n\n    # server errors\n    INTERNAL_SERVER_ERROR = 500, \"Internal Server Error\"\n    NOT_IMPLEMENTED = 501, \"Not Implemented\"\n    BAD_GATEWAY = 502, \"Bad Gateway\"\n    SERVICE_UNAVAILABLE = 503, \"Service Unavailable\"\n    GATEWAY_TIMEOUT = 504, \"Gateway Timeout\"\n    HTTP_VERSION_NOT_SUPPORTED = 505, \"HTTP Version Not Supported\"\n    VARIANT_ALSO_NEGOTIATES = 506, \"Variant Also Negotiates\"\n    INSUFFICIENT_STORAGE = 507, \"Insufficient Storage\"\n    LOOP_DETECTED = 508, \"Loop Detected\"\n    NOT_EXTENDED = 510, \"Not Extended\"\n    NETWORK_AUTHENTICATION_REQUIRED = 511, \"Network Authentication Required\"\n\n\n# Include lower-case styles for `requests` compatibility.\nfor code in codes:\n    setattr(codes, code._name_.lower(), int(code))\n"
  },
  {
    "path": "httpx/_transports/__init__.py",
    "content": "from .asgi import *\nfrom .base import *\nfrom .default import *\nfrom .mock import *\nfrom .wsgi import *\n\n__all__ = [\n    \"ASGITransport\",\n    \"AsyncBaseTransport\",\n    \"BaseTransport\",\n    \"AsyncHTTPTransport\",\n    \"HTTPTransport\",\n    \"MockTransport\",\n    \"WSGITransport\",\n]\n"
  },
  {
    "path": "httpx/_transports/asgi.py",
    "content": "from __future__ import annotations\n\nimport typing\n\nfrom .._models import Request, Response\nfrom .._types import AsyncByteStream\nfrom .base import AsyncBaseTransport\n\nif typing.TYPE_CHECKING:  # pragma: no cover\n    import asyncio\n\n    import trio\n\n    Event = typing.Union[asyncio.Event, trio.Event]\n\n\n_Message = typing.MutableMapping[str, typing.Any]\n_Receive = typing.Callable[[], typing.Awaitable[_Message]]\n_Send = typing.Callable[\n    [typing.MutableMapping[str, typing.Any]], typing.Awaitable[None]\n]\n_ASGIApp = typing.Callable[\n    [typing.MutableMapping[str, typing.Any], _Receive, _Send], typing.Awaitable[None]\n]\n\n__all__ = [\"ASGITransport\"]\n\n\ndef is_running_trio() -> bool:\n    try:\n        # sniffio is a dependency of trio.\n\n        # See https://github.com/python-trio/trio/issues/2802\n        import sniffio\n\n        if sniffio.current_async_library() == \"trio\":\n            return True\n    except ImportError:  # pragma: nocover\n        pass\n\n    return False\n\n\ndef create_event() -> Event:\n    if is_running_trio():\n        import trio\n\n        return trio.Event()\n\n    import asyncio\n\n    return asyncio.Event()\n\n\nclass ASGIResponseStream(AsyncByteStream):\n    def __init__(self, body: list[bytes]) -> None:\n        self._body = body\n\n    async def __aiter__(self) -> typing.AsyncIterator[bytes]:\n        yield b\"\".join(self._body)\n\n\nclass ASGITransport(AsyncBaseTransport):\n    \"\"\"\n    A custom AsyncTransport that handles sending requests directly to an ASGI app.\n\n    ```python\n    transport = httpx.ASGITransport(\n        app=app,\n        root_path=\"/submount\",\n        client=(\"1.2.3.4\", 123)\n    )\n    client = httpx.AsyncClient(transport=transport)\n    ```\n\n    Arguments:\n\n    * `app` - The ASGI application.\n    * `raise_app_exceptions` - Boolean indicating if exceptions in the application\n       should be raised. Default to `True`. Can be set to `False` for use cases\n       such as testing the content of a client 500 response.\n    * `root_path` - The root path on which the ASGI application should be mounted.\n    * `client` - A two-tuple indicating the client IP and port of incoming requests.\n    ```\n    \"\"\"\n\n    def __init__(\n        self,\n        app: _ASGIApp,\n        raise_app_exceptions: bool = True,\n        root_path: str = \"\",\n        client: tuple[str, int] = (\"127.0.0.1\", 123),\n    ) -> None:\n        self.app = app\n        self.raise_app_exceptions = raise_app_exceptions\n        self.root_path = root_path\n        self.client = client\n\n    async def handle_async_request(\n        self,\n        request: Request,\n    ) -> Response:\n        assert isinstance(request.stream, AsyncByteStream)\n\n        # ASGI scope.\n        scope = {\n            \"type\": \"http\",\n            \"asgi\": {\"version\": \"3.0\"},\n            \"http_version\": \"1.1\",\n            \"method\": request.method,\n            \"headers\": [(k.lower(), v) for (k, v) in request.headers.raw],\n            \"scheme\": request.url.scheme,\n            \"path\": request.url.path,\n            \"raw_path\": request.url.raw_path.split(b\"?\")[0],\n            \"query_string\": request.url.query,\n            \"server\": (request.url.host, request.url.port),\n            \"client\": self.client,\n            \"root_path\": self.root_path,\n        }\n\n        # Request.\n        request_body_chunks = request.stream.__aiter__()\n        request_complete = False\n\n        # Response.\n        status_code = None\n        response_headers = None\n        body_parts = []\n        response_started = False\n        response_complete = create_event()\n\n        # ASGI callables.\n\n        async def receive() -> dict[str, typing.Any]:\n            nonlocal request_complete\n\n            if request_complete:\n                await response_complete.wait()\n                return {\"type\": \"http.disconnect\"}\n\n            try:\n                body = await request_body_chunks.__anext__()\n            except StopAsyncIteration:\n                request_complete = True\n                return {\"type\": \"http.request\", \"body\": b\"\", \"more_body\": False}\n            return {\"type\": \"http.request\", \"body\": body, \"more_body\": True}\n\n        async def send(message: typing.MutableMapping[str, typing.Any]) -> None:\n            nonlocal status_code, response_headers, response_started\n\n            if message[\"type\"] == \"http.response.start\":\n                assert not response_started\n\n                status_code = message[\"status\"]\n                response_headers = message.get(\"headers\", [])\n                response_started = True\n\n            elif message[\"type\"] == \"http.response.body\":\n                assert not response_complete.is_set()\n                body = message.get(\"body\", b\"\")\n                more_body = message.get(\"more_body\", False)\n\n                if body and request.method != \"HEAD\":\n                    body_parts.append(body)\n\n                if not more_body:\n                    response_complete.set()\n\n        try:\n            await self.app(scope, receive, send)\n        except Exception:  # noqa: PIE-786\n            if self.raise_app_exceptions:\n                raise\n\n            response_complete.set()\n            if status_code is None:\n                status_code = 500\n            if response_headers is None:\n                response_headers = {}\n\n        assert response_complete.is_set()\n        assert status_code is not None\n        assert response_headers is not None\n\n        stream = ASGIResponseStream(body_parts)\n\n        return Response(status_code, headers=response_headers, stream=stream)\n"
  },
  {
    "path": "httpx/_transports/base.py",
    "content": "from __future__ import annotations\n\nimport typing\nfrom types import TracebackType\n\nfrom .._models import Request, Response\n\nT = typing.TypeVar(\"T\", bound=\"BaseTransport\")\nA = typing.TypeVar(\"A\", bound=\"AsyncBaseTransport\")\n\n__all__ = [\"AsyncBaseTransport\", \"BaseTransport\"]\n\n\nclass BaseTransport:\n    def __enter__(self: T) -> T:\n        return self\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None = None,\n        exc_value: BaseException | None = None,\n        traceback: TracebackType | None = None,\n    ) -> None:\n        self.close()\n\n    def handle_request(self, request: Request) -> Response:\n        \"\"\"\n        Send a single HTTP request and return a response.\n\n        Developers shouldn't typically ever need to call into this API directly,\n        since the Client class provides all the higher level user-facing API\n        niceties.\n\n        In order to properly release any network resources, the response\n        stream should *either* be consumed immediately, with a call to\n        `response.stream.read()`, or else the `handle_request` call should\n        be followed with a try/finally block to ensuring the stream is\n        always closed.\n\n        Example usage:\n\n            with httpx.HTTPTransport() as transport:\n                req = httpx.Request(\n                    method=b\"GET\",\n                    url=(b\"https\", b\"www.example.com\", 443, b\"/\"),\n                    headers=[(b\"Host\", b\"www.example.com\")],\n                )\n                resp = transport.handle_request(req)\n                body = resp.stream.read()\n                print(resp.status_code, resp.headers, body)\n\n\n        Takes a `Request` instance as the only argument.\n\n        Returns a `Response` instance.\n        \"\"\"\n        raise NotImplementedError(\n            \"The 'handle_request' method must be implemented.\"\n        )  # pragma: no cover\n\n    def close(self) -> None:\n        pass\n\n\nclass AsyncBaseTransport:\n    async def __aenter__(self: A) -> A:\n        return self\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None = None,\n        exc_value: BaseException | None = None,\n        traceback: TracebackType | None = None,\n    ) -> None:\n        await self.aclose()\n\n    async def handle_async_request(\n        self,\n        request: Request,\n    ) -> Response:\n        raise NotImplementedError(\n            \"The 'handle_async_request' method must be implemented.\"\n        )  # pragma: no cover\n\n    async def aclose(self) -> None:\n        pass\n"
  },
  {
    "path": "httpx/_transports/default.py",
    "content": "\"\"\"\nCustom transports, with nicely configured defaults.\n\nThe following additional keyword arguments are currently supported by httpcore...\n\n* uds: str\n* local_address: str\n* retries: int\n\nExample usages...\n\n# Disable HTTP/2 on a single specific domain.\nmounts = {\n    \"all://\": httpx.HTTPTransport(http2=True),\n    \"all://*example.org\": httpx.HTTPTransport()\n}\n\n# Using advanced httpcore configuration, with connection retries.\ntransport = httpx.HTTPTransport(retries=1)\nclient = httpx.Client(transport=transport)\n\n# Using advanced httpcore configuration, with unix domain sockets.\ntransport = httpx.HTTPTransport(uds=\"socket.uds\")\nclient = httpx.Client(transport=transport)\n\"\"\"\n\nfrom __future__ import annotations\n\nimport contextlib\nimport typing\nfrom types import TracebackType\n\nif typing.TYPE_CHECKING:\n    import ssl  # pragma: no cover\n\n    import httpx  # pragma: no cover\n\nfrom .._config import DEFAULT_LIMITS, Limits, Proxy, create_ssl_context\nfrom .._exceptions import (\n    ConnectError,\n    ConnectTimeout,\n    LocalProtocolError,\n    NetworkError,\n    PoolTimeout,\n    ProtocolError,\n    ProxyError,\n    ReadError,\n    ReadTimeout,\n    RemoteProtocolError,\n    TimeoutException,\n    UnsupportedProtocol,\n    WriteError,\n    WriteTimeout,\n)\nfrom .._models import Request, Response\nfrom .._types import AsyncByteStream, CertTypes, ProxyTypes, SyncByteStream\nfrom .._urls import URL\nfrom .base import AsyncBaseTransport, BaseTransport\n\nT = typing.TypeVar(\"T\", bound=\"HTTPTransport\")\nA = typing.TypeVar(\"A\", bound=\"AsyncHTTPTransport\")\n\nSOCKET_OPTION = typing.Union[\n    typing.Tuple[int, int, int],\n    typing.Tuple[int, int, typing.Union[bytes, bytearray]],\n    typing.Tuple[int, int, None, int],\n]\n\n__all__ = [\"AsyncHTTPTransport\", \"HTTPTransport\"]\n\nHTTPCORE_EXC_MAP: dict[type[Exception], type[httpx.HTTPError]] = {}\n\n\ndef _load_httpcore_exceptions() -> dict[type[Exception], type[httpx.HTTPError]]:\n    import httpcore\n\n    return {\n        httpcore.TimeoutException: TimeoutException,\n        httpcore.ConnectTimeout: ConnectTimeout,\n        httpcore.ReadTimeout: ReadTimeout,\n        httpcore.WriteTimeout: WriteTimeout,\n        httpcore.PoolTimeout: PoolTimeout,\n        httpcore.NetworkError: NetworkError,\n        httpcore.ConnectError: ConnectError,\n        httpcore.ReadError: ReadError,\n        httpcore.WriteError: WriteError,\n        httpcore.ProxyError: ProxyError,\n        httpcore.UnsupportedProtocol: UnsupportedProtocol,\n        httpcore.ProtocolError: ProtocolError,\n        httpcore.LocalProtocolError: LocalProtocolError,\n        httpcore.RemoteProtocolError: RemoteProtocolError,\n    }\n\n\n@contextlib.contextmanager\ndef map_httpcore_exceptions() -> typing.Iterator[None]:\n    global HTTPCORE_EXC_MAP\n    if len(HTTPCORE_EXC_MAP) == 0:\n        HTTPCORE_EXC_MAP = _load_httpcore_exceptions()\n    try:\n        yield\n    except Exception as exc:\n        mapped_exc = None\n\n        for from_exc, to_exc in HTTPCORE_EXC_MAP.items():\n            if not isinstance(exc, from_exc):\n                continue\n            # We want to map to the most specific exception we can find.\n            # Eg if `exc` is an `httpcore.ReadTimeout`, we want to map to\n            # `httpx.ReadTimeout`, not just `httpx.TimeoutException`.\n            if mapped_exc is None or issubclass(to_exc, mapped_exc):\n                mapped_exc = to_exc\n\n        if mapped_exc is None:  # pragma: no cover\n            raise\n\n        message = str(exc)\n        raise mapped_exc(message) from exc\n\n\nclass ResponseStream(SyncByteStream):\n    def __init__(self, httpcore_stream: typing.Iterable[bytes]) -> None:\n        self._httpcore_stream = httpcore_stream\n\n    def __iter__(self) -> typing.Iterator[bytes]:\n        with map_httpcore_exceptions():\n            for part in self._httpcore_stream:\n                yield part\n\n    def close(self) -> None:\n        if hasattr(self._httpcore_stream, \"close\"):\n            self._httpcore_stream.close()\n\n\nclass HTTPTransport(BaseTransport):\n    def __init__(\n        self,\n        verify: ssl.SSLContext | str | bool = True,\n        cert: CertTypes | None = None,\n        trust_env: bool = True,\n        http1: bool = True,\n        http2: bool = False,\n        limits: Limits = DEFAULT_LIMITS,\n        proxy: ProxyTypes | None = None,\n        uds: str | None = None,\n        local_address: str | None = None,\n        retries: int = 0,\n        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,\n    ) -> None:\n        import httpcore\n\n        proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy\n        ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)\n\n        if proxy is None:\n            self._pool = httpcore.ConnectionPool(\n                ssl_context=ssl_context,\n                max_connections=limits.max_connections,\n                max_keepalive_connections=limits.max_keepalive_connections,\n                keepalive_expiry=limits.keepalive_expiry,\n                http1=http1,\n                http2=http2,\n                uds=uds,\n                local_address=local_address,\n                retries=retries,\n                socket_options=socket_options,\n            )\n        elif proxy.url.scheme in (\"http\", \"https\"):\n            self._pool = httpcore.HTTPProxy(\n                proxy_url=httpcore.URL(\n                    scheme=proxy.url.raw_scheme,\n                    host=proxy.url.raw_host,\n                    port=proxy.url.port,\n                    target=proxy.url.raw_path,\n                ),\n                proxy_auth=proxy.raw_auth,\n                proxy_headers=proxy.headers.raw,\n                ssl_context=ssl_context,\n                proxy_ssl_context=proxy.ssl_context,\n                max_connections=limits.max_connections,\n                max_keepalive_connections=limits.max_keepalive_connections,\n                keepalive_expiry=limits.keepalive_expiry,\n                http1=http1,\n                http2=http2,\n                socket_options=socket_options,\n            )\n        elif proxy.url.scheme in (\"socks5\", \"socks5h\"):\n            try:\n                import socksio  # noqa\n            except ImportError:  # pragma: no cover\n                raise ImportError(\n                    \"Using SOCKS proxy, but the 'socksio' package is not installed. \"\n                    \"Make sure to install httpx using `pip install httpx[socks]`.\"\n                ) from None\n\n            self._pool = httpcore.SOCKSProxy(\n                proxy_url=httpcore.URL(\n                    scheme=proxy.url.raw_scheme,\n                    host=proxy.url.raw_host,\n                    port=proxy.url.port,\n                    target=proxy.url.raw_path,\n                ),\n                proxy_auth=proxy.raw_auth,\n                ssl_context=ssl_context,\n                max_connections=limits.max_connections,\n                max_keepalive_connections=limits.max_keepalive_connections,\n                keepalive_expiry=limits.keepalive_expiry,\n                http1=http1,\n                http2=http2,\n            )\n        else:  # pragma: no cover\n            raise ValueError(\n                \"Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h',\"\n                f\" but got {proxy.url.scheme!r}.\"\n            )\n\n    def __enter__(self: T) -> T:  # Use generics for subclass support.\n        self._pool.__enter__()\n        return self\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None = None,\n        exc_value: BaseException | None = None,\n        traceback: TracebackType | None = None,\n    ) -> None:\n        with map_httpcore_exceptions():\n            self._pool.__exit__(exc_type, exc_value, traceback)\n\n    def handle_request(\n        self,\n        request: Request,\n    ) -> Response:\n        assert isinstance(request.stream, SyncByteStream)\n        import httpcore\n\n        req = httpcore.Request(\n            method=request.method,\n            url=httpcore.URL(\n                scheme=request.url.raw_scheme,\n                host=request.url.raw_host,\n                port=request.url.port,\n                target=request.url.raw_path,\n            ),\n            headers=request.headers.raw,\n            content=request.stream,\n            extensions=request.extensions,\n        )\n        with map_httpcore_exceptions():\n            resp = self._pool.handle_request(req)\n\n        assert isinstance(resp.stream, typing.Iterable)\n\n        return Response(\n            status_code=resp.status,\n            headers=resp.headers,\n            stream=ResponseStream(resp.stream),\n            extensions=resp.extensions,\n        )\n\n    def close(self) -> None:\n        self._pool.close()\n\n\nclass AsyncResponseStream(AsyncByteStream):\n    def __init__(self, httpcore_stream: typing.AsyncIterable[bytes]) -> None:\n        self._httpcore_stream = httpcore_stream\n\n    async def __aiter__(self) -> typing.AsyncIterator[bytes]:\n        with map_httpcore_exceptions():\n            async for part in self._httpcore_stream:\n                yield part\n\n    async def aclose(self) -> None:\n        if hasattr(self._httpcore_stream, \"aclose\"):\n            await self._httpcore_stream.aclose()\n\n\nclass AsyncHTTPTransport(AsyncBaseTransport):\n    def __init__(\n        self,\n        verify: ssl.SSLContext | str | bool = True,\n        cert: CertTypes | None = None,\n        trust_env: bool = True,\n        http1: bool = True,\n        http2: bool = False,\n        limits: Limits = DEFAULT_LIMITS,\n        proxy: ProxyTypes | None = None,\n        uds: str | None = None,\n        local_address: str | None = None,\n        retries: int = 0,\n        socket_options: typing.Iterable[SOCKET_OPTION] | None = None,\n    ) -> None:\n        import httpcore\n\n        proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy\n        ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)\n\n        if proxy is None:\n            self._pool = httpcore.AsyncConnectionPool(\n                ssl_context=ssl_context,\n                max_connections=limits.max_connections,\n                max_keepalive_connections=limits.max_keepalive_connections,\n                keepalive_expiry=limits.keepalive_expiry,\n                http1=http1,\n                http2=http2,\n                uds=uds,\n                local_address=local_address,\n                retries=retries,\n                socket_options=socket_options,\n            )\n        elif proxy.url.scheme in (\"http\", \"https\"):\n            self._pool = httpcore.AsyncHTTPProxy(\n                proxy_url=httpcore.URL(\n                    scheme=proxy.url.raw_scheme,\n                    host=proxy.url.raw_host,\n                    port=proxy.url.port,\n                    target=proxy.url.raw_path,\n                ),\n                proxy_auth=proxy.raw_auth,\n                proxy_headers=proxy.headers.raw,\n                proxy_ssl_context=proxy.ssl_context,\n                ssl_context=ssl_context,\n                max_connections=limits.max_connections,\n                max_keepalive_connections=limits.max_keepalive_connections,\n                keepalive_expiry=limits.keepalive_expiry,\n                http1=http1,\n                http2=http2,\n                socket_options=socket_options,\n            )\n        elif proxy.url.scheme in (\"socks5\", \"socks5h\"):\n            try:\n                import socksio  # noqa\n            except ImportError:  # pragma: no cover\n                raise ImportError(\n                    \"Using SOCKS proxy, but the 'socksio' package is not installed. \"\n                    \"Make sure to install httpx using `pip install httpx[socks]`.\"\n                ) from None\n\n            self._pool = httpcore.AsyncSOCKSProxy(\n                proxy_url=httpcore.URL(\n                    scheme=proxy.url.raw_scheme,\n                    host=proxy.url.raw_host,\n                    port=proxy.url.port,\n                    target=proxy.url.raw_path,\n                ),\n                proxy_auth=proxy.raw_auth,\n                ssl_context=ssl_context,\n                max_connections=limits.max_connections,\n                max_keepalive_connections=limits.max_keepalive_connections,\n                keepalive_expiry=limits.keepalive_expiry,\n                http1=http1,\n                http2=http2,\n            )\n        else:  # pragma: no cover\n            raise ValueError(\n                \"Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h',\"\n                f\" but got {proxy.url.scheme!r}.\"\n            )\n\n    async def __aenter__(self: A) -> A:  # Use generics for subclass support.\n        await self._pool.__aenter__()\n        return self\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None = None,\n        exc_value: BaseException | None = None,\n        traceback: TracebackType | None = None,\n    ) -> None:\n        with map_httpcore_exceptions():\n            await self._pool.__aexit__(exc_type, exc_value, traceback)\n\n    async def handle_async_request(\n        self,\n        request: Request,\n    ) -> Response:\n        assert isinstance(request.stream, AsyncByteStream)\n        import httpcore\n\n        req = httpcore.Request(\n            method=request.method,\n            url=httpcore.URL(\n                scheme=request.url.raw_scheme,\n                host=request.url.raw_host,\n                port=request.url.port,\n                target=request.url.raw_path,\n            ),\n            headers=request.headers.raw,\n            content=request.stream,\n            extensions=request.extensions,\n        )\n        with map_httpcore_exceptions():\n            resp = await self._pool.handle_async_request(req)\n\n        assert isinstance(resp.stream, typing.AsyncIterable)\n\n        return Response(\n            status_code=resp.status,\n            headers=resp.headers,\n            stream=AsyncResponseStream(resp.stream),\n            extensions=resp.extensions,\n        )\n\n    async def aclose(self) -> None:\n        await self._pool.aclose()\n"
  },
  {
    "path": "httpx/_transports/mock.py",
    "content": "from __future__ import annotations\n\nimport typing\n\nfrom .._models import Request, Response\nfrom .base import AsyncBaseTransport, BaseTransport\n\nSyncHandler = typing.Callable[[Request], Response]\nAsyncHandler = typing.Callable[[Request], typing.Coroutine[None, None, Response]]\n\n\n__all__ = [\"MockTransport\"]\n\n\nclass MockTransport(AsyncBaseTransport, BaseTransport):\n    def __init__(self, handler: SyncHandler | AsyncHandler) -> None:\n        self.handler = handler\n\n    def handle_request(\n        self,\n        request: Request,\n    ) -> Response:\n        request.read()\n        response = self.handler(request)\n        if not isinstance(response, Response):  # pragma: no cover\n            raise TypeError(\"Cannot use an async handler in a sync Client\")\n        return response\n\n    async def handle_async_request(\n        self,\n        request: Request,\n    ) -> Response:\n        await request.aread()\n        response = self.handler(request)\n\n        # Allow handler to *optionally* be an `async` function.\n        # If it is, then the `response` variable need to be awaited to actually\n        # return the result.\n\n        if not isinstance(response, Response):\n            response = await response\n\n        return response\n"
  },
  {
    "path": "httpx/_transports/wsgi.py",
    "content": "from __future__ import annotations\n\nimport io\nimport itertools\nimport sys\nimport typing\n\nfrom .._models import Request, Response\nfrom .._types import SyncByteStream\nfrom .base import BaseTransport\n\nif typing.TYPE_CHECKING:\n    from _typeshed import OptExcInfo  # pragma: no cover\n    from _typeshed.wsgi import WSGIApplication  # pragma: no cover\n\n_T = typing.TypeVar(\"_T\")\n\n\n__all__ = [\"WSGITransport\"]\n\n\ndef _skip_leading_empty_chunks(body: typing.Iterable[_T]) -> typing.Iterable[_T]:\n    body = iter(body)\n    for chunk in body:\n        if chunk:\n            return itertools.chain([chunk], body)\n    return []\n\n\nclass WSGIByteStream(SyncByteStream):\n    def __init__(self, result: typing.Iterable[bytes]) -> None:\n        self._close = getattr(result, \"close\", None)\n        self._result = _skip_leading_empty_chunks(result)\n\n    def __iter__(self) -> typing.Iterator[bytes]:\n        for part in self._result:\n            yield part\n\n    def close(self) -> None:\n        if self._close is not None:\n            self._close()\n\n\nclass WSGITransport(BaseTransport):\n    \"\"\"\n    A custom transport that handles sending requests directly to an WSGI app.\n    The simplest way to use this functionality is to use the `app` argument.\n\n    ```\n    client = httpx.Client(app=app)\n    ```\n\n    Alternatively, you can setup the transport instance explicitly.\n    This allows you to include any additional configuration arguments specific\n    to the WSGITransport class:\n\n    ```\n    transport = httpx.WSGITransport(\n        app=app,\n        script_name=\"/submount\",\n        remote_addr=\"1.2.3.4\"\n    )\n    client = httpx.Client(transport=transport)\n    ```\n\n    Arguments:\n\n    * `app` - The WSGI application.\n    * `raise_app_exceptions` - Boolean indicating if exceptions in the application\n       should be raised. Default to `True`. Can be set to `False` for use cases\n       such as testing the content of a client 500 response.\n    * `script_name` - The root path on which the WSGI application should be mounted.\n    * `remote_addr` - A string indicating the client IP of incoming requests.\n    ```\n    \"\"\"\n\n    def __init__(\n        self,\n        app: WSGIApplication,\n        raise_app_exceptions: bool = True,\n        script_name: str = \"\",\n        remote_addr: str = \"127.0.0.1\",\n        wsgi_errors: typing.TextIO | None = None,\n    ) -> None:\n        self.app = app\n        self.raise_app_exceptions = raise_app_exceptions\n        self.script_name = script_name\n        self.remote_addr = remote_addr\n        self.wsgi_errors = wsgi_errors\n\n    def handle_request(self, request: Request) -> Response:\n        request.read()\n        wsgi_input = io.BytesIO(request.content)\n\n        port = request.url.port or {\"http\": 80, \"https\": 443}[request.url.scheme]\n        environ = {\n            \"wsgi.version\": (1, 0),\n            \"wsgi.url_scheme\": request.url.scheme,\n            \"wsgi.input\": wsgi_input,\n            \"wsgi.errors\": self.wsgi_errors or sys.stderr,\n            \"wsgi.multithread\": True,\n            \"wsgi.multiprocess\": False,\n            \"wsgi.run_once\": False,\n            \"REQUEST_METHOD\": request.method,\n            \"SCRIPT_NAME\": self.script_name,\n            \"PATH_INFO\": request.url.path,\n            \"QUERY_STRING\": request.url.query.decode(\"ascii\"),\n            \"SERVER_NAME\": request.url.host,\n            \"SERVER_PORT\": str(port),\n            \"SERVER_PROTOCOL\": \"HTTP/1.1\",\n            \"REMOTE_ADDR\": self.remote_addr,\n        }\n        for header_key, header_value in request.headers.raw:\n            key = header_key.decode(\"ascii\").upper().replace(\"-\", \"_\")\n            if key not in (\"CONTENT_TYPE\", \"CONTENT_LENGTH\"):\n                key = \"HTTP_\" + key\n            environ[key] = header_value.decode(\"ascii\")\n\n        seen_status = None\n        seen_response_headers = None\n        seen_exc_info = None\n\n        def start_response(\n            status: str,\n            response_headers: list[tuple[str, str]],\n            exc_info: OptExcInfo | None = None,\n        ) -> typing.Callable[[bytes], typing.Any]:\n            nonlocal seen_status, seen_response_headers, seen_exc_info\n            seen_status = status\n            seen_response_headers = response_headers\n            seen_exc_info = exc_info\n            return lambda _: None\n\n        result = self.app(environ, start_response)\n\n        stream = WSGIByteStream(result)\n\n        assert seen_status is not None\n        assert seen_response_headers is not None\n        if seen_exc_info and seen_exc_info[0] and self.raise_app_exceptions:\n            raise seen_exc_info[1]\n\n        status_code = int(seen_status.split()[0])\n        headers = [\n            (key.encode(\"ascii\"), value.encode(\"ascii\"))\n            for key, value in seen_response_headers\n        ]\n\n        return Response(status_code, headers=headers, stream=stream)\n"
  },
  {
    "path": "httpx/_types.py",
    "content": "\"\"\"\nType definitions for type checking purposes.\n\"\"\"\n\nfrom http.cookiejar import CookieJar\nfrom typing import (\n    IO,\n    TYPE_CHECKING,\n    Any,\n    AsyncIterable,\n    AsyncIterator,\n    Callable,\n    Dict,\n    Iterable,\n    Iterator,\n    List,\n    Mapping,\n    Optional,\n    Sequence,\n    Tuple,\n    Union,\n)\n\nif TYPE_CHECKING:  # pragma: no cover\n    from ._auth import Auth  # noqa: F401\n    from ._config import Proxy, Timeout  # noqa: F401\n    from ._models import Cookies, Headers, Request  # noqa: F401\n    from ._urls import URL, QueryParams  # noqa: F401\n\n\nPrimitiveData = Optional[Union[str, int, float, bool]]\n\nURLTypes = Union[\"URL\", str]\n\nQueryParamTypes = Union[\n    \"QueryParams\",\n    Mapping[str, Union[PrimitiveData, Sequence[PrimitiveData]]],\n    List[Tuple[str, PrimitiveData]],\n    Tuple[Tuple[str, PrimitiveData], ...],\n    str,\n    bytes,\n]\n\nHeaderTypes = Union[\n    \"Headers\",\n    Mapping[str, str],\n    Mapping[bytes, bytes],\n    Sequence[Tuple[str, str]],\n    Sequence[Tuple[bytes, bytes]],\n]\n\nCookieTypes = Union[\"Cookies\", CookieJar, Dict[str, str], List[Tuple[str, str]]]\n\nTimeoutTypes = Union[\n    Optional[float],\n    Tuple[Optional[float], Optional[float], Optional[float], Optional[float]],\n    \"Timeout\",\n]\nProxyTypes = Union[\"URL\", str, \"Proxy\"]\nCertTypes = Union[str, Tuple[str, str], Tuple[str, str, str]]\n\nAuthTypes = Union[\n    Tuple[Union[str, bytes], Union[str, bytes]],\n    Callable[[\"Request\"], \"Request\"],\n    \"Auth\",\n]\n\nRequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]\nResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]]\nResponseExtensions = Mapping[str, Any]\n\nRequestData = Mapping[str, Any]\n\nFileContent = Union[IO[bytes], bytes, str]\nFileTypes = Union[\n    # file (or bytes)\n    FileContent,\n    # (filename, file (or bytes))\n    Tuple[Optional[str], FileContent],\n    # (filename, file (or bytes), content_type)\n    Tuple[Optional[str], FileContent, Optional[str]],\n    # (filename, file (or bytes), content_type, headers)\n    Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],\n]\nRequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]\n\nRequestExtensions = Mapping[str, Any]\n\n__all__ = [\"AsyncByteStream\", \"SyncByteStream\"]\n\n\nclass SyncByteStream:\n    def __iter__(self) -> Iterator[bytes]:\n        raise NotImplementedError(\n            \"The '__iter__' method must be implemented.\"\n        )  # pragma: no cover\n        yield b\"\"  # pragma: no cover\n\n    def close(self) -> None:\n        \"\"\"\n        Subclasses can override this method to release any network resources\n        after a request/response cycle is complete.\n        \"\"\"\n\n\nclass AsyncByteStream:\n    async def __aiter__(self) -> AsyncIterator[bytes]:\n        raise NotImplementedError(\n            \"The '__aiter__' method must be implemented.\"\n        )  # pragma: no cover\n        yield b\"\"  # pragma: no cover\n\n    async def aclose(self) -> None:\n        pass\n"
  },
  {
    "path": "httpx/_urlparse.py",
    "content": "\"\"\"\nAn implementation of `urlparse` that provides URL validation and normalization\nas described by RFC3986.\n\nWe rely on this implementation rather than the one in Python's stdlib, because:\n\n* It provides more complete URL validation.\n* It properly differentiates between an empty querystring and an absent querystring,\n  to distinguish URLs with a trailing '?'.\n* It handles scheme, hostname, port, and path normalization.\n* It supports IDNA hostnames, normalizing them to their encoded form.\n* The API supports passing individual components, as well as the complete URL string.\n\nPreviously we relied on the excellent `rfc3986` package to handle URL parsing and\nvalidation, but this module provides a simpler alternative, with less indirection\nrequired.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport ipaddress\nimport re\nimport typing\n\nimport idna\n\nfrom ._exceptions import InvalidURL\n\nMAX_URL_LENGTH = 65536\n\n# https://datatracker.ietf.org/doc/html/rfc3986.html#section-2.3\nUNRESERVED_CHARACTERS = (\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~\"\n)\nSUB_DELIMS = \"!$&'()*+,;=\"\n\nPERCENT_ENCODED_REGEX = re.compile(\"%[A-Fa-f0-9]{2}\")\n\n# https://url.spec.whatwg.org/#percent-encoded-bytes\n\n# The fragment percent-encode set is the C0 control percent-encode set\n# and U+0020 SPACE, U+0022 (\"), U+003C (<), U+003E (>), and U+0060 (`).\nFRAG_SAFE = \"\".join(\n    [chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x3C, 0x3E, 0x60)]\n)\n\n# The query percent-encode set is the C0 control percent-encode set\n# and U+0020 SPACE, U+0022 (\"), U+0023 (#), U+003C (<), and U+003E (>).\nQUERY_SAFE = \"\".join(\n    [chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E)]\n)\n\n# The path percent-encode set is the query percent-encode set\n# and U+003F (?), U+0060 (`), U+007B ({), and U+007D (}).\nPATH_SAFE = \"\".join(\n    [\n        chr(i)\n        for i in range(0x20, 0x7F)\n        if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + (0x3F, 0x60, 0x7B, 0x7D)\n    ]\n)\n\n# The userinfo percent-encode set is the path percent-encode set\n# and U+002F (/), U+003A (:), U+003B (;), U+003D (=), U+0040 (@),\n# U+005B ([) to U+005E (^), inclusive, and U+007C (|).\nUSERNAME_SAFE = \"\".join(\n    [\n        chr(i)\n        for i in range(0x20, 0x7F)\n        if i\n        not in (0x20, 0x22, 0x23, 0x3C, 0x3E)\n        + (0x3F, 0x60, 0x7B, 0x7D)\n        + (0x2F, 0x3A, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C)\n    ]\n)\nPASSWORD_SAFE = \"\".join(\n    [\n        chr(i)\n        for i in range(0x20, 0x7F)\n        if i\n        not in (0x20, 0x22, 0x23, 0x3C, 0x3E)\n        + (0x3F, 0x60, 0x7B, 0x7D)\n        + (0x2F, 0x3A, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C)\n    ]\n)\n# Note... The terminology 'userinfo' percent-encode set in the WHATWG document\n# is used for the username and password quoting. For the joint userinfo component\n# we remove U+003A (:) from the safe set.\nUSERINFO_SAFE = \"\".join(\n    [\n        chr(i)\n        for i in range(0x20, 0x7F)\n        if i\n        not in (0x20, 0x22, 0x23, 0x3C, 0x3E)\n        + (0x3F, 0x60, 0x7B, 0x7D)\n        + (0x2F, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C)\n    ]\n)\n\n\n# {scheme}:      (optional)\n# //{authority}  (optional)\n# {path}\n# ?{query}       (optional)\n# #{fragment}    (optional)\nURL_REGEX = re.compile(\n    (\n        r\"(?:(?P<scheme>{scheme}):)?\"\n        r\"(?://(?P<authority>{authority}))?\"\n        r\"(?P<path>{path})\"\n        r\"(?:\\?(?P<query>{query}))?\"\n        r\"(?:#(?P<fragment>{fragment}))?\"\n    ).format(\n        scheme=\"([a-zA-Z][a-zA-Z0-9+.-]*)?\",\n        authority=\"[^/?#]*\",\n        path=\"[^?#]*\",\n        query=\"[^#]*\",\n        fragment=\".*\",\n    )\n)\n\n# {userinfo}@    (optional)\n# {host}\n# :{port}        (optional)\nAUTHORITY_REGEX = re.compile(\n    (\n        r\"(?:(?P<userinfo>{userinfo})@)?\" r\"(?P<host>{host})\" r\":?(?P<port>{port})?\"\n    ).format(\n        userinfo=\".*\",  # Any character sequence.\n        host=\"(\\\\[.*\\\\]|[^:@]*)\",  # Either any character sequence excluding ':' or '@',\n        # or an IPv6 address enclosed within square brackets.\n        port=\".*\",  # Any character sequence.\n    )\n)\n\n\n# If we call urlparse with an individual component, then we need to regex\n# validate that component individually.\n# Note that we're duplicating the same strings as above. Shock! Horror!!\nCOMPONENT_REGEX = {\n    \"scheme\": re.compile(\"([a-zA-Z][a-zA-Z0-9+.-]*)?\"),\n    \"authority\": re.compile(\"[^/?#]*\"),\n    \"path\": re.compile(\"[^?#]*\"),\n    \"query\": re.compile(\"[^#]*\"),\n    \"fragment\": re.compile(\".*\"),\n    \"userinfo\": re.compile(\"[^@]*\"),\n    \"host\": re.compile(\"(\\\\[.*\\\\]|[^:]*)\"),\n    \"port\": re.compile(\".*\"),\n}\n\n\n# We use these simple regexs as a first pass before handing off to\n# the stdlib 'ipaddress' module for IP address validation.\nIPv4_STYLE_HOSTNAME = re.compile(r\"^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$\")\nIPv6_STYLE_HOSTNAME = re.compile(r\"^\\[.*\\]$\")\n\n\nclass ParseResult(typing.NamedTuple):\n    scheme: str\n    userinfo: str\n    host: str\n    port: int | None\n    path: str\n    query: str | None\n    fragment: str | None\n\n    @property\n    def authority(self) -> str:\n        return \"\".join(\n            [\n                f\"{self.userinfo}@\" if self.userinfo else \"\",\n                f\"[{self.host}]\" if \":\" in self.host else self.host,\n                f\":{self.port}\" if self.port is not None else \"\",\n            ]\n        )\n\n    @property\n    def netloc(self) -> str:\n        return \"\".join(\n            [\n                f\"[{self.host}]\" if \":\" in self.host else self.host,\n                f\":{self.port}\" if self.port is not None else \"\",\n            ]\n        )\n\n    def copy_with(self, **kwargs: str | None) -> ParseResult:\n        if not kwargs:\n            return self\n\n        defaults = {\n            \"scheme\": self.scheme,\n            \"authority\": self.authority,\n            \"path\": self.path,\n            \"query\": self.query,\n            \"fragment\": self.fragment,\n        }\n        defaults.update(kwargs)\n        return urlparse(\"\", **defaults)\n\n    def __str__(self) -> str:\n        authority = self.authority\n        return \"\".join(\n            [\n                f\"{self.scheme}:\" if self.scheme else \"\",\n                f\"//{authority}\" if authority else \"\",\n                self.path,\n                f\"?{self.query}\" if self.query is not None else \"\",\n                f\"#{self.fragment}\" if self.fragment is not None else \"\",\n            ]\n        )\n\n\ndef urlparse(url: str = \"\", **kwargs: str | None) -> ParseResult:\n    # Initial basic checks on allowable URLs.\n    # ---------------------------------------\n\n    # Hard limit the maximum allowable URL length.\n    if len(url) > MAX_URL_LENGTH:\n        raise InvalidURL(\"URL too long\")\n\n    # If a URL includes any ASCII control characters including \\t, \\r, \\n,\n    # then treat it as invalid.\n    if any(char.isascii() and not char.isprintable() for char in url):\n        char = next(char for char in url if char.isascii() and not char.isprintable())\n        idx = url.find(char)\n        error = (\n            f\"Invalid non-printable ASCII character in URL, {char!r} at position {idx}.\"\n        )\n        raise InvalidURL(error)\n\n    # Some keyword arguments require special handling.\n    # ------------------------------------------------\n\n    # Coerce \"port\" to a string, if it is provided as an integer.\n    if \"port\" in kwargs:\n        port = kwargs[\"port\"]\n        kwargs[\"port\"] = str(port) if isinstance(port, int) else port\n\n    # Replace \"netloc\" with \"host and \"port\".\n    if \"netloc\" in kwargs:\n        netloc = kwargs.pop(\"netloc\") or \"\"\n        kwargs[\"host\"], _, kwargs[\"port\"] = netloc.partition(\":\")\n\n    # Replace \"username\" and/or \"password\" with \"userinfo\".\n    if \"username\" in kwargs or \"password\" in kwargs:\n        username = quote(kwargs.pop(\"username\", \"\") or \"\", safe=USERNAME_SAFE)\n        password = quote(kwargs.pop(\"password\", \"\") or \"\", safe=PASSWORD_SAFE)\n        kwargs[\"userinfo\"] = f\"{username}:{password}\" if password else username\n\n    # Replace \"raw_path\" with \"path\" and \"query\".\n    if \"raw_path\" in kwargs:\n        raw_path = kwargs.pop(\"raw_path\") or \"\"\n        kwargs[\"path\"], seperator, kwargs[\"query\"] = raw_path.partition(\"?\")\n        if not seperator:\n            kwargs[\"query\"] = None\n\n    # Ensure that IPv6 \"host\" addresses are always escaped with \"[...]\".\n    if \"host\" in kwargs:\n        host = kwargs.get(\"host\") or \"\"\n        if \":\" in host and not (host.startswith(\"[\") and host.endswith(\"]\")):\n            kwargs[\"host\"] = f\"[{host}]\"\n\n    # If any keyword arguments are provided, ensure they are valid.\n    # -------------------------------------------------------------\n\n    for key, value in kwargs.items():\n        if value is not None:\n            if len(value) > MAX_URL_LENGTH:\n                raise InvalidURL(f\"URL component '{key}' too long\")\n\n            # If a component includes any ASCII control characters including \\t, \\r, \\n,\n            # then treat it as invalid.\n            if any(char.isascii() and not char.isprintable() for char in value):\n                char = next(\n                    char for char in value if char.isascii() and not char.isprintable()\n                )\n                idx = value.find(char)\n                error = (\n                    f\"Invalid non-printable ASCII character in URL {key} component, \"\n                    f\"{char!r} at position {idx}.\"\n                )\n                raise InvalidURL(error)\n\n            # Ensure that keyword arguments match as a valid regex.\n            if not COMPONENT_REGEX[key].fullmatch(value):\n                raise InvalidURL(f\"Invalid URL component '{key}'\")\n\n    # The URL_REGEX will always match, but may have empty components.\n    url_match = URL_REGEX.match(url)\n    assert url_match is not None\n    url_dict = url_match.groupdict()\n\n    # * 'scheme', 'authority', and 'path' may be empty strings.\n    # * 'query' may be 'None', indicating no trailing \"?\" portion.\n    #   Any string including the empty string, indicates a trailing \"?\".\n    # * 'fragment' may be 'None', indicating no trailing \"#\" portion.\n    #   Any string including the empty string, indicates a trailing \"#\".\n    scheme = kwargs.get(\"scheme\", url_dict[\"scheme\"]) or \"\"\n    authority = kwargs.get(\"authority\", url_dict[\"authority\"]) or \"\"\n    path = kwargs.get(\"path\", url_dict[\"path\"]) or \"\"\n    query = kwargs.get(\"query\", url_dict[\"query\"])\n    frag = kwargs.get(\"fragment\", url_dict[\"fragment\"])\n\n    # The AUTHORITY_REGEX will always match, but may have empty components.\n    authority_match = AUTHORITY_REGEX.match(authority)\n    assert authority_match is not None\n    authority_dict = authority_match.groupdict()\n\n    # * 'userinfo' and 'host' may be empty strings.\n    # * 'port' may be 'None'.\n    userinfo = kwargs.get(\"userinfo\", authority_dict[\"userinfo\"]) or \"\"\n    host = kwargs.get(\"host\", authority_dict[\"host\"]) or \"\"\n    port = kwargs.get(\"port\", authority_dict[\"port\"])\n\n    # Normalize and validate each component.\n    # We end up with a parsed representation of the URL,\n    # with components that are plain ASCII bytestrings.\n    parsed_scheme: str = scheme.lower()\n    parsed_userinfo: str = quote(userinfo, safe=USERINFO_SAFE)\n    parsed_host: str = encode_host(host)\n    parsed_port: int | None = normalize_port(port, scheme)\n\n    has_scheme = parsed_scheme != \"\"\n    has_authority = (\n        parsed_userinfo != \"\" or parsed_host != \"\" or parsed_port is not None\n    )\n    validate_path(path, has_scheme=has_scheme, has_authority=has_authority)\n    if has_scheme or has_authority:\n        path = normalize_path(path)\n\n    parsed_path: str = quote(path, safe=PATH_SAFE)\n    parsed_query: str | None = None if query is None else quote(query, safe=QUERY_SAFE)\n    parsed_frag: str | None = None if frag is None else quote(frag, safe=FRAG_SAFE)\n\n    # The parsed ASCII bytestrings are our canonical form.\n    # All properties of the URL are derived from these.\n    return ParseResult(\n        parsed_scheme,\n        parsed_userinfo,\n        parsed_host,\n        parsed_port,\n        parsed_path,\n        parsed_query,\n        parsed_frag,\n    )\n\n\ndef encode_host(host: str) -> str:\n    if not host:\n        return \"\"\n\n    elif IPv4_STYLE_HOSTNAME.match(host):\n        # Validate IPv4 hostnames like #.#.#.#\n        #\n        # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2\n        #\n        # IPv4address = dec-octet \".\" dec-octet \".\" dec-octet \".\" dec-octet\n        try:\n            ipaddress.IPv4Address(host)\n        except ipaddress.AddressValueError:\n            raise InvalidURL(f\"Invalid IPv4 address: {host!r}\")\n        return host\n\n    elif IPv6_STYLE_HOSTNAME.match(host):\n        # Validate IPv6 hostnames like [...]\n        #\n        # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2\n        #\n        # \"A host identified by an Internet Protocol literal address, version 6\n        # [RFC3513] or later, is distinguished by enclosing the IP literal\n        # within square brackets (\"[\" and \"]\").  This is the only place where\n        # square bracket characters are allowed in the URI syntax.\"\n        try:\n            ipaddress.IPv6Address(host[1:-1])\n        except ipaddress.AddressValueError:\n            raise InvalidURL(f\"Invalid IPv6 address: {host!r}\")\n        return host[1:-1]\n\n    elif host.isascii():\n        # Regular ASCII hostnames\n        #\n        # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2\n        #\n        # reg-name    = *( unreserved / pct-encoded / sub-delims )\n        WHATWG_SAFE = '\"`{}%|\\\\'\n        return quote(host.lower(), safe=SUB_DELIMS + WHATWG_SAFE)\n\n    # IDNA hostnames\n    try:\n        return idna.encode(host.lower()).decode(\"ascii\")\n    except idna.IDNAError:\n        raise InvalidURL(f\"Invalid IDNA hostname: {host!r}\")\n\n\ndef normalize_port(port: str | int | None, scheme: str) -> int | None:\n    # From https://tools.ietf.org/html/rfc3986#section-3.2.3\n    #\n    # \"A scheme may define a default port.  For example, the \"http\" scheme\n    # defines a default port of \"80\", corresponding to its reserved TCP\n    # port number.  The type of port designated by the port number (e.g.,\n    # TCP, UDP, SCTP) is defined by the URI scheme.  URI producers and\n    # normalizers should omit the port component and its \":\" delimiter if\n    # port is empty or if its value would be the same as that of the\n    # scheme's default.\"\n    if port is None or port == \"\":\n        return None\n\n    try:\n        port_as_int = int(port)\n    except ValueError:\n        raise InvalidURL(f\"Invalid port: {port!r}\")\n\n    # See https://url.spec.whatwg.org/#url-miscellaneous\n    default_port = {\"ftp\": 21, \"http\": 80, \"https\": 443, \"ws\": 80, \"wss\": 443}.get(\n        scheme\n    )\n    if port_as_int == default_port:\n        return None\n    return port_as_int\n\n\ndef validate_path(path: str, has_scheme: bool, has_authority: bool) -> None:\n    \"\"\"\n    Path validation rules that depend on if the URL contains\n    a scheme or authority component.\n\n    See https://datatracker.ietf.org/doc/html/rfc3986.html#section-3.3\n    \"\"\"\n    if has_authority:\n        # If a URI contains an authority component, then the path component\n        # must either be empty or begin with a slash (\"/\") character.\"\n        if path and not path.startswith(\"/\"):\n            raise InvalidURL(\"For absolute URLs, path must be empty or begin with '/'\")\n\n    if not has_scheme and not has_authority:\n        # If a URI does not contain an authority component, then the path cannot begin\n        # with two slash characters (\"//\").\n        if path.startswith(\"//\"):\n            raise InvalidURL(\"Relative URLs cannot have a path starting with '//'\")\n\n        # In addition, a URI reference (Section 4.1) may be a relative-path reference,\n        # in which case the first path segment cannot contain a colon (\":\") character.\n        if path.startswith(\":\"):\n            raise InvalidURL(\"Relative URLs cannot have a path starting with ':'\")\n\n\ndef normalize_path(path: str) -> str:\n    \"\"\"\n    Drop \".\" and \"..\" segments from a URL path.\n\n    For example:\n\n        normalize_path(\"/path/./to/somewhere/..\") == \"/path/to\"\n    \"\"\"\n    # Fast return when no '.' characters in the path.\n    if \".\" not in path:\n        return path\n\n    components = path.split(\"/\")\n\n    # Fast return when no '.' or '..' components in the path.\n    if \".\" not in components and \"..\" not in components:\n        return path\n\n    # https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4\n    output: list[str] = []\n    for component in components:\n        if component == \".\":\n            pass\n        elif component == \"..\":\n            if output and output != [\"\"]:\n                output.pop()\n        else:\n            output.append(component)\n    return \"/\".join(output)\n\n\ndef PERCENT(string: str) -> str:\n    return \"\".join([f\"%{byte:02X}\" for byte in string.encode(\"utf-8\")])\n\n\ndef percent_encoded(string: str, safe: str) -> str:\n    \"\"\"\n    Use percent-encoding to quote a string.\n    \"\"\"\n    NON_ESCAPED_CHARS = UNRESERVED_CHARACTERS + safe\n\n    # Fast path for strings that don't need escaping.\n    if not string.rstrip(NON_ESCAPED_CHARS):\n        return string\n\n    return \"\".join(\n        [char if char in NON_ESCAPED_CHARS else PERCENT(char) for char in string]\n    )\n\n\ndef quote(string: str, safe: str) -> str:\n    \"\"\"\n    Use percent-encoding to quote a string, omitting existing '%xx' escape sequences.\n\n    See: https://www.rfc-editor.org/rfc/rfc3986#section-2.1\n\n    * `string`: The string to be percent-escaped.\n    * `safe`: A string containing characters that may be treated as safe, and do not\n        need to be escaped. Unreserved characters are always treated as safe.\n        See: https://www.rfc-editor.org/rfc/rfc3986#section-2.3\n    \"\"\"\n    parts = []\n    current_position = 0\n    for match in re.finditer(PERCENT_ENCODED_REGEX, string):\n        start_position, end_position = match.start(), match.end()\n        matched_text = match.group(0)\n        # Add any text up to the '%xx' escape sequence.\n        if start_position != current_position:\n            leading_text = string[current_position:start_position]\n            parts.append(percent_encoded(leading_text, safe=safe))\n\n        # Add the '%xx' escape sequence.\n        parts.append(matched_text)\n        current_position = end_position\n\n    # Add any text after the final '%xx' escape sequence.\n    if current_position != len(string):\n        trailing_text = string[current_position:]\n        parts.append(percent_encoded(trailing_text, safe=safe))\n\n    return \"\".join(parts)\n"
  },
  {
    "path": "httpx/_urls.py",
    "content": "from __future__ import annotations\n\nimport typing\nfrom urllib.parse import parse_qs, unquote, urlencode\n\nimport idna\n\nfrom ._types import QueryParamTypes\nfrom ._urlparse import urlparse\nfrom ._utils import primitive_value_to_str\n\n__all__ = [\"URL\", \"QueryParams\"]\n\n\nclass URL:\n    \"\"\"\n    url = httpx.URL(\"HTTPS://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink\")\n\n    assert url.scheme == \"https\"\n    assert url.username == \"jo@email.com\"\n    assert url.password == \"a secret\"\n    assert url.userinfo == b\"jo%40email.com:a%20secret\"\n    assert url.host == \"müller.de\"\n    assert url.raw_host == b\"xn--mller-kva.de\"\n    assert url.port == 1234\n    assert url.netloc == b\"xn--mller-kva.de:1234\"\n    assert url.path == \"/pa th\"\n    assert url.query == b\"?search=ab\"\n    assert url.raw_path == b\"/pa%20th?search=ab\"\n    assert url.fragment == \"anchorlink\"\n\n    The components of a URL are broken down like this:\n\n       https://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink\n    [scheme]   [  username  ] [password] [ host ][port][ path ] [ query ] [fragment]\n               [       userinfo        ] [   netloc   ][    raw_path    ]\n\n    Note that:\n\n    * `url.scheme` is normalized to always be lowercased.\n\n    * `url.host` is normalized to always be lowercased. Internationalized domain\n      names are represented in unicode, without IDNA encoding applied. For instance:\n\n      url = httpx.URL(\"http://中国.icom.museum\")\n      assert url.host == \"中国.icom.museum\"\n      url = httpx.URL(\"http://xn--fiqs8s.icom.museum\")\n      assert url.host == \"中国.icom.museum\"\n\n    * `url.raw_host` is normalized to always be lowercased, and is IDNA encoded.\n\n      url = httpx.URL(\"http://中国.icom.museum\")\n      assert url.raw_host == b\"xn--fiqs8s.icom.museum\"\n      url = httpx.URL(\"http://xn--fiqs8s.icom.museum\")\n      assert url.raw_host == b\"xn--fiqs8s.icom.museum\"\n\n    * `url.port` is either None or an integer. URLs that include the default port for\n      \"http\", \"https\", \"ws\", \"wss\", and \"ftp\" schemes have their port\n      normalized to `None`.\n\n      assert httpx.URL(\"http://example.com\") == httpx.URL(\"http://example.com:80\")\n      assert httpx.URL(\"http://example.com\").port is None\n      assert httpx.URL(\"http://example.com:80\").port is None\n\n    * `url.userinfo` is raw bytes, without URL escaping. Usually you'll want to work\n      with `url.username` and `url.password` instead, which handle the URL escaping.\n\n    * `url.raw_path` is raw bytes of both the path and query, without URL escaping.\n      This portion is used as the target when constructing HTTP requests. Usually you'll\n      want to work with `url.path` instead.\n\n    * `url.query` is raw bytes, without URL escaping. A URL query string portion can\n      only be properly URL escaped when decoding the parameter names and values\n      themselves.\n    \"\"\"\n\n    def __init__(self, url: URL | str = \"\", **kwargs: typing.Any) -> None:\n        if kwargs:\n            allowed = {\n                \"scheme\": str,\n                \"username\": str,\n                \"password\": str,\n                \"userinfo\": bytes,\n                \"host\": str,\n                \"port\": int,\n                \"netloc\": bytes,\n                \"path\": str,\n                \"query\": bytes,\n                \"raw_path\": bytes,\n                \"fragment\": str,\n                \"params\": object,\n            }\n\n            # Perform type checking for all supported keyword arguments.\n            for key, value in kwargs.items():\n                if key not in allowed:\n                    message = f\"{key!r} is an invalid keyword argument for URL()\"\n                    raise TypeError(message)\n                if value is not None and not isinstance(value, allowed[key]):\n                    expected = allowed[key].__name__\n                    seen = type(value).__name__\n                    message = f\"Argument {key!r} must be {expected} but got {seen}\"\n                    raise TypeError(message)\n                if isinstance(value, bytes):\n                    kwargs[key] = value.decode(\"ascii\")\n\n            if \"params\" in kwargs:\n                # Replace any \"params\" keyword with the raw \"query\" instead.\n                #\n                # Ensure that empty params use `kwargs[\"query\"] = None` rather\n                # than `kwargs[\"query\"] = \"\"`, so that generated URLs do not\n                # include an empty trailing \"?\".\n                params = kwargs.pop(\"params\")\n                kwargs[\"query\"] = None if not params else str(QueryParams(params))\n\n        if isinstance(url, str):\n            self._uri_reference = urlparse(url, **kwargs)\n        elif isinstance(url, URL):\n            self._uri_reference = url._uri_reference.copy_with(**kwargs)\n        else:\n            raise TypeError(\n                \"Invalid type for url.  Expected str or httpx.URL,\"\n                f\" got {type(url)}: {url!r}\"\n            )\n\n    @property\n    def scheme(self) -> str:\n        \"\"\"\n        The URL scheme, such as \"http\", \"https\".\n        Always normalised to lowercase.\n        \"\"\"\n        return self._uri_reference.scheme\n\n    @property\n    def raw_scheme(self) -> bytes:\n        \"\"\"\n        The raw bytes representation of the URL scheme, such as b\"http\", b\"https\".\n        Always normalised to lowercase.\n        \"\"\"\n        return self._uri_reference.scheme.encode(\"ascii\")\n\n    @property\n    def userinfo(self) -> bytes:\n        \"\"\"\n        The URL userinfo as a raw bytestring.\n        For example: b\"jo%40email.com:a%20secret\".\n        \"\"\"\n        return self._uri_reference.userinfo.encode(\"ascii\")\n\n    @property\n    def username(self) -> str:\n        \"\"\"\n        The URL username as a string, with URL decoding applied.\n        For example: \"jo@email.com\"\n        \"\"\"\n        userinfo = self._uri_reference.userinfo\n        return unquote(userinfo.partition(\":\")[0])\n\n    @property\n    def password(self) -> str:\n        \"\"\"\n        The URL password as a string, with URL decoding applied.\n        For example: \"a secret\"\n        \"\"\"\n        userinfo = self._uri_reference.userinfo\n        return unquote(userinfo.partition(\":\")[2])\n\n    @property\n    def host(self) -> str:\n        \"\"\"\n        The URL host as a string.\n        Always normalized to lowercase, with IDNA hosts decoded into unicode.\n\n        Examples:\n\n        url = httpx.URL(\"http://www.EXAMPLE.org\")\n        assert url.host == \"www.example.org\"\n\n        url = httpx.URL(\"http://中国.icom.museum\")\n        assert url.host == \"中国.icom.museum\"\n\n        url = httpx.URL(\"http://xn--fiqs8s.icom.museum\")\n        assert url.host == \"中国.icom.museum\"\n\n        url = httpx.URL(\"https://[::ffff:192.168.0.1]\")\n        assert url.host == \"::ffff:192.168.0.1\"\n        \"\"\"\n        host: str = self._uri_reference.host\n\n        if host.startswith(\"xn--\"):\n            host = idna.decode(host)\n\n        return host\n\n    @property\n    def raw_host(self) -> bytes:\n        \"\"\"\n        The raw bytes representation of the URL host.\n        Always normalized to lowercase, and IDNA encoded.\n\n        Examples:\n\n        url = httpx.URL(\"http://www.EXAMPLE.org\")\n        assert url.raw_host == b\"www.example.org\"\n\n        url = httpx.URL(\"http://中国.icom.museum\")\n        assert url.raw_host == b\"xn--fiqs8s.icom.museum\"\n\n        url = httpx.URL(\"http://xn--fiqs8s.icom.museum\")\n        assert url.raw_host == b\"xn--fiqs8s.icom.museum\"\n\n        url = httpx.URL(\"https://[::ffff:192.168.0.1]\")\n        assert url.raw_host == b\"::ffff:192.168.0.1\"\n        \"\"\"\n        return self._uri_reference.host.encode(\"ascii\")\n\n    @property\n    def port(self) -> int | None:\n        \"\"\"\n        The URL port as an integer.\n\n        Note that the URL class performs port normalization as per the WHATWG spec.\n        Default ports for \"http\", \"https\", \"ws\", \"wss\", and \"ftp\" schemes are always\n        treated as `None`.\n\n        For example:\n\n        assert httpx.URL(\"http://www.example.com\") == httpx.URL(\"http://www.example.com:80\")\n        assert httpx.URL(\"http://www.example.com:80\").port is None\n        \"\"\"\n        return self._uri_reference.port\n\n    @property\n    def netloc(self) -> bytes:\n        \"\"\"\n        Either `<host>` or `<host>:<port>` as bytes.\n        Always normalized to lowercase, and IDNA encoded.\n\n        This property may be used for generating the value of a request\n        \"Host\" header.\n        \"\"\"\n        return self._uri_reference.netloc.encode(\"ascii\")\n\n    @property\n    def path(self) -> str:\n        \"\"\"\n        The URL path as a string. Excluding the query string, and URL decoded.\n\n        For example:\n\n        url = httpx.URL(\"https://example.com/pa%20th\")\n        assert url.path == \"/pa th\"\n        \"\"\"\n        path = self._uri_reference.path or \"/\"\n        return unquote(path)\n\n    @property\n    def query(self) -> bytes:\n        \"\"\"\n        The URL query string, as raw bytes, excluding the leading b\"?\".\n\n        This is necessarily a bytewise interface, because we cannot\n        perform URL decoding of this representation until we've parsed\n        the keys and values into a QueryParams instance.\n\n        For example:\n\n        url = httpx.URL(\"https://example.com/?filter=some%20search%20terms\")\n        assert url.query == b\"filter=some%20search%20terms\"\n        \"\"\"\n        query = self._uri_reference.query or \"\"\n        return query.encode(\"ascii\")\n\n    @property\n    def params(self) -> QueryParams:\n        \"\"\"\n        The URL query parameters, neatly parsed and packaged into an immutable\n        multidict representation.\n        \"\"\"\n        return QueryParams(self._uri_reference.query)\n\n    @property\n    def raw_path(self) -> bytes:\n        \"\"\"\n        The complete URL path and query string as raw bytes.\n        Used as the target when constructing HTTP requests.\n\n        For example:\n\n        GET /users?search=some%20text HTTP/1.1\n        Host: www.example.org\n        Connection: close\n        \"\"\"\n        path = self._uri_reference.path or \"/\"\n        if self._uri_reference.query is not None:\n            path += \"?\" + self._uri_reference.query\n        return path.encode(\"ascii\")\n\n    @property\n    def fragment(self) -> str:\n        \"\"\"\n        The URL fragments, as used in HTML anchors.\n        As a string, without the leading '#'.\n        \"\"\"\n        return unquote(self._uri_reference.fragment or \"\")\n\n    @property\n    def is_absolute_url(self) -> bool:\n        \"\"\"\n        Return `True` for absolute URLs such as 'http://example.com/path',\n        and `False` for relative URLs such as '/path'.\n        \"\"\"\n        # We don't use `.is_absolute` from `rfc3986` because it treats\n        # URLs with a fragment portion as not absolute.\n        # What we actually care about is if the URL provides\n        # a scheme and hostname to which connections should be made.\n        return bool(self._uri_reference.scheme and self._uri_reference.host)\n\n    @property\n    def is_relative_url(self) -> bool:\n        \"\"\"\n        Return `False` for absolute URLs such as 'http://example.com/path',\n        and `True` for relative URLs such as '/path'.\n        \"\"\"\n        return not self.is_absolute_url\n\n    def copy_with(self, **kwargs: typing.Any) -> URL:\n        \"\"\"\n        Copy this URL, returning a new URL with some components altered.\n        Accepts the same set of parameters as the components that are made\n        available via properties on the `URL` class.\n\n        For example:\n\n        url = httpx.URL(\"https://www.example.com\").copy_with(\n            username=\"jo@gmail.com\", password=\"a secret\"\n        )\n        assert url == \"https://jo%40email.com:a%20secret@www.example.com\"\n        \"\"\"\n        return URL(self, **kwargs)\n\n    def copy_set_param(self, key: str, value: typing.Any = None) -> URL:\n        return self.copy_with(params=self.params.set(key, value))\n\n    def copy_add_param(self, key: str, value: typing.Any = None) -> URL:\n        return self.copy_with(params=self.params.add(key, value))\n\n    def copy_remove_param(self, key: str) -> URL:\n        return self.copy_with(params=self.params.remove(key))\n\n    def copy_merge_params(self, params: QueryParamTypes) -> URL:\n        return self.copy_with(params=self.params.merge(params))\n\n    def join(self, url: URL | str) -> URL:\n        \"\"\"\n        Return an absolute URL, using this URL as the base.\n\n        Eg.\n\n        url = httpx.URL(\"https://www.example.com/test\")\n        url = url.join(\"/new/path\")\n        assert url == \"https://www.example.com/new/path\"\n        \"\"\"\n        from urllib.parse import urljoin\n\n        return URL(urljoin(str(self), str(URL(url))))\n\n    def __hash__(self) -> int:\n        return hash(str(self))\n\n    def __eq__(self, other: typing.Any) -> bool:\n        return isinstance(other, (URL, str)) and str(self) == str(URL(other))\n\n    def __str__(self) -> str:\n        return str(self._uri_reference)\n\n    def __repr__(self) -> str:\n        scheme, userinfo, host, port, path, query, fragment = self._uri_reference\n\n        if \":\" in userinfo:\n            # Mask any password component.\n            userinfo = f\"{userinfo.split(':')[0]}:[secure]\"\n\n        authority = \"\".join(\n            [\n                f\"{userinfo}@\" if userinfo else \"\",\n                f\"[{host}]\" if \":\" in host else host,\n                f\":{port}\" if port is not None else \"\",\n            ]\n        )\n        url = \"\".join(\n            [\n                f\"{self.scheme}:\" if scheme else \"\",\n                f\"//{authority}\" if authority else \"\",\n                path,\n                f\"?{query}\" if query is not None else \"\",\n                f\"#{fragment}\" if fragment is not None else \"\",\n            ]\n        )\n\n        return f\"{self.__class__.__name__}({url!r})\"\n\n    @property\n    def raw(self) -> tuple[bytes, bytes, int, bytes]:  # pragma: nocover\n        import collections\n        import warnings\n\n        warnings.warn(\"URL.raw is deprecated.\")\n        RawURL = collections.namedtuple(\n            \"RawURL\", [\"raw_scheme\", \"raw_host\", \"port\", \"raw_path\"]\n        )\n        return RawURL(\n            raw_scheme=self.raw_scheme,\n            raw_host=self.raw_host,\n            port=self.port,\n            raw_path=self.raw_path,\n        )\n\n\nclass QueryParams(typing.Mapping[str, str]):\n    \"\"\"\n    URL query parameters, as a multi-dict.\n    \"\"\"\n\n    def __init__(self, *args: QueryParamTypes | None, **kwargs: typing.Any) -> None:\n        assert len(args) < 2, \"Too many arguments.\"\n        assert not (args and kwargs), \"Cannot mix named and unnamed arguments.\"\n\n        value = args[0] if args else kwargs\n\n        if value is None or isinstance(value, (str, bytes)):\n            value = value.decode(\"ascii\") if isinstance(value, bytes) else value\n            self._dict = parse_qs(value, keep_blank_values=True)\n        elif isinstance(value, QueryParams):\n            self._dict = {k: list(v) for k, v in value._dict.items()}\n        else:\n            dict_value: dict[typing.Any, list[typing.Any]] = {}\n            if isinstance(value, (list, tuple)):\n                # Convert list inputs like:\n                #     [(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")]\n                # To a dict representation, like:\n                #     {\"a\": [\"123\", \"456\"], \"b\": [\"789\"]}\n                for item in value:\n                    dict_value.setdefault(item[0], []).append(item[1])\n            else:\n                # Convert dict inputs like:\n                #    {\"a\": \"123\", \"b\": [\"456\", \"789\"]}\n                # To dict inputs where values are always lists, like:\n                #    {\"a\": [\"123\"], \"b\": [\"456\", \"789\"]}\n                dict_value = {\n                    k: list(v) if isinstance(v, (list, tuple)) else [v]\n                    for k, v in value.items()\n                }\n\n            # Ensure that keys and values are neatly coerced to strings.\n            # We coerce values `True` and `False` to JSON-like \"true\" and \"false\"\n            # representations, and coerce `None` values to the empty string.\n            self._dict = {\n                str(k): [primitive_value_to_str(item) for item in v]\n                for k, v in dict_value.items()\n            }\n\n    def keys(self) -> typing.KeysView[str]:\n        \"\"\"\n        Return all the keys in the query params.\n\n        Usage:\n\n        q = httpx.QueryParams(\"a=123&a=456&b=789\")\n        assert list(q.keys()) == [\"a\", \"b\"]\n        \"\"\"\n        return self._dict.keys()\n\n    def values(self) -> typing.ValuesView[str]:\n        \"\"\"\n        Return all the values in the query params. If a key occurs more than once\n        only the first item for that key is returned.\n\n        Usage:\n\n        q = httpx.QueryParams(\"a=123&a=456&b=789\")\n        assert list(q.values()) == [\"123\", \"789\"]\n        \"\"\"\n        return {k: v[0] for k, v in self._dict.items()}.values()\n\n    def items(self) -> typing.ItemsView[str, str]:\n        \"\"\"\n        Return all items in the query params. If a key occurs more than once\n        only the first item for that key is returned.\n\n        Usage:\n\n        q = httpx.QueryParams(\"a=123&a=456&b=789\")\n        assert list(q.items()) == [(\"a\", \"123\"), (\"b\", \"789\")]\n        \"\"\"\n        return {k: v[0] for k, v in self._dict.items()}.items()\n\n    def multi_items(self) -> list[tuple[str, str]]:\n        \"\"\"\n        Return all items in the query params. Allow duplicate keys to occur.\n\n        Usage:\n\n        q = httpx.QueryParams(\"a=123&a=456&b=789\")\n        assert list(q.multi_items()) == [(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")]\n        \"\"\"\n        multi_items: list[tuple[str, str]] = []\n        for k, v in self._dict.items():\n            multi_items.extend([(k, i) for i in v])\n        return multi_items\n\n    def get(self, key: typing.Any, default: typing.Any = None) -> typing.Any:\n        \"\"\"\n        Get a value from the query param for a given key. If the key occurs\n        more than once, then only the first value is returned.\n\n        Usage:\n\n        q = httpx.QueryParams(\"a=123&a=456&b=789\")\n        assert q.get(\"a\") == \"123\"\n        \"\"\"\n        if key in self._dict:\n            return self._dict[str(key)][0]\n        return default\n\n    def get_list(self, key: str) -> list[str]:\n        \"\"\"\n        Get all values from the query param for a given key.\n\n        Usage:\n\n        q = httpx.QueryParams(\"a=123&a=456&b=789\")\n        assert q.get_list(\"a\") == [\"123\", \"456\"]\n        \"\"\"\n        return list(self._dict.get(str(key), []))\n\n    def set(self, key: str, value: typing.Any = None) -> QueryParams:\n        \"\"\"\n        Return a new QueryParams instance, setting the value of a key.\n\n        Usage:\n\n        q = httpx.QueryParams(\"a=123\")\n        q = q.set(\"a\", \"456\")\n        assert q == httpx.QueryParams(\"a=456\")\n        \"\"\"\n        q = QueryParams()\n        q._dict = dict(self._dict)\n        q._dict[str(key)] = [primitive_value_to_str(value)]\n        return q\n\n    def add(self, key: str, value: typing.Any = None) -> QueryParams:\n        \"\"\"\n        Return a new QueryParams instance, setting or appending the value of a key.\n\n        Usage:\n\n        q = httpx.QueryParams(\"a=123\")\n        q = q.add(\"a\", \"456\")\n        assert q == httpx.QueryParams(\"a=123&a=456\")\n        \"\"\"\n        q = QueryParams()\n        q._dict = dict(self._dict)\n        q._dict[str(key)] = q.get_list(key) + [primitive_value_to_str(value)]\n        return q\n\n    def remove(self, key: str) -> QueryParams:\n        \"\"\"\n        Return a new QueryParams instance, removing the value of a key.\n\n        Usage:\n\n        q = httpx.QueryParams(\"a=123\")\n        q = q.remove(\"a\")\n        assert q == httpx.QueryParams(\"\")\n        \"\"\"\n        q = QueryParams()\n        q._dict = dict(self._dict)\n        q._dict.pop(str(key), None)\n        return q\n\n    def merge(self, params: QueryParamTypes | None = None) -> QueryParams:\n        \"\"\"\n        Return a new QueryParams instance, updated with.\n\n        Usage:\n\n        q = httpx.QueryParams(\"a=123\")\n        q = q.merge({\"b\": \"456\"})\n        assert q == httpx.QueryParams(\"a=123&b=456\")\n\n        q = httpx.QueryParams(\"a=123\")\n        q = q.merge({\"a\": \"456\", \"b\": \"789\"})\n        assert q == httpx.QueryParams(\"a=456&b=789\")\n        \"\"\"\n        q = QueryParams(params)\n        q._dict = {**self._dict, **q._dict}\n        return q\n\n    def __getitem__(self, key: typing.Any) -> str:\n        return self._dict[key][0]\n\n    def __contains__(self, key: typing.Any) -> bool:\n        return key in self._dict\n\n    def __iter__(self) -> typing.Iterator[typing.Any]:\n        return iter(self.keys())\n\n    def __len__(self) -> int:\n        return len(self._dict)\n\n    def __bool__(self) -> bool:\n        return bool(self._dict)\n\n    def __hash__(self) -> int:\n        return hash(str(self))\n\n    def __eq__(self, other: typing.Any) -> bool:\n        if not isinstance(other, self.__class__):\n            return False\n        return sorted(self.multi_items()) == sorted(other.multi_items())\n\n    def __str__(self) -> str:\n        return urlencode(self.multi_items())\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        query_string = str(self)\n        return f\"{class_name}({query_string!r})\"\n\n    def update(self, params: QueryParamTypes | None = None) -> None:\n        raise RuntimeError(\n            \"QueryParams are immutable since 0.18.0. \"\n            \"Use `q = q.merge(...)` to create an updated copy.\"\n        )\n\n    def __setitem__(self, key: str, value: str) -> None:\n        raise RuntimeError(\n            \"QueryParams are immutable since 0.18.0. \"\n            \"Use `q = q.set(key, value)` to create an updated copy.\"\n        )\n"
  },
  {
    "path": "httpx/_utils.py",
    "content": "from __future__ import annotations\n\nimport ipaddress\nimport os\nimport re\nimport typing\nfrom urllib.request import getproxies\n\nfrom ._types import PrimitiveData\n\nif typing.TYPE_CHECKING:  # pragma: no cover\n    from ._urls import URL\n\n\ndef primitive_value_to_str(value: PrimitiveData) -> str:\n    \"\"\"\n    Coerce a primitive data type into a string value.\n\n    Note that we prefer JSON-style 'true'/'false' for boolean values here.\n    \"\"\"\n    if value is True:\n        return \"true\"\n    elif value is False:\n        return \"false\"\n    elif value is None:\n        return \"\"\n    return str(value)\n\n\ndef get_environment_proxies() -> dict[str, str | None]:\n    \"\"\"Gets proxy information from the environment\"\"\"\n\n    # urllib.request.getproxies() falls back on System\n    # Registry and Config for proxies on Windows and macOS.\n    # We don't want to propagate non-HTTP proxies into\n    # our configuration such as 'TRAVIS_APT_PROXY'.\n    proxy_info = getproxies()\n    mounts: dict[str, str | None] = {}\n\n    for scheme in (\"http\", \"https\", \"all\"):\n        if proxy_info.get(scheme):\n            hostname = proxy_info[scheme]\n            mounts[f\"{scheme}://\"] = (\n                hostname if \"://\" in hostname else f\"http://{hostname}\"\n            )\n\n    no_proxy_hosts = [host.strip() for host in proxy_info.get(\"no\", \"\").split(\",\")]\n    for hostname in no_proxy_hosts:\n        # See https://curl.haxx.se/libcurl/c/CURLOPT_NOPROXY.html for details\n        # on how names in `NO_PROXY` are handled.\n        if hostname == \"*\":\n            # If NO_PROXY=* is used or if \"*\" occurs as any one of the comma\n            # separated hostnames, then we should just bypass any information\n            # from HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and always ignore\n            # proxies.\n            return {}\n        elif hostname:\n            # NO_PROXY=.google.com is marked as \"all://*.google.com,\n            #   which disables \"www.google.com\" but not \"google.com\"\n            # NO_PROXY=google.com is marked as \"all://*google.com,\n            #   which disables \"www.google.com\" and \"google.com\".\n            #   (But not \"wwwgoogle.com\")\n            # NO_PROXY can include domains, IPv6, IPv4 addresses and \"localhost\"\n            #   NO_PROXY=example.com,::1,localhost,192.168.0.0/16\n            if \"://\" in hostname:\n                mounts[hostname] = None\n            elif is_ipv4_hostname(hostname):\n                mounts[f\"all://{hostname}\"] = None\n            elif is_ipv6_hostname(hostname):\n                mounts[f\"all://[{hostname}]\"] = None\n            elif hostname.lower() == \"localhost\":\n                mounts[f\"all://{hostname}\"] = None\n            else:\n                mounts[f\"all://*{hostname}\"] = None\n\n    return mounts\n\n\ndef to_bytes(value: str | bytes, encoding: str = \"utf-8\") -> bytes:\n    return value.encode(encoding) if isinstance(value, str) else value\n\n\ndef to_str(value: str | bytes, encoding: str = \"utf-8\") -> str:\n    return value if isinstance(value, str) else value.decode(encoding)\n\n\ndef to_bytes_or_str(value: str, match_type_of: typing.AnyStr) -> typing.AnyStr:\n    return value if isinstance(match_type_of, str) else value.encode()\n\n\ndef unquote(value: str) -> str:\n    return value[1:-1] if value[0] == value[-1] == '\"' else value\n\n\ndef peek_filelike_length(stream: typing.Any) -> int | None:\n    \"\"\"\n    Given a file-like stream object, return its length in number of bytes\n    without reading it into memory.\n    \"\"\"\n    try:\n        # Is it an actual file?\n        fd = stream.fileno()\n        # Yup, seems to be an actual file.\n        length = os.fstat(fd).st_size\n    except (AttributeError, OSError):\n        # No... Maybe it's something that supports random access, like `io.BytesIO`?\n        try:\n            # Assuming so, go to end of stream to figure out its length,\n            # then put it back in place.\n            offset = stream.tell()\n            length = stream.seek(0, os.SEEK_END)\n            stream.seek(offset)\n        except (AttributeError, OSError):\n            # Not even that? Sorry, we're doomed...\n            return None\n\n    return length\n\n\nclass URLPattern:\n    \"\"\"\n    A utility class currently used for making lookups against proxy keys...\n\n    # Wildcard matching...\n    >>> pattern = URLPattern(\"all://\")\n    >>> pattern.matches(httpx.URL(\"http://example.com\"))\n    True\n\n    # Witch scheme matching...\n    >>> pattern = URLPattern(\"https://\")\n    >>> pattern.matches(httpx.URL(\"https://example.com\"))\n    True\n    >>> pattern.matches(httpx.URL(\"http://example.com\"))\n    False\n\n    # With domain matching...\n    >>> pattern = URLPattern(\"https://example.com\")\n    >>> pattern.matches(httpx.URL(\"https://example.com\"))\n    True\n    >>> pattern.matches(httpx.URL(\"http://example.com\"))\n    False\n    >>> pattern.matches(httpx.URL(\"https://other.com\"))\n    False\n\n    # Wildcard scheme, with domain matching...\n    >>> pattern = URLPattern(\"all://example.com\")\n    >>> pattern.matches(httpx.URL(\"https://example.com\"))\n    True\n    >>> pattern.matches(httpx.URL(\"http://example.com\"))\n    True\n    >>> pattern.matches(httpx.URL(\"https://other.com\"))\n    False\n\n    # With port matching...\n    >>> pattern = URLPattern(\"https://example.com:1234\")\n    >>> pattern.matches(httpx.URL(\"https://example.com:1234\"))\n    True\n    >>> pattern.matches(httpx.URL(\"https://example.com\"))\n    False\n    \"\"\"\n\n    def __init__(self, pattern: str) -> None:\n        from ._urls import URL\n\n        if pattern and \":\" not in pattern:\n            raise ValueError(\n                f\"Proxy keys should use proper URL forms rather \"\n                f\"than plain scheme strings. \"\n                f'Instead of \"{pattern}\", use \"{pattern}://\"'\n            )\n\n        url = URL(pattern)\n        self.pattern = pattern\n        self.scheme = \"\" if url.scheme == \"all\" else url.scheme\n        self.host = \"\" if url.host == \"*\" else url.host\n        self.port = url.port\n        if not url.host or url.host == \"*\":\n            self.host_regex: typing.Pattern[str] | None = None\n        elif url.host.startswith(\"*.\"):\n            # *.example.com should match \"www.example.com\", but not \"example.com\"\n            domain = re.escape(url.host[2:])\n            self.host_regex = re.compile(f\"^.+\\\\.{domain}$\")\n        elif url.host.startswith(\"*\"):\n            # *example.com should match \"www.example.com\" and \"example.com\"\n            domain = re.escape(url.host[1:])\n            self.host_regex = re.compile(f\"^(.+\\\\.)?{domain}$\")\n        else:\n            # example.com should match \"example.com\" but not \"www.example.com\"\n            domain = re.escape(url.host)\n            self.host_regex = re.compile(f\"^{domain}$\")\n\n    def matches(self, other: URL) -> bool:\n        if self.scheme and self.scheme != other.scheme:\n            return False\n        if (\n            self.host\n            and self.host_regex is not None\n            and not self.host_regex.match(other.host)\n        ):\n            return False\n        if self.port is not None and self.port != other.port:\n            return False\n        return True\n\n    @property\n    def priority(self) -> tuple[int, int, int]:\n        \"\"\"\n        The priority allows URLPattern instances to be sortable, so that\n        we can match from most specific to least specific.\n        \"\"\"\n        # URLs with a port should take priority over URLs without a port.\n        port_priority = 0 if self.port is not None else 1\n        # Longer hostnames should match first.\n        host_priority = -len(self.host)\n        # Longer schemes should match first.\n        scheme_priority = -len(self.scheme)\n        return (port_priority, host_priority, scheme_priority)\n\n    def __hash__(self) -> int:\n        return hash(self.pattern)\n\n    def __lt__(self, other: URLPattern) -> bool:\n        return self.priority < other.priority\n\n    def __eq__(self, other: typing.Any) -> bool:\n        return isinstance(other, URLPattern) and self.pattern == other.pattern\n\n\ndef is_ipv4_hostname(hostname: str) -> bool:\n    try:\n        ipaddress.IPv4Address(hostname.split(\"/\")[0])\n    except Exception:\n        return False\n    return True\n\n\ndef is_ipv6_hostname(hostname: str) -> bool:\n    try:\n        ipaddress.IPv6Address(hostname.split(\"/\")[0])\n    except Exception:\n        return False\n    return True\n"
  },
  {
    "path": "httpx/py.typed",
    "content": ""
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: HTTPX\nsite_description: A next-generation HTTP client for Python.\nsite_url: https://www.python-httpx.org/\n\ntheme:\n    name: 'material'\n    custom_dir: 'docs/overrides'\n    palette:\n      - scheme: 'default'\n        media: '(prefers-color-scheme: light)'\n        toggle:\n          icon: 'material/lightbulb'\n          name: \"Switch to dark mode\"\n      - scheme: 'slate'\n        media: '(prefers-color-scheme: dark)'\n        primary: 'blue'\n        toggle:\n          icon: 'material/lightbulb-outline'\n          name: 'Switch to light mode'\n\nrepo_name: encode/httpx\nrepo_url: https://github.com/encode/httpx/\nedit_uri: \"\"\n\nnav:\n    - Introduction: 'index.md'\n    - QuickStart: 'quickstart.md'\n    - Advanced:\n        - Clients: 'advanced/clients.md'\n        - Authentication: 'advanced/authentication.md'\n        - SSL: 'advanced/ssl.md'\n        - Proxies: 'advanced/proxies.md'\n        - Timeouts: 'advanced/timeouts.md'\n        - Resource Limits: 'advanced/resource-limits.md'\n        - Event Hooks: 'advanced/event-hooks.md'\n        - Transports: 'advanced/transports.md'\n        - Text Encodings: 'advanced/text-encodings.md'\n        - Extensions: 'advanced/extensions.md'\n    - Guides:\n        - Async Support: 'async.md'\n        - HTTP/2 Support: 'http2.md'\n        - Logging: 'logging.md'\n        - Requests Compatibility: 'compatibility.md'\n        - Troubleshooting: 'troubleshooting.md'\n    - API Reference:\n        - Developer Interface: 'api.md'\n        - Exceptions: 'exceptions.md'\n        - Environment Variables: 'environment_variables.md'\n    - Community:\n        - Third Party Packages: 'third_party_packages.md'\n        - Contributing: 'contributing.md'\n        - Code of Conduct: 'code_of_conduct.md'\n\nmarkdown_extensions:\n  - admonition\n  - codehilite:\n      css_class: highlight\n  - mkautodoc\n\nextra_css:\n    - css/custom.css\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"hatchling\", \"hatch-fancy-pypi-readme\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"httpx\"\ndescription = \"The next generation HTTP client.\"\nlicense = \"BSD-3-Clause\"\nrequires-python = \">=3.9\"\nauthors = [\n    { name = \"Tom Christie\", email = \"tom@tomchristie.com\" },\n]\nclassifiers = [\n    \"Development Status :: 4 - Beta\",\n    \"Environment :: Web Environment\",\n    \"Framework :: AsyncIO\",\n    \"Framework :: Trio\",\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: BSD License\",\n    \"Operating System :: OS Independent\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3 :: Only\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Topic :: Internet :: WWW/HTTP\",\n]\ndependencies = [\n    \"certifi\",\n    \"httpcore==1.*\",\n    \"anyio\",\n    \"idna\",\n]\ndynamic = [\"readme\", \"version\"]\n\n[project.optional-dependencies]\nbrotli = [\n    \"brotli; platform_python_implementation == 'CPython'\",\n    \"brotlicffi; platform_python_implementation != 'CPython'\",\n]\ncli = [\n    \"click==8.*\",\n    \"pygments==2.*\",\n    \"rich>=10,<15\",\n]\nhttp2 = [\n    \"h2>=3,<5\",\n]\nsocks = [\n    \"socksio==1.*\",\n]\nzstd = [\n  \"zstandard>=0.18.0\",\n]\n\n[project.scripts]\nhttpx = \"httpx:main\"\n\n[project.urls]\nChangelog = \"https://github.com/encode/httpx/blob/master/CHANGELOG.md\"\nDocumentation = \"https://www.python-httpx.org\"\nHomepage = \"https://github.com/encode/httpx\"\nSource = \"https://github.com/encode/httpx\"\n\n[tool.hatch.version]\npath = \"httpx/__version__.py\"\n\n[tool.hatch.build.targets.sdist]\ninclude = [\n    \"/httpx\",\n    \"/CHANGELOG.md\",\n    \"/README.md\",\n    \"/tests\",\n]\n\n[tool.hatch.metadata.hooks.fancy-pypi-readme]\ncontent-type = \"text/markdown\"\n\n[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]\npath = \"README.md\"\n\n[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]\ntext = \"\\n## Release Information\\n\\n\"\n\n[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]\npath = \"CHANGELOG.md\"\npattern = \"\\n(###.+?\\n)## \"\n\n[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]\ntext = \"\\n---\\n\\n[Full changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md)\\n\"\n\n[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]\npattern = 'src=\"(docs/img/.*?)\"'\nreplacement = 'src=\"https://raw.githubusercontent.com/encode/httpx/master/\\1\"'\n\n[tool.ruff.lint]\nselect = [\"E\", \"F\", \"I\", \"B\", \"PIE\"]\nignore = [\"B904\", \"B028\"]\n\n[tool.ruff.lint.isort]\ncombine-as-imports = true\n\n[tool.ruff.lint.per-file-ignores]\n\"__init__.py\" = [\"F403\", \"F405\"]\n\n[tool.mypy]\nignore_missing_imports = true\nstrict = true\n\n[[tool.mypy.overrides]]\nmodule = \"tests.*\"\ndisallow_untyped_defs = false\ncheck_untyped_defs = true\n\n[tool.pytest.ini_options]\naddopts = \"-rxXs\"\nfilterwarnings = [\n  \"error\",\n  \"ignore: You seem to already have a custom sys.excepthook handler installed. I'll skip installing Trio's custom handler, but this means MultiErrors will not show full tracebacks.:RuntimeWarning\",\n  # See: https://github.com/agronholm/anyio/issues/508\n  \"ignore: trio.MultiError is deprecated since Trio 0.22.0:trio.TrioDeprecationWarning\"\n]\nmarkers = [\n  \"copied_from(source, changes=None): mark test as copied from somewhere else, along with a description of changes made to accodomate e.g. our test setup\",\n  \"network: marks tests which require network connection. Used in 3rd-party build environments that have network disabled.\"\n]\n\n[tool.coverage.run]\nomit = [\"venv/*\"]\ninclude = [\"httpx/*\", \"tests/*\"]\n"
  },
  {
    "path": "requirements.txt",
    "content": "# We're pinning our tooling, because it's an environment we can strictly control.\n# On the other hand, we're not pinning package dependencies, because our tests\n# needs to pass with the latest version of the packages.\n# Reference: https://github.com/encode/httpx/pull/1721#discussion_r661241588\n-e .[brotli,cli,http2,socks,zstd]\n\n# Optional charset auto-detection\n# Used in our test cases\nchardet==5.2.0\n\n# Documentation\nmkdocs==1.6.1\nmkautodoc==0.2.0\nmkdocs-material==9.6.18\n\n# Packaging\nbuild==1.3.0\ntwine==6.1.0\n\n# Tests & Linting\ncoverage[toml]==7.10.6\ncryptography==45.0.7\nmypy==1.17.1\npytest==8.4.1\nruff==0.12.11\ntrio==0.31.0\ntrio-typing==0.10.0\ntrustme==1.2.1\nuvicorn==0.35.0\n"
  },
  {
    "path": "scripts/build",
    "content": "#!/bin/sh -e\n\nif [ -d 'venv' ] ; then\n    PREFIX=\"venv/bin/\"\nelse\n    PREFIX=\"\"\nfi\n\nset -x\n\n${PREFIX}python -m build\n${PREFIX}twine check dist/*\n${PREFIX}mkdocs build\n"
  },
  {
    "path": "scripts/check",
    "content": "#!/bin/sh -e\n\nexport PREFIX=\"\"\nif [ -d 'venv' ] ; then\n    export PREFIX=\"venv/bin/\"\nfi\nexport SOURCE_FILES=\"httpx tests\"\n\nset -x\n\n./scripts/sync-version\n${PREFIX}ruff format $SOURCE_FILES --diff\n${PREFIX}mypy $SOURCE_FILES\n${PREFIX}ruff check $SOURCE_FILES\n"
  },
  {
    "path": "scripts/clean",
    "content": "#!/bin/sh -e\n\nif [ -d 'dist' ] ; then\n    rm -r dist\nfi\nif [ -d 'site' ] ; then\n    rm -r site\nfi\nif [ -d 'htmlcov' ] ; then\n    rm -r htmlcov\nfi\nif [ -d 'httpx.egg-info' ] ; then\n    rm -r httpx.egg-info\nfi\n"
  },
  {
    "path": "scripts/coverage",
    "content": "#!/bin/sh -e\n\nexport PREFIX=\"\"\nif [ -d 'venv' ] ; then\n    export PREFIX=\"venv/bin/\"\nfi\nexport SOURCE_FILES=\"httpx tests\"\n\nset -x\n\n${PREFIX}coverage report --show-missing --skip-covered --fail-under=100\n"
  },
  {
    "path": "scripts/docs",
    "content": "#!/bin/sh -e\n\nexport PREFIX=\"\"\nif [ -d 'venv' ] ; then\n    export PREFIX=\"venv/bin/\"\nfi\n\nset -x\n\n${PREFIX}mkdocs serve\n"
  },
  {
    "path": "scripts/install",
    "content": "#!/bin/sh -e\n\n# Use the Python executable provided from the `-p` option, or a default.\n[ \"$1\" = \"-p\" ] && PYTHON=$2 || PYTHON=\"python3\"\n\nREQUIREMENTS=\"requirements.txt\"\nVENV=\"venv\"\n\nset -x\n\nif [ -z \"$GITHUB_ACTIONS\" ]; then\n    \"$PYTHON\" -m venv \"$VENV\"\n    PIP=\"$VENV/bin/pip\"\nelse\n    PIP=\"pip\"\nfi\n\n\"$PIP\" install -U pip\n\"$PIP\" install -r \"$REQUIREMENTS\"\n"
  },
  {
    "path": "scripts/lint",
    "content": "#!/bin/sh -e\n\nexport PREFIX=\"\"\nif [ -d 'venv' ]; then\n    export PREFIX=\"venv/bin/\"\nfi\nexport SOURCE_FILES=\"httpx tests\"\n\nset -x\n\n${PREFIX}ruff check --fix $SOURCE_FILES\n${PREFIX}ruff format $SOURCE_FILES\n"
  },
  {
    "path": "scripts/publish",
    "content": "#!/bin/sh -e\n\nVERSION_FILE=\"httpx/__version__.py\"\n\nif [ -d 'venv' ] ; then\n    PREFIX=\"venv/bin/\"\nelse\n    PREFIX=\"\"\nfi\n\nif [ ! -z \"$GITHUB_ACTIONS\" ]; then\n  git config --local user.email \"41898282+github-actions[bot]@users.noreply.github.com\"\n  git config --local user.name \"GitHub Action\"\n\n  VERSION=`grep __version__ ${VERSION_FILE} | grep -o '[0-9][^\"]*'`\n\n  if [ \"refs/tags/${VERSION}\" != \"${GITHUB_REF}\" ] ; then\n    echo \"GitHub Ref '${GITHUB_REF}' did not match package version '${VERSION}'\"\n    exit 1\n  fi\nfi\n\nset -x\n\n${PREFIX}twine upload dist/*\n${PREFIX}mkdocs gh-deploy --force\n"
  },
  {
    "path": "scripts/sync-version",
    "content": "#!/bin/sh -e\n\nSEMVER_REGEX=\"([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+)?\"\nCHANGELOG_VERSION=$(grep -o -E $SEMVER_REGEX CHANGELOG.md | sed -n 2p)\nVERSION=$(grep -o -E $SEMVER_REGEX httpx/__version__.py | head -1)\necho \"CHANGELOG_VERSION: $CHANGELOG_VERSION\"\necho \"VERSION: $VERSION\"\nif [ \"$CHANGELOG_VERSION\" != \"$VERSION\" ]; then\n    echo \"Version in changelog does not match version in httpx/__version__.py!\"\n    exit 1\nfi\n"
  },
  {
    "path": "scripts/test",
    "content": "#!/bin/sh\n\nexport PREFIX=\"\"\nif [ -d 'venv' ] ; then\n    export PREFIX=\"venv/bin/\"\nfi\n\nset -ex\n\nif [ -z $GITHUB_ACTIONS ]; then\n    scripts/check\nfi\n\n${PREFIX}coverage run -m pytest \"$@\"\n\nif [ -z $GITHUB_ACTIONS ]; then\n    scripts/coverage\nfi\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/client/__init__.py",
    "content": ""
  },
  {
    "path": "tests/client/test_async_client.py",
    "content": "from __future__ import annotations\n\nimport typing\nfrom datetime import timedelta\n\nimport pytest\n\nimport httpx\n\n\n@pytest.mark.anyio\nasync def test_get(server):\n    url = server.url\n    async with httpx.AsyncClient(http2=True) as client:\n        response = await client.get(url)\n    assert response.status_code == 200\n    assert response.text == \"Hello, world!\"\n    assert response.http_version == \"HTTP/1.1\"\n    assert response.headers\n    assert repr(response) == \"<Response [200 OK]>\"\n    assert response.elapsed > timedelta(seconds=0)\n\n\n@pytest.mark.parametrize(\n    \"url\",\n    [\n        pytest.param(\"invalid://example.org\", id=\"scheme-not-http(s)\"),\n        pytest.param(\"://example.org\", id=\"no-scheme\"),\n        pytest.param(\"http://\", id=\"no-host\"),\n    ],\n)\n@pytest.mark.anyio\nasync def test_get_invalid_url(server, url):\n    async with httpx.AsyncClient() as client:\n        with pytest.raises((httpx.UnsupportedProtocol, httpx.LocalProtocolError)):\n            await client.get(url)\n\n\n@pytest.mark.anyio\nasync def test_build_request(server):\n    url = server.url.copy_with(path=\"/echo_headers\")\n    headers = {\"Custom-header\": \"value\"}\n    async with httpx.AsyncClient() as client:\n        request = client.build_request(\"GET\", url)\n        request.headers.update(headers)\n        response = await client.send(request)\n\n    assert response.status_code == 200\n    assert response.url == url\n\n    assert response.json()[\"Custom-header\"] == \"value\"\n\n\n@pytest.mark.anyio\nasync def test_post(server):\n    url = server.url\n    async with httpx.AsyncClient() as client:\n        response = await client.post(url, content=b\"Hello, world!\")\n    assert response.status_code == 200\n\n\n@pytest.mark.anyio\nasync def test_post_json(server):\n    url = server.url\n    async with httpx.AsyncClient() as client:\n        response = await client.post(url, json={\"text\": \"Hello, world!\"})\n    assert response.status_code == 200\n\n\n@pytest.mark.anyio\nasync def test_stream_response(server):\n    async with httpx.AsyncClient() as client:\n        async with client.stream(\"GET\", server.url) as response:\n            body = await response.aread()\n\n    assert response.status_code == 200\n    assert body == b\"Hello, world!\"\n    assert response.content == b\"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_access_content_stream_response(server):\n    async with httpx.AsyncClient() as client:\n        async with client.stream(\"GET\", server.url) as response:\n            pass\n\n    assert response.status_code == 200\n    with pytest.raises(httpx.ResponseNotRead):\n        response.content  # noqa: B018\n\n\n@pytest.mark.anyio\nasync def test_stream_request(server):\n    async def hello_world() -> typing.AsyncIterator[bytes]:\n        yield b\"Hello, \"\n        yield b\"world!\"\n\n    async with httpx.AsyncClient() as client:\n        response = await client.post(server.url, content=hello_world())\n    assert response.status_code == 200\n\n\n@pytest.mark.anyio\nasync def test_cannot_stream_sync_request(server):\n    def hello_world() -> typing.Iterator[bytes]:  # pragma: no cover\n        yield b\"Hello, \"\n        yield b\"world!\"\n\n    async with httpx.AsyncClient() as client:\n        with pytest.raises(RuntimeError):\n            await client.post(server.url, content=hello_world())\n\n\n@pytest.mark.anyio\nasync def test_raise_for_status(server):\n    async with httpx.AsyncClient() as client:\n        for status_code in (200, 400, 404, 500, 505):\n            response = await client.request(\n                \"GET\", server.url.copy_with(path=f\"/status/{status_code}\")\n            )\n\n            if 400 <= status_code < 600:\n                with pytest.raises(httpx.HTTPStatusError) as exc_info:\n                    response.raise_for_status()\n                assert exc_info.value.response == response\n            else:\n                assert response.raise_for_status() is response\n\n\n@pytest.mark.anyio\nasync def test_options(server):\n    async with httpx.AsyncClient() as client:\n        response = await client.options(server.url)\n    assert response.status_code == 200\n    assert response.text == \"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_head(server):\n    async with httpx.AsyncClient() as client:\n        response = await client.head(server.url)\n    assert response.status_code == 200\n    assert response.text == \"\"\n\n\n@pytest.mark.anyio\nasync def test_put(server):\n    async with httpx.AsyncClient() as client:\n        response = await client.put(server.url, content=b\"Hello, world!\")\n    assert response.status_code == 200\n\n\n@pytest.mark.anyio\nasync def test_patch(server):\n    async with httpx.AsyncClient() as client:\n        response = await client.patch(server.url, content=b\"Hello, world!\")\n    assert response.status_code == 200\n\n\n@pytest.mark.anyio\nasync def test_delete(server):\n    async with httpx.AsyncClient() as client:\n        response = await client.delete(server.url)\n    assert response.status_code == 200\n    assert response.text == \"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_100_continue(server):\n    headers = {\"Expect\": \"100-continue\"}\n    content = b\"Echo request body\"\n\n    async with httpx.AsyncClient() as client:\n        response = await client.post(\n            server.url.copy_with(path=\"/echo_body\"), headers=headers, content=content\n        )\n\n    assert response.status_code == 200\n    assert response.content == content\n\n\n@pytest.mark.anyio\nasync def test_context_managed_transport():\n    class Transport(httpx.AsyncBaseTransport):\n        def __init__(self) -> None:\n            self.events: list[str] = []\n\n        async def aclose(self):\n            # The base implementation of httpx.AsyncBaseTransport just\n            # calls into `.aclose`, so simple transport cases can just override\n            # this method for any cleanup, where more complex cases\n            # might want to additionally override `__aenter__`/`__aexit__`.\n            self.events.append(\"transport.aclose\")\n\n        async def __aenter__(self):\n            await super().__aenter__()\n            self.events.append(\"transport.__aenter__\")\n\n        async def __aexit__(self, *args):\n            await super().__aexit__(*args)\n            self.events.append(\"transport.__aexit__\")\n\n    transport = Transport()\n    async with httpx.AsyncClient(transport=transport):\n        pass\n\n    assert transport.events == [\n        \"transport.__aenter__\",\n        \"transport.aclose\",\n        \"transport.__aexit__\",\n    ]\n\n\n@pytest.mark.anyio\nasync def test_context_managed_transport_and_mount():\n    class Transport(httpx.AsyncBaseTransport):\n        def __init__(self, name: str) -> None:\n            self.name: str = name\n            self.events: list[str] = []\n\n        async def aclose(self):\n            # The base implementation of httpx.AsyncBaseTransport just\n            # calls into `.aclose`, so simple transport cases can just override\n            # this method for any cleanup, where more complex cases\n            # might want to additionally override `__aenter__`/`__aexit__`.\n            self.events.append(f\"{self.name}.aclose\")\n\n        async def __aenter__(self):\n            await super().__aenter__()\n            self.events.append(f\"{self.name}.__aenter__\")\n\n        async def __aexit__(self, *args):\n            await super().__aexit__(*args)\n            self.events.append(f\"{self.name}.__aexit__\")\n\n    transport = Transport(name=\"transport\")\n    mounted = Transport(name=\"mounted\")\n    async with httpx.AsyncClient(\n        transport=transport, mounts={\"http://www.example.org\": mounted}\n    ):\n        pass\n\n    assert transport.events == [\n        \"transport.__aenter__\",\n        \"transport.aclose\",\n        \"transport.__aexit__\",\n    ]\n    assert mounted.events == [\n        \"mounted.__aenter__\",\n        \"mounted.aclose\",\n        \"mounted.__aexit__\",\n    ]\n\n\ndef hello_world(request):\n    return httpx.Response(200, text=\"Hello, world!\")\n\n\n@pytest.mark.anyio\nasync def test_client_closed_state_using_implicit_open():\n    client = httpx.AsyncClient(transport=httpx.MockTransport(hello_world))\n\n    assert not client.is_closed\n    await client.get(\"http://example.com\")\n\n    assert not client.is_closed\n    await client.aclose()\n\n    assert client.is_closed\n    # Once we're close we cannot make any more requests.\n    with pytest.raises(RuntimeError):\n        await client.get(\"http://example.com\")\n\n    # Once we're closed we cannot reopen the client.\n    with pytest.raises(RuntimeError):\n        async with client:\n            pass  # pragma: no cover\n\n\n@pytest.mark.anyio\nasync def test_client_closed_state_using_with_block():\n    async with httpx.AsyncClient(transport=httpx.MockTransport(hello_world)) as client:\n        assert not client.is_closed\n        await client.get(\"http://example.com\")\n\n    assert client.is_closed\n    with pytest.raises(RuntimeError):\n        await client.get(\"http://example.com\")\n\n\ndef unmounted(request: httpx.Request) -> httpx.Response:\n    data = {\"app\": \"unmounted\"}\n    return httpx.Response(200, json=data)\n\n\ndef mounted(request: httpx.Request) -> httpx.Response:\n    data = {\"app\": \"mounted\"}\n    return httpx.Response(200, json=data)\n\n\n@pytest.mark.anyio\nasync def test_mounted_transport():\n    transport = httpx.MockTransport(unmounted)\n    mounts = {\"custom://\": httpx.MockTransport(mounted)}\n\n    async with httpx.AsyncClient(transport=transport, mounts=mounts) as client:\n        response = await client.get(\"https://www.example.com\")\n        assert response.status_code == 200\n        assert response.json() == {\"app\": \"unmounted\"}\n\n        response = await client.get(\"custom://www.example.com\")\n        assert response.status_code == 200\n        assert response.json() == {\"app\": \"mounted\"}\n\n\n@pytest.mark.anyio\nasync def test_async_mock_transport():\n    async def hello_world(request: httpx.Request) -> httpx.Response:\n        return httpx.Response(200, text=\"Hello, world!\")\n\n    transport = httpx.MockTransport(hello_world)\n\n    async with httpx.AsyncClient(transport=transport) as client:\n        response = await client.get(\"https://www.example.com\")\n        assert response.status_code == 200\n        assert response.text == \"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_cancellation_during_stream():\n    \"\"\"\n    If any BaseException is raised during streaming the response, then the\n    stream should be closed.\n\n    This includes:\n\n    * `asyncio.CancelledError` (A subclass of BaseException from Python 3.8 onwards.)\n    * `trio.Cancelled`\n    * `KeyboardInterrupt`\n    * `SystemExit`\n\n    See https://github.com/encode/httpx/issues/2139\n    \"\"\"\n    stream_was_closed = False\n\n    def response_with_cancel_during_stream(request):\n        class CancelledStream(httpx.AsyncByteStream):\n            async def __aiter__(self) -> typing.AsyncIterator[bytes]:\n                yield b\"Hello\"\n                raise KeyboardInterrupt()\n                yield b\", world\"  # pragma: no cover\n\n            async def aclose(self) -> None:\n                nonlocal stream_was_closed\n                stream_was_closed = True\n\n        return httpx.Response(\n            200, headers={\"Content-Length\": \"12\"}, stream=CancelledStream()\n        )\n\n    transport = httpx.MockTransport(response_with_cancel_during_stream)\n\n    async with httpx.AsyncClient(transport=transport) as client:\n        with pytest.raises(KeyboardInterrupt):\n            await client.get(\"https://www.example.com\")\n        assert stream_was_closed\n\n\n@pytest.mark.anyio\nasync def test_server_extensions(server):\n    url = server.url\n    async with httpx.AsyncClient(http2=True) as client:\n        response = await client.get(url)\n    assert response.status_code == 200\n    assert response.extensions[\"http_version\"] == b\"HTTP/1.1\"\n"
  },
  {
    "path": "tests/client/test_auth.py",
    "content": "\"\"\"\nIntegration tests for authentication.\n\nUnit tests for auth classes also exist in tests/test_auth.py\n\"\"\"\n\nimport hashlib\nimport netrc\nimport os\nimport sys\nimport threading\nimport typing\nfrom urllib.request import parse_keqv_list\n\nimport anyio\nimport pytest\n\nimport httpx\n\nfrom ..common import FIXTURES_DIR\n\n\nclass App:\n    \"\"\"\n    A mock app to test auth credentials.\n    \"\"\"\n\n    def __init__(self, auth_header: str = \"\", status_code: int = 200) -> None:\n        self.auth_header = auth_header\n        self.status_code = status_code\n\n    def __call__(self, request: httpx.Request) -> httpx.Response:\n        headers = {\"www-authenticate\": self.auth_header} if self.auth_header else {}\n        data = {\"auth\": request.headers.get(\"Authorization\")}\n        return httpx.Response(self.status_code, headers=headers, json=data)\n\n\nclass DigestApp:\n    def __init__(\n        self,\n        algorithm: str = \"SHA-256\",\n        send_response_after_attempt: int = 1,\n        qop: str = \"auth\",\n        regenerate_nonce: bool = True,\n    ) -> None:\n        self.algorithm = algorithm\n        self.send_response_after_attempt = send_response_after_attempt\n        self.qop = qop\n        self._regenerate_nonce = regenerate_nonce\n        self._response_count = 0\n\n    def __call__(self, request: httpx.Request) -> httpx.Response:\n        if self._response_count < self.send_response_after_attempt:\n            return self.challenge_send(request)\n\n        data = {\"auth\": request.headers.get(\"Authorization\")}\n        return httpx.Response(200, json=data)\n\n    def challenge_send(self, request: httpx.Request) -> httpx.Response:\n        self._response_count += 1\n        nonce = (\n            hashlib.sha256(os.urandom(8)).hexdigest()\n            if self._regenerate_nonce\n            else \"ee96edced2a0b43e4869e96ebe27563f369c1205a049d06419bb51d8aeddf3d3\"\n        )\n        challenge_data = {\n            \"nonce\": nonce,\n            \"qop\": self.qop,\n            \"opaque\": (\n                \"ee6378f3ee14ebfd2fff54b70a91a7c9390518047f242ab2271380db0e14bda1\"\n            ),\n            \"algorithm\": self.algorithm,\n            \"stale\": \"FALSE\",\n        }\n        challenge_str = \", \".join(\n            '{}=\"{}\"'.format(key, value)\n            for key, value in challenge_data.items()\n            if value\n        )\n\n        headers = {\n            \"www-authenticate\": f'Digest realm=\"httpx@example.org\", {challenge_str}',\n        }\n        return httpx.Response(401, headers=headers)\n\n\nclass RepeatAuth(httpx.Auth):\n    \"\"\"\n    A mock authentication scheme that requires clients to send\n    the request a fixed number of times, and then send a last request containing\n    an aggregation of nonces that the server sent in 'WWW-Authenticate' headers\n    of intermediate responses.\n    \"\"\"\n\n    requires_request_body = True\n\n    def __init__(self, repeat: int) -> None:\n        self.repeat = repeat\n\n    def auth_flow(\n        self, request: httpx.Request\n    ) -> typing.Generator[httpx.Request, httpx.Response, None]:\n        nonces = []\n\n        for index in range(self.repeat):\n            request.headers[\"Authorization\"] = f\"Repeat {index}\"\n            response = yield request\n            nonces.append(response.headers[\"www-authenticate\"])\n\n        key = \".\".join(nonces)\n        request.headers[\"Authorization\"] = f\"Repeat {key}\"\n        yield request\n\n\nclass ResponseBodyAuth(httpx.Auth):\n    \"\"\"\n    A mock authentication scheme that requires clients to send an 'Authorization'\n    header, then send back the contents of the response in the 'Authorization'\n    header.\n    \"\"\"\n\n    requires_response_body = True\n\n    def __init__(self, token: str) -> None:\n        self.token = token\n\n    def auth_flow(\n        self, request: httpx.Request\n    ) -> typing.Generator[httpx.Request, httpx.Response, None]:\n        request.headers[\"Authorization\"] = self.token\n        response = yield request\n        data = response.text\n        request.headers[\"Authorization\"] = data\n        yield request\n\n\nclass SyncOrAsyncAuth(httpx.Auth):\n    \"\"\"\n    A mock authentication scheme that uses a different implementation for the\n    sync and async cases.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self._lock = threading.Lock()\n        self._async_lock = anyio.Lock()\n\n    def sync_auth_flow(\n        self, request: httpx.Request\n    ) -> typing.Generator[httpx.Request, httpx.Response, None]:\n        with self._lock:\n            request.headers[\"Authorization\"] = \"sync-auth\"\n        yield request\n\n    async def async_auth_flow(\n        self, request: httpx.Request\n    ) -> typing.AsyncGenerator[httpx.Request, httpx.Response]:\n        async with self._async_lock:\n            request.headers[\"Authorization\"] = \"async-auth\"\n        yield request\n\n\n@pytest.mark.anyio\nasync def test_basic_auth() -> None:\n    url = \"https://example.org/\"\n    auth = (\"user\", \"password123\")\n    app = App()\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": \"Basic dXNlcjpwYXNzd29yZDEyMw==\"}\n\n\n@pytest.mark.anyio\nasync def test_basic_auth_with_stream() -> None:\n    \"\"\"\n    See: https://github.com/encode/httpx/pull/1312\n    \"\"\"\n    url = \"https://example.org/\"\n    auth = (\"user\", \"password123\")\n    app = App()\n\n    async with httpx.AsyncClient(\n        transport=httpx.MockTransport(app), auth=auth\n    ) as client:\n        async with client.stream(\"GET\", url) as response:\n            await response.aread()\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": \"Basic dXNlcjpwYXNzd29yZDEyMw==\"}\n\n\n@pytest.mark.anyio\nasync def test_basic_auth_in_url() -> None:\n    url = \"https://user:password123@example.org/\"\n    app = App()\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": \"Basic dXNlcjpwYXNzd29yZDEyMw==\"}\n\n\n@pytest.mark.anyio\nasync def test_basic_auth_on_session() -> None:\n    url = \"https://example.org/\"\n    auth = (\"user\", \"password123\")\n    app = App()\n\n    async with httpx.AsyncClient(\n        transport=httpx.MockTransport(app), auth=auth\n    ) as client:\n        response = await client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": \"Basic dXNlcjpwYXNzd29yZDEyMw==\"}\n\n\n@pytest.mark.anyio\nasync def test_custom_auth() -> None:\n    url = \"https://example.org/\"\n    app = App()\n\n    def auth(request: httpx.Request) -> httpx.Request:\n        request.headers[\"Authorization\"] = \"Token 123\"\n        return request\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": \"Token 123\"}\n\n\ndef test_netrc_auth_credentials_exist() -> None:\n    \"\"\"\n    When netrc auth is being used and a request is made to a host that is\n    in the netrc file, then the relevant credentials should be applied.\n    \"\"\"\n    netrc_file = str(FIXTURES_DIR / \".netrc\")\n    url = \"http://netrcexample.org\"\n    app = App()\n    auth = httpx.NetRCAuth(netrc_file)\n\n    with httpx.Client(transport=httpx.MockTransport(app), auth=auth) as client:\n        response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\n        \"auth\": \"Basic ZXhhbXBsZS11c2VybmFtZTpleGFtcGxlLXBhc3N3b3Jk\"\n    }\n\n\ndef test_netrc_auth_credentials_do_not_exist() -> None:\n    \"\"\"\n    When netrc auth is being used and a request is made to a host that is\n    not in the netrc file, then no credentials should be applied.\n    \"\"\"\n    netrc_file = str(FIXTURES_DIR / \".netrc\")\n    url = \"http://example.org\"\n    app = App()\n    auth = httpx.NetRCAuth(netrc_file)\n\n    with httpx.Client(transport=httpx.MockTransport(app), auth=auth) as client:\n        response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": None}\n\n\n@pytest.mark.skipif(\n    sys.version_info >= (3, 11),\n    reason=\"netrc files without a password are valid from Python >= 3.11\",\n)\ndef test_netrc_auth_nopassword_parse_error() -> None:  # pragma: no cover\n    \"\"\"\n    Python has different netrc parsing behaviours with different versions.\n    For Python < 3.11 a netrc file with no password is invalid. In this case\n    we want to allow the parse error to be raised.\n    \"\"\"\n    netrc_file = str(FIXTURES_DIR / \".netrc-nopassword\")\n    with pytest.raises(netrc.NetrcParseError):\n        httpx.NetRCAuth(netrc_file)\n\n\n@pytest.mark.anyio\nasync def test_auth_disable_per_request() -> None:\n    url = \"https://example.org/\"\n    auth = (\"user\", \"password123\")\n    app = App()\n\n    async with httpx.AsyncClient(\n        transport=httpx.MockTransport(app), auth=auth\n    ) as client:\n        response = await client.get(url, auth=None)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": None}\n\n\ndef test_auth_hidden_url() -> None:\n    url = \"http://example-username:example-password@example.org/\"\n    expected = \"URL('http://example-username:[secure]@example.org/')\"\n    assert url == httpx.URL(url)\n    assert expected == repr(httpx.URL(url))\n\n\n@pytest.mark.anyio\nasync def test_auth_hidden_header() -> None:\n    url = \"https://example.org/\"\n    auth = (\"example-username\", \"example-password\")\n    app = App()\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert \"'authorization': '[secure]'\" in str(response.request.headers)\n\n\n@pytest.mark.anyio\nasync def test_auth_property() -> None:\n    app = App()\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        assert client.auth is None\n\n        client.auth = (\"user\", \"password123\")\n        assert isinstance(client.auth, httpx.BasicAuth)\n\n        url = \"https://example.org/\"\n        response = await client.get(url)\n        assert response.status_code == 200\n        assert response.json() == {\"auth\": \"Basic dXNlcjpwYXNzd29yZDEyMw==\"}\n\n\n@pytest.mark.anyio\nasync def test_auth_invalid_type() -> None:\n    app = App()\n\n    with pytest.raises(TypeError):\n        client = httpx.AsyncClient(\n            transport=httpx.MockTransport(app),\n            auth=\"not a tuple, not a callable\",  # type: ignore\n        )\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        with pytest.raises(TypeError):\n            await client.get(auth=\"not a tuple, not a callable\")  # type: ignore\n\n        with pytest.raises(TypeError):\n            client.auth = \"not a tuple, not a callable\"  # type: ignore\n\n\n@pytest.mark.anyio\nasync def test_digest_auth_returns_no_auth_if_no_digest_header_in_response() -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = App()\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": None}\n    assert len(response.history) == 0\n\n\ndef test_digest_auth_returns_no_auth_if_alternate_auth_scheme() -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    auth_header = \"Token ...\"\n    app = App(auth_header=auth_header, status_code=401)\n\n    client = httpx.Client(transport=httpx.MockTransport(app))\n    response = client.get(url, auth=auth)\n\n    assert response.status_code == 401\n    assert response.json() == {\"auth\": None}\n    assert len(response.history) == 0\n\n\n@pytest.mark.anyio\nasync def test_digest_auth_200_response_including_digest_auth_header() -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    auth_header = 'Digest realm=\"realm@host.com\",qop=\"auth\",nonce=\"abc\",opaque=\"xyz\"'\n    app = App(auth_header=auth_header, status_code=200)\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": None}\n    assert len(response.history) == 0\n\n\n@pytest.mark.anyio\nasync def test_digest_auth_401_response_without_digest_auth_header() -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = App(auth_header=\"\", status_code=401)\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 401\n    assert response.json() == {\"auth\": None}\n    assert len(response.history) == 0\n\n\n@pytest.mark.parametrize(\n    \"algorithm,expected_hash_length,expected_response_length\",\n    [\n        (\"MD5\", 64, 32),\n        (\"MD5-SESS\", 64, 32),\n        (\"SHA\", 64, 40),\n        (\"SHA-SESS\", 64, 40),\n        (\"SHA-256\", 64, 64),\n        (\"SHA-256-SESS\", 64, 64),\n        (\"SHA-512\", 64, 128),\n        (\"SHA-512-SESS\", 64, 128),\n    ],\n)\n@pytest.mark.anyio\nasync def test_digest_auth(\n    algorithm: str, expected_hash_length: int, expected_response_length: int\n) -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = DigestApp(algorithm=algorithm)\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert len(response.history) == 1\n\n    authorization = typing.cast(typing.Dict[str, typing.Any], response.json())[\"auth\"]\n    scheme, _, fields = authorization.partition(\" \")\n    assert scheme == \"Digest\"\n\n    response_fields = [field.strip() for field in fields.split(\",\")]\n    digest_data = dict(field.split(\"=\") for field in response_fields)\n\n    assert digest_data[\"username\"] == '\"user\"'\n    assert digest_data[\"realm\"] == '\"httpx@example.org\"'\n    assert \"nonce\" in digest_data\n    assert digest_data[\"uri\"] == '\"/\"'\n    assert len(digest_data[\"response\"]) == expected_response_length + 2  # extra quotes\n    assert len(digest_data[\"opaque\"]) == expected_hash_length + 2\n    assert digest_data[\"algorithm\"] == algorithm\n    assert digest_data[\"qop\"] == \"auth\"\n    assert digest_data[\"nc\"] == \"00000001\"\n    assert len(digest_data[\"cnonce\"]) == 16 + 2\n\n\n@pytest.mark.anyio\nasync def test_digest_auth_no_specified_qop() -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = DigestApp(qop=\"\")\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert len(response.history) == 1\n\n    authorization = typing.cast(typing.Dict[str, typing.Any], response.json())[\"auth\"]\n    scheme, _, fields = authorization.partition(\" \")\n    assert scheme == \"Digest\"\n\n    response_fields = [field.strip() for field in fields.split(\",\")]\n    digest_data = dict(field.split(\"=\") for field in response_fields)\n\n    assert \"qop\" not in digest_data\n    assert \"nc\" not in digest_data\n    assert \"cnonce\" not in digest_data\n    assert digest_data[\"username\"] == '\"user\"'\n    assert digest_data[\"realm\"] == '\"httpx@example.org\"'\n    assert len(digest_data[\"nonce\"]) == 64 + 2  # extra quotes\n    assert digest_data[\"uri\"] == '\"/\"'\n    assert len(digest_data[\"response\"]) == 64 + 2\n    assert len(digest_data[\"opaque\"]) == 64 + 2\n    assert digest_data[\"algorithm\"] == \"SHA-256\"\n\n\n@pytest.mark.parametrize(\"qop\", (\"auth, auth-int\", \"auth,auth-int\", \"unknown,auth\"))\n@pytest.mark.anyio\nasync def test_digest_auth_qop_including_spaces_and_auth_returns_auth(qop: str) -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = DigestApp(qop=qop)\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert len(response.history) == 1\n\n\n@pytest.mark.anyio\nasync def test_digest_auth_qop_auth_int_not_implemented() -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = DigestApp(qop=\"auth-int\")\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        with pytest.raises(NotImplementedError):\n            await client.get(url, auth=auth)\n\n\n@pytest.mark.anyio\nasync def test_digest_auth_qop_must_be_auth_or_auth_int() -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = DigestApp(qop=\"not-auth\")\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        with pytest.raises(httpx.ProtocolError):\n            await client.get(url, auth=auth)\n\n\n@pytest.mark.anyio\nasync def test_digest_auth_incorrect_credentials() -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = DigestApp(send_response_after_attempt=2)\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 401\n    assert len(response.history) == 1\n\n\n@pytest.mark.anyio\nasync def test_digest_auth_reuses_challenge() -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = DigestApp()\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response_1 = await client.get(url, auth=auth)\n        response_2 = await client.get(url, auth=auth)\n\n        assert response_1.status_code == 200\n        assert response_2.status_code == 200\n\n        assert len(response_1.history) == 1\n        assert len(response_2.history) == 0\n\n\n@pytest.mark.anyio\nasync def test_digest_auth_resets_nonce_count_after_401() -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = DigestApp()\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response_1 = await client.get(url, auth=auth)\n        assert response_1.status_code == 200\n        assert len(response_1.history) == 1\n\n        first_nonce = parse_keqv_list(\n            response_1.request.headers[\"Authorization\"].split(\", \")\n        )[\"nonce\"]\n        first_nc = parse_keqv_list(\n            response_1.request.headers[\"Authorization\"].split(\", \")\n        )[\"nc\"]\n\n        # with this we now force a 401 on a subsequent (but initial) request\n        app.send_response_after_attempt = 2\n\n        # we expect the client again to try to authenticate,\n        # i.e. the history length must be 1\n        response_2 = await client.get(url, auth=auth)\n        assert response_2.status_code == 200\n        assert len(response_2.history) == 1\n\n        second_nonce = parse_keqv_list(\n            response_2.request.headers[\"Authorization\"].split(\", \")\n        )[\"nonce\"]\n        second_nc = parse_keqv_list(\n            response_2.request.headers[\"Authorization\"].split(\", \")\n        )[\"nc\"]\n\n    assert first_nonce != second_nonce  # ensures that the auth challenge was reset\n    assert (\n        first_nc == second_nc\n    )  # ensures the nonce count is reset when the authentication failed\n\n\n@pytest.mark.parametrize(\n    \"auth_header\",\n    [\n        'Digest realm=\"httpx@example.org\", qop=\"auth\"',  # missing fields\n        'Digest realm=\"httpx@example.org\", qop=\"auth,au',  # malformed fields list\n    ],\n)\n@pytest.mark.anyio\nasync def test_async_digest_auth_raises_protocol_error_on_malformed_header(\n    auth_header: str,\n) -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = App(auth_header=auth_header, status_code=401)\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        with pytest.raises(httpx.ProtocolError):\n            await client.get(url, auth=auth)\n\n\n@pytest.mark.parametrize(\n    \"auth_header\",\n    [\n        'Digest realm=\"httpx@example.org\", qop=\"auth\"',  # missing fields\n        'Digest realm=\"httpx@example.org\", qop=\"auth,au',  # malformed fields list\n    ],\n)\ndef test_sync_digest_auth_raises_protocol_error_on_malformed_header(\n    auth_header: str,\n) -> None:\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = App(auth_header=auth_header, status_code=401)\n\n    with httpx.Client(transport=httpx.MockTransport(app)) as client:\n        with pytest.raises(httpx.ProtocolError):\n            client.get(url, auth=auth)\n\n\n@pytest.mark.anyio\nasync def test_async_auth_history() -> None:\n    \"\"\"\n    Test that intermediate requests sent as part of an authentication flow\n    are recorded in the response history.\n    \"\"\"\n    url = \"https://example.org/\"\n    auth = RepeatAuth(repeat=2)\n    app = App(auth_header=\"abc\")\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": \"Repeat abc.abc\"}\n\n    assert len(response.history) == 2\n    resp1, resp2 = response.history\n    assert resp1.json() == {\"auth\": \"Repeat 0\"}\n    assert resp2.json() == {\"auth\": \"Repeat 1\"}\n\n    assert len(resp2.history) == 1\n    assert resp2.history == [resp1]\n\n    assert len(resp1.history) == 0\n\n\ndef test_sync_auth_history() -> None:\n    \"\"\"\n    Test that intermediate requests sent as part of an authentication flow\n    are recorded in the response history.\n    \"\"\"\n    url = \"https://example.org/\"\n    auth = RepeatAuth(repeat=2)\n    app = App(auth_header=\"abc\")\n\n    with httpx.Client(transport=httpx.MockTransport(app)) as client:\n        response = client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": \"Repeat abc.abc\"}\n\n    assert len(response.history) == 2\n    resp1, resp2 = response.history\n    assert resp1.json() == {\"auth\": \"Repeat 0\"}\n    assert resp2.json() == {\"auth\": \"Repeat 1\"}\n\n    assert len(resp2.history) == 1\n    assert resp2.history == [resp1]\n\n    assert len(resp1.history) == 0\n\n\nclass ConsumeBodyTransport(httpx.MockTransport):\n    async def handle_async_request(self, request: httpx.Request) -> httpx.Response:\n        assert isinstance(request.stream, httpx.AsyncByteStream)\n        [_ async for _ in request.stream]\n        return self.handler(request)  # type: ignore[return-value]\n\n\n@pytest.mark.anyio\nasync def test_digest_auth_unavailable_streaming_body():\n    url = \"https://example.org/\"\n    auth = httpx.DigestAuth(username=\"user\", password=\"password123\")\n    app = DigestApp()\n\n    async def streaming_body() -> typing.AsyncIterator[bytes]:\n        yield b\"Example request body\"  # pragma: no cover\n\n    async with httpx.AsyncClient(transport=ConsumeBodyTransport(app)) as client:\n        with pytest.raises(httpx.StreamConsumed):\n            await client.post(url, content=streaming_body(), auth=auth)\n\n\n@pytest.mark.anyio\nasync def test_async_auth_reads_response_body() -> None:\n    \"\"\"\n    Test that we can read the response body in an auth flow if `requires_response_body`\n    is set.\n    \"\"\"\n    url = \"https://example.org/\"\n    auth = ResponseBodyAuth(\"xyz\")\n    app = App()\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": '{\"auth\":\"xyz\"}'}\n\n\ndef test_sync_auth_reads_response_body() -> None:\n    \"\"\"\n    Test that we can read the response body in an auth flow if `requires_response_body`\n    is set.\n    \"\"\"\n    url = \"https://example.org/\"\n    auth = ResponseBodyAuth(\"xyz\")\n    app = App()\n\n    with httpx.Client(transport=httpx.MockTransport(app)) as client:\n        response = client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": '{\"auth\":\"xyz\"}'}\n\n\n@pytest.mark.anyio\nasync def test_async_auth() -> None:\n    \"\"\"\n    Test that we can use an auth implementation specific to the async case, to\n    support cases that require performing I/O or using concurrency primitives (such\n    as checking a disk-based cache or fetching a token from a remote auth server).\n    \"\"\"\n    url = \"https://example.org/\"\n    auth = SyncOrAsyncAuth()\n    app = App()\n\n    async with httpx.AsyncClient(transport=httpx.MockTransport(app)) as client:\n        response = await client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": \"async-auth\"}\n\n\ndef test_sync_auth() -> None:\n    \"\"\"\n    Test that we can use an auth implementation specific to the sync case.\n    \"\"\"\n    url = \"https://example.org/\"\n    auth = SyncOrAsyncAuth()\n    app = App()\n\n    with httpx.Client(transport=httpx.MockTransport(app)) as client:\n        response = client.get(url, auth=auth)\n\n    assert response.status_code == 200\n    assert response.json() == {\"auth\": \"sync-auth\"}\n"
  },
  {
    "path": "tests/client/test_client.py",
    "content": "from __future__ import annotations\n\nimport typing\nfrom datetime import timedelta\n\nimport chardet\nimport pytest\n\nimport httpx\n\n\ndef autodetect(content):\n    return chardet.detect(content).get(\"encoding\")\n\n\ndef test_get(server):\n    url = server.url\n    with httpx.Client(http2=True) as http:\n        response = http.get(url)\n    assert response.status_code == 200\n    assert response.url == url\n    assert response.content == b\"Hello, world!\"\n    assert response.text == \"Hello, world!\"\n    assert response.http_version == \"HTTP/1.1\"\n    assert response.encoding == \"utf-8\"\n    assert response.request.url == url\n    assert response.headers\n    assert response.is_redirect is False\n    assert repr(response) == \"<Response [200 OK]>\"\n    assert response.elapsed > timedelta(0)\n\n\n@pytest.mark.parametrize(\n    \"url\",\n    [\n        pytest.param(\"invalid://example.org\", id=\"scheme-not-http(s)\"),\n        pytest.param(\"://example.org\", id=\"no-scheme\"),\n        pytest.param(\"http://\", id=\"no-host\"),\n    ],\n)\ndef test_get_invalid_url(server, url):\n    with httpx.Client() as client:\n        with pytest.raises((httpx.UnsupportedProtocol, httpx.LocalProtocolError)):\n            client.get(url)\n\n\ndef test_build_request(server):\n    url = server.url.copy_with(path=\"/echo_headers\")\n    headers = {\"Custom-header\": \"value\"}\n\n    with httpx.Client() as client:\n        request = client.build_request(\"GET\", url)\n        request.headers.update(headers)\n        response = client.send(request)\n\n    assert response.status_code == 200\n    assert response.url == url\n\n    assert response.json()[\"Custom-header\"] == \"value\"\n\n\ndef test_build_post_request(server):\n    url = server.url.copy_with(path=\"/echo_headers\")\n    headers = {\"Custom-header\": \"value\"}\n\n    with httpx.Client() as client:\n        request = client.build_request(\"POST\", url)\n        request.headers.update(headers)\n        response = client.send(request)\n\n    assert response.status_code == 200\n    assert response.url == url\n\n    assert response.json()[\"Content-length\"] == \"0\"\n    assert response.json()[\"Custom-header\"] == \"value\"\n\n\ndef test_post(server):\n    with httpx.Client() as client:\n        response = client.post(server.url, content=b\"Hello, world!\")\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_post_json(server):\n    with httpx.Client() as client:\n        response = client.post(server.url, json={\"text\": \"Hello, world!\"})\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_stream_response(server):\n    with httpx.Client() as client:\n        with client.stream(\"GET\", server.url) as response:\n            content = response.read()\n    assert response.status_code == 200\n    assert content == b\"Hello, world!\"\n\n\ndef test_stream_iterator(server):\n    body = b\"\"\n\n    with httpx.Client() as client:\n        with client.stream(\"GET\", server.url) as response:\n            for chunk in response.iter_bytes():\n                body += chunk\n\n    assert response.status_code == 200\n    assert body == b\"Hello, world!\"\n\n\ndef test_raw_iterator(server):\n    body = b\"\"\n\n    with httpx.Client() as client:\n        with client.stream(\"GET\", server.url) as response:\n            for chunk in response.iter_raw():\n                body += chunk\n\n    assert response.status_code == 200\n    assert body == b\"Hello, world!\"\n\n\ndef test_cannot_stream_async_request(server):\n    async def hello_world() -> typing.AsyncIterator[bytes]:  # pragma: no cover\n        yield b\"Hello, \"\n        yield b\"world!\"\n\n    with httpx.Client() as client:\n        with pytest.raises(RuntimeError):\n            client.post(server.url, content=hello_world())\n\n\ndef test_raise_for_status(server):\n    with httpx.Client() as client:\n        for status_code in (200, 400, 404, 500, 505):\n            response = client.request(\n                \"GET\", server.url.copy_with(path=f\"/status/{status_code}\")\n            )\n            if 400 <= status_code < 600:\n                with pytest.raises(httpx.HTTPStatusError) as exc_info:\n                    response.raise_for_status()\n                assert exc_info.value.response == response\n                assert exc_info.value.request.url.path == f\"/status/{status_code}\"\n            else:\n                assert response.raise_for_status() is response\n\n\ndef test_options(server):\n    with httpx.Client() as client:\n        response = client.options(server.url)\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_head(server):\n    with httpx.Client() as client:\n        response = client.head(server.url)\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_put(server):\n    with httpx.Client() as client:\n        response = client.put(server.url, content=b\"Hello, world!\")\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_patch(server):\n    with httpx.Client() as client:\n        response = client.patch(server.url, content=b\"Hello, world!\")\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_delete(server):\n    with httpx.Client() as client:\n        response = client.delete(server.url)\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_base_url(server):\n    base_url = server.url\n    with httpx.Client(base_url=base_url) as client:\n        response = client.get(\"/\")\n    assert response.status_code == 200\n    assert response.url == base_url\n\n\ndef test_merge_absolute_url():\n    client = httpx.Client(base_url=\"https://www.example.com/\")\n    request = client.build_request(\"GET\", \"http://www.example.com/\")\n    assert request.url == \"http://www.example.com/\"\n\n\ndef test_merge_relative_url():\n    client = httpx.Client(base_url=\"https://www.example.com/\")\n    request = client.build_request(\"GET\", \"/testing/123\")\n    assert request.url == \"https://www.example.com/testing/123\"\n\n\ndef test_merge_relative_url_with_path():\n    client = httpx.Client(base_url=\"https://www.example.com/some/path\")\n    request = client.build_request(\"GET\", \"/testing/123\")\n    assert request.url == \"https://www.example.com/some/path/testing/123\"\n\n\ndef test_merge_relative_url_with_dotted_path():\n    client = httpx.Client(base_url=\"https://www.example.com/some/path\")\n    request = client.build_request(\"GET\", \"../testing/123\")\n    assert request.url == \"https://www.example.com/some/testing/123\"\n\n\ndef test_merge_relative_url_with_path_including_colon():\n    client = httpx.Client(base_url=\"https://www.example.com/some/path\")\n    request = client.build_request(\"GET\", \"/testing:123\")\n    assert request.url == \"https://www.example.com/some/path/testing:123\"\n\n\ndef test_merge_relative_url_with_encoded_slashes():\n    client = httpx.Client(base_url=\"https://www.example.com/\")\n    request = client.build_request(\"GET\", \"/testing%2F123\")\n    assert request.url == \"https://www.example.com/testing%2F123\"\n\n    client = httpx.Client(base_url=\"https://www.example.com/base%2Fpath\")\n    request = client.build_request(\"GET\", \"/testing\")\n    assert request.url == \"https://www.example.com/base%2Fpath/testing\"\n\n\ndef test_context_managed_transport():\n    class Transport(httpx.BaseTransport):\n        def __init__(self) -> None:\n            self.events: list[str] = []\n\n        def close(self):\n            # The base implementation of httpx.BaseTransport just\n            # calls into `.close`, so simple transport cases can just override\n            # this method for any cleanup, where more complex cases\n            # might want to additionally override `__enter__`/`__exit__`.\n            self.events.append(\"transport.close\")\n\n        def __enter__(self):\n            super().__enter__()\n            self.events.append(\"transport.__enter__\")\n\n        def __exit__(self, *args):\n            super().__exit__(*args)\n            self.events.append(\"transport.__exit__\")\n\n    transport = Transport()\n    with httpx.Client(transport=transport):\n        pass\n\n    assert transport.events == [\n        \"transport.__enter__\",\n        \"transport.close\",\n        \"transport.__exit__\",\n    ]\n\n\ndef test_context_managed_transport_and_mount():\n    class Transport(httpx.BaseTransport):\n        def __init__(self, name: str) -> None:\n            self.name: str = name\n            self.events: list[str] = []\n\n        def close(self):\n            # The base implementation of httpx.BaseTransport just\n            # calls into `.close`, so simple transport cases can just override\n            # this method for any cleanup, where more complex cases\n            # might want to additionally override `__enter__`/`__exit__`.\n            self.events.append(f\"{self.name}.close\")\n\n        def __enter__(self):\n            super().__enter__()\n            self.events.append(f\"{self.name}.__enter__\")\n\n        def __exit__(self, *args):\n            super().__exit__(*args)\n            self.events.append(f\"{self.name}.__exit__\")\n\n    transport = Transport(name=\"transport\")\n    mounted = Transport(name=\"mounted\")\n    with httpx.Client(transport=transport, mounts={\"http://www.example.org\": mounted}):\n        pass\n\n    assert transport.events == [\n        \"transport.__enter__\",\n        \"transport.close\",\n        \"transport.__exit__\",\n    ]\n    assert mounted.events == [\n        \"mounted.__enter__\",\n        \"mounted.close\",\n        \"mounted.__exit__\",\n    ]\n\n\ndef hello_world(request):\n    return httpx.Response(200, text=\"Hello, world!\")\n\n\ndef test_client_closed_state_using_implicit_open():\n    client = httpx.Client(transport=httpx.MockTransport(hello_world))\n\n    assert not client.is_closed\n    client.get(\"http://example.com\")\n\n    assert not client.is_closed\n    client.close()\n\n    assert client.is_closed\n\n    # Once we're close we cannot make any more requests.\n    with pytest.raises(RuntimeError):\n        client.get(\"http://example.com\")\n\n    # Once we're closed we cannot reopen the client.\n    with pytest.raises(RuntimeError):\n        with client:\n            pass  # pragma: no cover\n\n\ndef test_client_closed_state_using_with_block():\n    with httpx.Client(transport=httpx.MockTransport(hello_world)) as client:\n        assert not client.is_closed\n        client.get(\"http://example.com\")\n\n    assert client.is_closed\n    with pytest.raises(RuntimeError):\n        client.get(\"http://example.com\")\n\n\ndef echo_raw_headers(request: httpx.Request) -> httpx.Response:\n    data = [\n        (name.decode(\"ascii\"), value.decode(\"ascii\"))\n        for name, value in request.headers.raw\n    ]\n    return httpx.Response(200, json=data)\n\n\ndef test_raw_client_header():\n    \"\"\"\n    Set a header in the Client.\n    \"\"\"\n    url = \"http://example.org/echo_headers\"\n    headers = {\"Example-Header\": \"example-value\"}\n\n    client = httpx.Client(\n        transport=httpx.MockTransport(echo_raw_headers), headers=headers\n    )\n    response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == [\n        [\"Host\", \"example.org\"],\n        [\"Accept\", \"*/*\"],\n        [\"Accept-Encoding\", \"gzip, deflate, br, zstd\"],\n        [\"Connection\", \"keep-alive\"],\n        [\"User-Agent\", f\"python-httpx/{httpx.__version__}\"],\n        [\"Example-Header\", \"example-value\"],\n    ]\n\n\ndef unmounted(request: httpx.Request) -> httpx.Response:\n    data = {\"app\": \"unmounted\"}\n    return httpx.Response(200, json=data)\n\n\ndef mounted(request: httpx.Request) -> httpx.Response:\n    data = {\"app\": \"mounted\"}\n    return httpx.Response(200, json=data)\n\n\ndef test_mounted_transport():\n    transport = httpx.MockTransport(unmounted)\n    mounts = {\"custom://\": httpx.MockTransport(mounted)}\n\n    client = httpx.Client(transport=transport, mounts=mounts)\n\n    response = client.get(\"https://www.example.com\")\n    assert response.status_code == 200\n    assert response.json() == {\"app\": \"unmounted\"}\n\n    response = client.get(\"custom://www.example.com\")\n    assert response.status_code == 200\n    assert response.json() == {\"app\": \"mounted\"}\n\n\ndef test_all_mounted_transport():\n    mounts = {\"all://\": httpx.MockTransport(mounted)}\n\n    client = httpx.Client(mounts=mounts)\n\n    response = client.get(\"https://www.example.com\")\n    assert response.status_code == 200\n    assert response.json() == {\"app\": \"mounted\"}\n\n\ndef test_server_extensions(server):\n    url = server.url.copy_with(path=\"/http_version_2\")\n    with httpx.Client(http2=True) as client:\n        response = client.get(url)\n    assert response.status_code == 200\n    assert response.extensions[\"http_version\"] == b\"HTTP/1.1\"\n\n\ndef test_client_decode_text_using_autodetect():\n    # Ensure that a 'default_encoding=autodetect' on the response allows for\n    # encoding autodetection to be used when no \"Content-Type: text/plain; charset=...\"\n    # info is present.\n    #\n    # Here we have some french text encoded with ISO-8859-1, rather than UTF-8.\n    text = (\n        \"Non-seulement Despréaux ne se trompait pas, mais de tous les écrivains \"\n        \"que la France a produits, sans excepter Voltaire lui-même, imprégné de \"\n        \"l'esprit anglais par son séjour à Londres, c'est incontestablement \"\n        \"Molière ou Poquelin qui reproduit avec l'exactitude la plus vive et la \"\n        \"plus complète le fond du génie français.\"\n    )\n\n    def cp1252_but_no_content_type(request):\n        content = text.encode(\"ISO-8859-1\")\n        return httpx.Response(200, content=content)\n\n    transport = httpx.MockTransport(cp1252_but_no_content_type)\n    with httpx.Client(transport=transport, default_encoding=autodetect) as client:\n        response = client.get(\"http://www.example.com\")\n\n        assert response.status_code == 200\n        assert response.reason_phrase == \"OK\"\n        assert response.encoding == \"ISO-8859-1\"\n        assert response.text == text\n\n\ndef test_client_decode_text_using_explicit_encoding():\n    # Ensure that a 'default_encoding=\"...\"' on the response is used for text decoding\n    # when no \"Content-Type: text/plain; charset=...\"\" info is present.\n    #\n    # Here we have some french text encoded with ISO-8859-1, rather than UTF-8.\n    text = (\n        \"Non-seulement Despréaux ne se trompait pas, mais de tous les écrivains \"\n        \"que la France a produits, sans excepter Voltaire lui-même, imprégné de \"\n        \"l'esprit anglais par son séjour à Londres, c'est incontestablement \"\n        \"Molière ou Poquelin qui reproduit avec l'exactitude la plus vive et la \"\n        \"plus complète le fond du génie français.\"\n    )\n\n    def cp1252_but_no_content_type(request):\n        content = text.encode(\"ISO-8859-1\")\n        return httpx.Response(200, content=content)\n\n    transport = httpx.MockTransport(cp1252_but_no_content_type)\n    with httpx.Client(transport=transport, default_encoding=autodetect) as client:\n        response = client.get(\"http://www.example.com\")\n\n        assert response.status_code == 200\n        assert response.reason_phrase == \"OK\"\n        assert response.encoding == \"ISO-8859-1\"\n        assert response.text == text\n"
  },
  {
    "path": "tests/client/test_cookies.py",
    "content": "from http.cookiejar import Cookie, CookieJar\n\nimport pytest\n\nimport httpx\n\n\ndef get_and_set_cookies(request: httpx.Request) -> httpx.Response:\n    if request.url.path == \"/echo_cookies\":\n        data = {\"cookies\": request.headers.get(\"cookie\")}\n        return httpx.Response(200, json=data)\n    elif request.url.path == \"/set_cookie\":\n        return httpx.Response(200, headers={\"set-cookie\": \"example-name=example-value\"})\n    else:\n        raise NotImplementedError()  # pragma: no cover\n\n\ndef test_set_cookie() -> None:\n    \"\"\"\n    Send a request including a cookie.\n    \"\"\"\n    url = \"http://example.org/echo_cookies\"\n    cookies = {\"example-name\": \"example-value\"}\n\n    client = httpx.Client(\n        cookies=cookies, transport=httpx.MockTransport(get_and_set_cookies)\n    )\n    response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\"cookies\": \"example-name=example-value\"}\n\n\ndef test_set_per_request_cookie_is_deprecated() -> None:\n    \"\"\"\n    Sending a request including a per-request cookie is deprecated.\n    \"\"\"\n    url = \"http://example.org/echo_cookies\"\n    cookies = {\"example-name\": \"example-value\"}\n\n    client = httpx.Client(transport=httpx.MockTransport(get_and_set_cookies))\n    with pytest.warns(DeprecationWarning):\n        response = client.get(url, cookies=cookies)\n\n    assert response.status_code == 200\n    assert response.json() == {\"cookies\": \"example-name=example-value\"}\n\n\ndef test_set_cookie_with_cookiejar() -> None:\n    \"\"\"\n    Send a request including a cookie, using a `CookieJar` instance.\n    \"\"\"\n\n    url = \"http://example.org/echo_cookies\"\n    cookies = CookieJar()\n    cookie = Cookie(\n        version=0,\n        name=\"example-name\",\n        value=\"example-value\",\n        port=None,\n        port_specified=False,\n        domain=\"\",\n        domain_specified=False,\n        domain_initial_dot=False,\n        path=\"/\",\n        path_specified=True,\n        secure=False,\n        expires=None,\n        discard=True,\n        comment=None,\n        comment_url=None,\n        rest={\"HttpOnly\": \"\"},\n        rfc2109=False,\n    )\n    cookies.set_cookie(cookie)\n\n    client = httpx.Client(\n        cookies=cookies, transport=httpx.MockTransport(get_and_set_cookies)\n    )\n    response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\"cookies\": \"example-name=example-value\"}\n\n\ndef test_setting_client_cookies_to_cookiejar() -> None:\n    \"\"\"\n    Send a request including a cookie, using a `CookieJar` instance.\n    \"\"\"\n\n    url = \"http://example.org/echo_cookies\"\n    cookies = CookieJar()\n    cookie = Cookie(\n        version=0,\n        name=\"example-name\",\n        value=\"example-value\",\n        port=None,\n        port_specified=False,\n        domain=\"\",\n        domain_specified=False,\n        domain_initial_dot=False,\n        path=\"/\",\n        path_specified=True,\n        secure=False,\n        expires=None,\n        discard=True,\n        comment=None,\n        comment_url=None,\n        rest={\"HttpOnly\": \"\"},\n        rfc2109=False,\n    )\n    cookies.set_cookie(cookie)\n\n    client = httpx.Client(\n        cookies=cookies, transport=httpx.MockTransport(get_and_set_cookies)\n    )\n    response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\"cookies\": \"example-name=example-value\"}\n\n\ndef test_set_cookie_with_cookies_model() -> None:\n    \"\"\"\n    Send a request including a cookie, using a `Cookies` instance.\n    \"\"\"\n\n    url = \"http://example.org/echo_cookies\"\n    cookies = httpx.Cookies()\n    cookies[\"example-name\"] = \"example-value\"\n\n    client = httpx.Client(transport=httpx.MockTransport(get_and_set_cookies))\n    client.cookies = cookies\n    response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\"cookies\": \"example-name=example-value\"}\n\n\ndef test_get_cookie() -> None:\n    url = \"http://example.org/set_cookie\"\n\n    client = httpx.Client(transport=httpx.MockTransport(get_and_set_cookies))\n    response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.cookies[\"example-name\"] == \"example-value\"\n    assert client.cookies[\"example-name\"] == \"example-value\"\n\n\ndef test_cookie_persistence() -> None:\n    \"\"\"\n    Ensure that Client instances persist cookies between requests.\n    \"\"\"\n    client = httpx.Client(transport=httpx.MockTransport(get_and_set_cookies))\n\n    response = client.get(\"http://example.org/echo_cookies\")\n    assert response.status_code == 200\n    assert response.json() == {\"cookies\": None}\n\n    response = client.get(\"http://example.org/set_cookie\")\n    assert response.status_code == 200\n    assert response.cookies[\"example-name\"] == \"example-value\"\n    assert client.cookies[\"example-name\"] == \"example-value\"\n\n    response = client.get(\"http://example.org/echo_cookies\")\n    assert response.status_code == 200\n    assert response.json() == {\"cookies\": \"example-name=example-value\"}\n"
  },
  {
    "path": "tests/client/test_event_hooks.py",
    "content": "import pytest\n\nimport httpx\n\n\ndef app(request: httpx.Request) -> httpx.Response:\n    if request.url.path == \"/redirect\":\n        return httpx.Response(303, headers={\"server\": \"testserver\", \"location\": \"/\"})\n    elif request.url.path.startswith(\"/status/\"):\n        status_code = int(request.url.path[-3:])\n        return httpx.Response(status_code, headers={\"server\": \"testserver\"})\n\n    return httpx.Response(200, headers={\"server\": \"testserver\"})\n\n\ndef test_event_hooks():\n    events = []\n\n    def on_request(request):\n        events.append({\"event\": \"request\", \"headers\": dict(request.headers)})\n\n    def on_response(response):\n        events.append({\"event\": \"response\", \"headers\": dict(response.headers)})\n\n    event_hooks = {\"request\": [on_request], \"response\": [on_response]}\n\n    with httpx.Client(\n        event_hooks=event_hooks, transport=httpx.MockTransport(app)\n    ) as http:\n        http.get(\"http://127.0.0.1:8000/\", auth=(\"username\", \"password\"))\n\n    assert events == [\n        {\n            \"event\": \"request\",\n            \"headers\": {\n                \"host\": \"127.0.0.1:8000\",\n                \"user-agent\": f\"python-httpx/{httpx.__version__}\",\n                \"accept\": \"*/*\",\n                \"accept-encoding\": \"gzip, deflate, br, zstd\",\n                \"connection\": \"keep-alive\",\n                \"authorization\": \"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\",\n            },\n        },\n        {\n            \"event\": \"response\",\n            \"headers\": {\"server\": \"testserver\"},\n        },\n    ]\n\n\ndef test_event_hooks_raising_exception(server):\n    def raise_on_4xx_5xx(response):\n        response.raise_for_status()\n\n    event_hooks = {\"response\": [raise_on_4xx_5xx]}\n\n    with httpx.Client(\n        event_hooks=event_hooks, transport=httpx.MockTransport(app)\n    ) as http:\n        try:\n            http.get(\"http://127.0.0.1:8000/status/400\")\n        except httpx.HTTPStatusError as exc:\n            assert exc.response.is_closed\n\n\n@pytest.mark.anyio\nasync def test_async_event_hooks():\n    events = []\n\n    async def on_request(request):\n        events.append({\"event\": \"request\", \"headers\": dict(request.headers)})\n\n    async def on_response(response):\n        events.append({\"event\": \"response\", \"headers\": dict(response.headers)})\n\n    event_hooks = {\"request\": [on_request], \"response\": [on_response]}\n\n    async with httpx.AsyncClient(\n        event_hooks=event_hooks, transport=httpx.MockTransport(app)\n    ) as http:\n        await http.get(\"http://127.0.0.1:8000/\", auth=(\"username\", \"password\"))\n\n    assert events == [\n        {\n            \"event\": \"request\",\n            \"headers\": {\n                \"host\": \"127.0.0.1:8000\",\n                \"user-agent\": f\"python-httpx/{httpx.__version__}\",\n                \"accept\": \"*/*\",\n                \"accept-encoding\": \"gzip, deflate, br, zstd\",\n                \"connection\": \"keep-alive\",\n                \"authorization\": \"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\",\n            },\n        },\n        {\n            \"event\": \"response\",\n            \"headers\": {\"server\": \"testserver\"},\n        },\n    ]\n\n\n@pytest.mark.anyio\nasync def test_async_event_hooks_raising_exception():\n    async def raise_on_4xx_5xx(response):\n        response.raise_for_status()\n\n    event_hooks = {\"response\": [raise_on_4xx_5xx]}\n\n    async with httpx.AsyncClient(\n        event_hooks=event_hooks, transport=httpx.MockTransport(app)\n    ) as http:\n        try:\n            await http.get(\"http://127.0.0.1:8000/status/400\")\n        except httpx.HTTPStatusError as exc:\n            assert exc.response.is_closed\n\n\ndef test_event_hooks_with_redirect():\n    \"\"\"\n    A redirect request should trigger additional 'request' and 'response' event hooks.\n    \"\"\"\n\n    events = []\n\n    def on_request(request):\n        events.append({\"event\": \"request\", \"headers\": dict(request.headers)})\n\n    def on_response(response):\n        events.append({\"event\": \"response\", \"headers\": dict(response.headers)})\n\n    event_hooks = {\"request\": [on_request], \"response\": [on_response]}\n\n    with httpx.Client(\n        event_hooks=event_hooks,\n        transport=httpx.MockTransport(app),\n        follow_redirects=True,\n    ) as http:\n        http.get(\"http://127.0.0.1:8000/redirect\", auth=(\"username\", \"password\"))\n\n    assert events == [\n        {\n            \"event\": \"request\",\n            \"headers\": {\n                \"host\": \"127.0.0.1:8000\",\n                \"user-agent\": f\"python-httpx/{httpx.__version__}\",\n                \"accept\": \"*/*\",\n                \"accept-encoding\": \"gzip, deflate, br, zstd\",\n                \"connection\": \"keep-alive\",\n                \"authorization\": \"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\",\n            },\n        },\n        {\n            \"event\": \"response\",\n            \"headers\": {\"location\": \"/\", \"server\": \"testserver\"},\n        },\n        {\n            \"event\": \"request\",\n            \"headers\": {\n                \"host\": \"127.0.0.1:8000\",\n                \"user-agent\": f\"python-httpx/{httpx.__version__}\",\n                \"accept\": \"*/*\",\n                \"accept-encoding\": \"gzip, deflate, br, zstd\",\n                \"connection\": \"keep-alive\",\n                \"authorization\": \"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\",\n            },\n        },\n        {\n            \"event\": \"response\",\n            \"headers\": {\"server\": \"testserver\"},\n        },\n    ]\n\n\n@pytest.mark.anyio\nasync def test_async_event_hooks_with_redirect():\n    \"\"\"\n    A redirect request should trigger additional 'request' and 'response' event hooks.\n    \"\"\"\n\n    events = []\n\n    async def on_request(request):\n        events.append({\"event\": \"request\", \"headers\": dict(request.headers)})\n\n    async def on_response(response):\n        events.append({\"event\": \"response\", \"headers\": dict(response.headers)})\n\n    event_hooks = {\"request\": [on_request], \"response\": [on_response]}\n\n    async with httpx.AsyncClient(\n        event_hooks=event_hooks,\n        transport=httpx.MockTransport(app),\n        follow_redirects=True,\n    ) as http:\n        await http.get(\"http://127.0.0.1:8000/redirect\", auth=(\"username\", \"password\"))\n\n    assert events == [\n        {\n            \"event\": \"request\",\n            \"headers\": {\n                \"host\": \"127.0.0.1:8000\",\n                \"user-agent\": f\"python-httpx/{httpx.__version__}\",\n                \"accept\": \"*/*\",\n                \"accept-encoding\": \"gzip, deflate, br, zstd\",\n                \"connection\": \"keep-alive\",\n                \"authorization\": \"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\",\n            },\n        },\n        {\n            \"event\": \"response\",\n            \"headers\": {\"location\": \"/\", \"server\": \"testserver\"},\n        },\n        {\n            \"event\": \"request\",\n            \"headers\": {\n                \"host\": \"127.0.0.1:8000\",\n                \"user-agent\": f\"python-httpx/{httpx.__version__}\",\n                \"accept\": \"*/*\",\n                \"accept-encoding\": \"gzip, deflate, br, zstd\",\n                \"connection\": \"keep-alive\",\n                \"authorization\": \"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\",\n            },\n        },\n        {\n            \"event\": \"response\",\n            \"headers\": {\"server\": \"testserver\"},\n        },\n    ]\n"
  },
  {
    "path": "tests/client/test_headers.py",
    "content": "#!/usr/bin/env python3\n\nimport pytest\n\nimport httpx\n\n\ndef echo_headers(request: httpx.Request) -> httpx.Response:\n    data = {\"headers\": dict(request.headers)}\n    return httpx.Response(200, json=data)\n\n\ndef echo_repeated_headers_multi_items(request: httpx.Request) -> httpx.Response:\n    data = {\"headers\": list(request.headers.multi_items())}\n    return httpx.Response(200, json=data)\n\n\ndef echo_repeated_headers_items(request: httpx.Request) -> httpx.Response:\n    data = {\"headers\": list(request.headers.items())}\n    return httpx.Response(200, json=data)\n\n\ndef test_client_header():\n    \"\"\"\n    Set a header in the Client.\n    \"\"\"\n    url = \"http://example.org/echo_headers\"\n    headers = {\"Example-Header\": \"example-value\"}\n\n    client = httpx.Client(transport=httpx.MockTransport(echo_headers), headers=headers)\n    response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\n        \"headers\": {\n            \"accept\": \"*/*\",\n            \"accept-encoding\": \"gzip, deflate, br, zstd\",\n            \"connection\": \"keep-alive\",\n            \"example-header\": \"example-value\",\n            \"host\": \"example.org\",\n            \"user-agent\": f\"python-httpx/{httpx.__version__}\",\n        }\n    }\n\n\ndef test_header_merge():\n    url = \"http://example.org/echo_headers\"\n    client_headers = {\"User-Agent\": \"python-myclient/0.2.1\"}\n    request_headers = {\"X-Auth-Token\": \"FooBarBazToken\"}\n    client = httpx.Client(\n        transport=httpx.MockTransport(echo_headers), headers=client_headers\n    )\n    response = client.get(url, headers=request_headers)\n\n    assert response.status_code == 200\n    assert response.json() == {\n        \"headers\": {\n            \"accept\": \"*/*\",\n            \"accept-encoding\": \"gzip, deflate, br, zstd\",\n            \"connection\": \"keep-alive\",\n            \"host\": \"example.org\",\n            \"user-agent\": \"python-myclient/0.2.1\",\n            \"x-auth-token\": \"FooBarBazToken\",\n        }\n    }\n\n\ndef test_header_merge_conflicting_headers():\n    url = \"http://example.org/echo_headers\"\n    client_headers = {\"X-Auth-Token\": \"FooBar\"}\n    request_headers = {\"X-Auth-Token\": \"BazToken\"}\n    client = httpx.Client(\n        transport=httpx.MockTransport(echo_headers), headers=client_headers\n    )\n    response = client.get(url, headers=request_headers)\n\n    assert response.status_code == 200\n    assert response.json() == {\n        \"headers\": {\n            \"accept\": \"*/*\",\n            \"accept-encoding\": \"gzip, deflate, br, zstd\",\n            \"connection\": \"keep-alive\",\n            \"host\": \"example.org\",\n            \"user-agent\": f\"python-httpx/{httpx.__version__}\",\n            \"x-auth-token\": \"BazToken\",\n        }\n    }\n\n\ndef test_header_update():\n    url = \"http://example.org/echo_headers\"\n    client = httpx.Client(transport=httpx.MockTransport(echo_headers))\n    first_response = client.get(url)\n    client.headers.update(\n        {\"User-Agent\": \"python-myclient/0.2.1\", \"Another-Header\": \"AThing\"}\n    )\n    second_response = client.get(url)\n\n    assert first_response.status_code == 200\n    assert first_response.json() == {\n        \"headers\": {\n            \"accept\": \"*/*\",\n            \"accept-encoding\": \"gzip, deflate, br, zstd\",\n            \"connection\": \"keep-alive\",\n            \"host\": \"example.org\",\n            \"user-agent\": f\"python-httpx/{httpx.__version__}\",\n        }\n    }\n\n    assert second_response.status_code == 200\n    assert second_response.json() == {\n        \"headers\": {\n            \"accept\": \"*/*\",\n            \"accept-encoding\": \"gzip, deflate, br, zstd\",\n            \"another-header\": \"AThing\",\n            \"connection\": \"keep-alive\",\n            \"host\": \"example.org\",\n            \"user-agent\": \"python-myclient/0.2.1\",\n        }\n    }\n\n\ndef test_header_repeated_items():\n    url = \"http://example.org/echo_headers\"\n    client = httpx.Client(transport=httpx.MockTransport(echo_repeated_headers_items))\n    response = client.get(url, headers=[(\"x-header\", \"1\"), (\"x-header\", \"2,3\")])\n\n    assert response.status_code == 200\n\n    echoed_headers = response.json()[\"headers\"]\n    # as per RFC 7230, the whitespace after a comma is insignificant\n    # so we split and strip here so that we can do a safe comparison\n    assert [\"x-header\", [\"1\", \"2\", \"3\"]] in [\n        [k, [subv.lstrip() for subv in v.split(\",\")]] for k, v in echoed_headers\n    ]\n\n\ndef test_header_repeated_multi_items():\n    url = \"http://example.org/echo_headers\"\n    client = httpx.Client(\n        transport=httpx.MockTransport(echo_repeated_headers_multi_items)\n    )\n    response = client.get(url, headers=[(\"x-header\", \"1\"), (\"x-header\", \"2,3\")])\n\n    assert response.status_code == 200\n\n    echoed_headers = response.json()[\"headers\"]\n    assert [\"x-header\", \"1\"] in echoed_headers\n    assert [\"x-header\", \"2,3\"] in echoed_headers\n\n\ndef test_remove_default_header():\n    \"\"\"\n    Remove a default header from the Client.\n    \"\"\"\n    url = \"http://example.org/echo_headers\"\n\n    client = httpx.Client(transport=httpx.MockTransport(echo_headers))\n    del client.headers[\"User-Agent\"]\n\n    response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\n        \"headers\": {\n            \"accept\": \"*/*\",\n            \"accept-encoding\": \"gzip, deflate, br, zstd\",\n            \"connection\": \"keep-alive\",\n            \"host\": \"example.org\",\n        }\n    }\n\n\ndef test_header_does_not_exist():\n    headers = httpx.Headers({\"foo\": \"bar\"})\n    with pytest.raises(KeyError):\n        del headers[\"baz\"]\n\n\ndef test_header_with_incorrect_value():\n    with pytest.raises(\n        TypeError,\n        match=f\"Header value must be str or bytes, not {type(None)}\",\n    ):\n        httpx.Headers({\"foo\": None})  # type: ignore\n\n\ndef test_host_with_auth_and_port_in_url():\n    \"\"\"\n    The Host header should only include the hostname, or hostname:port\n    (for non-default ports only). Any userinfo or default port should not\n    be present.\n    \"\"\"\n    url = \"http://username:password@example.org:80/echo_headers\"\n\n    client = httpx.Client(transport=httpx.MockTransport(echo_headers))\n    response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\n        \"headers\": {\n            \"accept\": \"*/*\",\n            \"accept-encoding\": \"gzip, deflate, br, zstd\",\n            \"connection\": \"keep-alive\",\n            \"host\": \"example.org\",\n            \"user-agent\": f\"python-httpx/{httpx.__version__}\",\n            \"authorization\": \"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\",\n        }\n    }\n\n\ndef test_host_with_non_default_port_in_url():\n    \"\"\"\n    If the URL includes a non-default port, then it should be included in\n    the Host header.\n    \"\"\"\n    url = \"http://username:password@example.org:123/echo_headers\"\n\n    client = httpx.Client(transport=httpx.MockTransport(echo_headers))\n    response = client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\n        \"headers\": {\n            \"accept\": \"*/*\",\n            \"accept-encoding\": \"gzip, deflate, br, zstd\",\n            \"connection\": \"keep-alive\",\n            \"host\": \"example.org:123\",\n            \"user-agent\": f\"python-httpx/{httpx.__version__}\",\n            \"authorization\": \"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\",\n        }\n    }\n\n\ndef test_request_auto_headers():\n    request = httpx.Request(\"GET\", \"https://www.example.org/\")\n    assert \"host\" in request.headers\n\n\ndef test_same_origin():\n    origin = httpx.URL(\"https://example.com\")\n    request = httpx.Request(\"GET\", \"HTTPS://EXAMPLE.COM:443\")\n\n    client = httpx.Client()\n    headers = client._redirect_headers(request, origin, \"GET\")\n\n    assert headers[\"Host\"] == request.url.netloc.decode(\"ascii\")\n\n\ndef test_not_same_origin():\n    origin = httpx.URL(\"https://example.com\")\n    request = httpx.Request(\"GET\", \"HTTP://EXAMPLE.COM:80\")\n\n    client = httpx.Client()\n    headers = client._redirect_headers(request, origin, \"GET\")\n\n    assert headers[\"Host\"] == origin.netloc.decode(\"ascii\")\n\n\ndef test_is_https_redirect():\n    url = httpx.URL(\"https://example.com\")\n    request = httpx.Request(\n        \"GET\", \"http://example.com\", headers={\"Authorization\": \"empty\"}\n    )\n\n    client = httpx.Client()\n    headers = client._redirect_headers(request, url, \"GET\")\n\n    assert \"Authorization\" in headers\n\n\ndef test_is_not_https_redirect():\n    url = httpx.URL(\"https://www.example.com\")\n    request = httpx.Request(\n        \"GET\", \"http://example.com\", headers={\"Authorization\": \"empty\"}\n    )\n\n    client = httpx.Client()\n    headers = client._redirect_headers(request, url, \"GET\")\n\n    assert \"Authorization\" not in headers\n\n\ndef test_is_not_https_redirect_if_not_default_ports():\n    url = httpx.URL(\"https://example.com:1337\")\n    request = httpx.Request(\n        \"GET\", \"http://example.com:9999\", headers={\"Authorization\": \"empty\"}\n    )\n\n    client = httpx.Client()\n    headers = client._redirect_headers(request, url, \"GET\")\n\n    assert \"Authorization\" not in headers\n"
  },
  {
    "path": "tests/client/test_properties.py",
    "content": "import httpx\n\n\ndef test_client_base_url():\n    client = httpx.Client()\n    client.base_url = \"https://www.example.org/\"\n    assert isinstance(client.base_url, httpx.URL)\n    assert client.base_url == \"https://www.example.org/\"\n\n\ndef test_client_base_url_without_trailing_slash():\n    client = httpx.Client()\n    client.base_url = \"https://www.example.org/path\"\n    assert isinstance(client.base_url, httpx.URL)\n    assert client.base_url == \"https://www.example.org/path/\"\n\n\ndef test_client_base_url_with_trailing_slash():\n    client = httpx.Client()\n    client.base_url = \"https://www.example.org/path/\"\n    assert isinstance(client.base_url, httpx.URL)\n    assert client.base_url == \"https://www.example.org/path/\"\n\n\ndef test_client_headers():\n    client = httpx.Client()\n    client.headers = {\"a\": \"b\"}\n    assert isinstance(client.headers, httpx.Headers)\n    assert client.headers[\"A\"] == \"b\"\n\n\ndef test_client_cookies():\n    client = httpx.Client()\n    client.cookies = {\"a\": \"b\"}\n    assert isinstance(client.cookies, httpx.Cookies)\n    mycookies = list(client.cookies.jar)\n    assert len(mycookies) == 1\n    assert mycookies[0].name == \"a\" and mycookies[0].value == \"b\"\n\n\ndef test_client_timeout():\n    expected_timeout = 12.0\n    client = httpx.Client()\n\n    client.timeout = expected_timeout\n\n    assert isinstance(client.timeout, httpx.Timeout)\n    assert client.timeout.connect == expected_timeout\n    assert client.timeout.read == expected_timeout\n    assert client.timeout.write == expected_timeout\n    assert client.timeout.pool == expected_timeout\n\n\ndef test_client_event_hooks():\n    def on_request(request):\n        pass  # pragma: no cover\n\n    client = httpx.Client()\n    client.event_hooks = {\"request\": [on_request]}\n    assert client.event_hooks == {\"request\": [on_request], \"response\": []}\n\n\ndef test_client_trust_env():\n    client = httpx.Client()\n    assert client.trust_env\n\n    client = httpx.Client(trust_env=False)\n    assert not client.trust_env\n"
  },
  {
    "path": "tests/client/test_proxies.py",
    "content": "import httpcore\nimport pytest\n\nimport httpx\n\n\ndef url_to_origin(url: str) -> httpcore.URL:\n    \"\"\"\n    Given a URL string, return the origin in the raw tuple format that\n    `httpcore` uses for it's representation.\n    \"\"\"\n    u = httpx.URL(url)\n    return httpcore.URL(scheme=u.raw_scheme, host=u.raw_host, port=u.port, target=\"/\")\n\n\ndef test_socks_proxy():\n    url = httpx.URL(\"http://www.example.com\")\n\n    for proxy in (\"socks5://localhost/\", \"socks5h://localhost/\"):\n        client = httpx.Client(proxy=proxy)\n        transport = client._transport_for_url(url)\n        assert isinstance(transport, httpx.HTTPTransport)\n        assert isinstance(transport._pool, httpcore.SOCKSProxy)\n\n        async_client = httpx.AsyncClient(proxy=proxy)\n        async_transport = async_client._transport_for_url(url)\n        assert isinstance(async_transport, httpx.AsyncHTTPTransport)\n        assert isinstance(async_transport._pool, httpcore.AsyncSOCKSProxy)\n\n\nPROXY_URL = \"http://[::1]\"\n\n\n@pytest.mark.parametrize(\n    [\"url\", \"proxies\", \"expected\"],\n    [\n        (\"http://example.com\", {}, None),\n        (\"http://example.com\", {\"https://\": PROXY_URL}, None),\n        (\"http://example.com\", {\"http://example.net\": PROXY_URL}, None),\n        # Using \"*\" should match any domain name.\n        (\"http://example.com\", {\"http://*\": PROXY_URL}, PROXY_URL),\n        (\"https://example.com\", {\"http://*\": PROXY_URL}, None),\n        # Using \"example.com\" should match example.com, but not www.example.com\n        (\"http://example.com\", {\"http://example.com\": PROXY_URL}, PROXY_URL),\n        (\"http://www.example.com\", {\"http://example.com\": PROXY_URL}, None),\n        # Using \"*.example.com\" should match www.example.com, but not example.com\n        (\"http://example.com\", {\"http://*.example.com\": PROXY_URL}, None),\n        (\"http://www.example.com\", {\"http://*.example.com\": PROXY_URL}, PROXY_URL),\n        # Using \"*example.com\" should match example.com and www.example.com\n        (\"http://example.com\", {\"http://*example.com\": PROXY_URL}, PROXY_URL),\n        (\"http://www.example.com\", {\"http://*example.com\": PROXY_URL}, PROXY_URL),\n        (\"http://wwwexample.com\", {\"http://*example.com\": PROXY_URL}, None),\n        # ...\n        (\"http://example.com:443\", {\"http://example.com\": PROXY_URL}, PROXY_URL),\n        (\"http://example.com\", {\"all://\": PROXY_URL}, PROXY_URL),\n        (\"http://example.com\", {\"http://\": PROXY_URL}, PROXY_URL),\n        (\"http://example.com\", {\"all://example.com\": PROXY_URL}, PROXY_URL),\n        (\"http://example.com\", {\"http://example.com\": PROXY_URL}, PROXY_URL),\n        (\"http://example.com\", {\"http://example.com:80\": PROXY_URL}, PROXY_URL),\n        (\"http://example.com:8080\", {\"http://example.com:8080\": PROXY_URL}, PROXY_URL),\n        (\"http://example.com:8080\", {\"http://example.com\": PROXY_URL}, PROXY_URL),\n        (\n            \"http://example.com\",\n            {\n                \"all://\": PROXY_URL + \":1\",\n                \"http://\": PROXY_URL + \":2\",\n                \"all://example.com\": PROXY_URL + \":3\",\n                \"http://example.com\": PROXY_URL + \":4\",\n            },\n            PROXY_URL + \":4\",\n        ),\n        (\n            \"http://example.com\",\n            {\n                \"all://\": PROXY_URL + \":1\",\n                \"http://\": PROXY_URL + \":2\",\n                \"all://example.com\": PROXY_URL + \":3\",\n            },\n            PROXY_URL + \":3\",\n        ),\n        (\n            \"http://example.com\",\n            {\"all://\": PROXY_URL + \":1\", \"http://\": PROXY_URL + \":2\"},\n            PROXY_URL + \":2\",\n        ),\n    ],\n)\ndef test_transport_for_request(url, proxies, expected):\n    mounts = {key: httpx.HTTPTransport(proxy=value) for key, value in proxies.items()}\n    client = httpx.Client(mounts=mounts)\n\n    transport = client._transport_for_url(httpx.URL(url))\n\n    if expected is None:\n        assert transport is client._transport\n    else:\n        assert isinstance(transport, httpx.HTTPTransport)\n        assert isinstance(transport._pool, httpcore.HTTPProxy)\n        assert transport._pool._proxy_url == url_to_origin(expected)\n\n\n@pytest.mark.anyio\n@pytest.mark.network\nasync def test_async_proxy_close():\n    try:\n        transport = httpx.AsyncHTTPTransport(proxy=PROXY_URL)\n        client = httpx.AsyncClient(mounts={\"https://\": transport})\n        await client.get(\"http://example.com\")\n    finally:\n        await client.aclose()\n\n\n@pytest.mark.network\ndef test_sync_proxy_close():\n    try:\n        transport = httpx.HTTPTransport(proxy=PROXY_URL)\n        client = httpx.Client(mounts={\"https://\": transport})\n        client.get(\"http://example.com\")\n    finally:\n        client.close()\n\n\ndef test_unsupported_proxy_scheme():\n    with pytest.raises(ValueError):\n        httpx.Client(proxy=\"ftp://127.0.0.1\")\n\n\n@pytest.mark.parametrize(\n    [\"url\", \"env\", \"expected\"],\n    [\n        (\"http://google.com\", {}, None),\n        (\n            \"http://google.com\",\n            {\"HTTP_PROXY\": \"http://example.com\"},\n            \"http://example.com\",\n        ),\n        # Auto prepend http scheme\n        (\"http://google.com\", {\"HTTP_PROXY\": \"example.com\"}, \"http://example.com\"),\n        (\n            \"http://google.com\",\n            {\"HTTP_PROXY\": \"http://example.com\", \"NO_PROXY\": \"google.com\"},\n            None,\n        ),\n        # Everything proxied when NO_PROXY is empty/unset\n        (\n            \"http://127.0.0.1\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"\"},\n            \"http://localhost:123\",\n        ),\n        # Not proxied if NO_PROXY matches URL.\n        (\n            \"http://127.0.0.1\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"127.0.0.1\"},\n            None,\n        ),\n        # Proxied if NO_PROXY scheme does not match URL.\n        (\n            \"http://127.0.0.1\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"https://127.0.0.1\"},\n            \"http://localhost:123\",\n        ),\n        # Proxied if NO_PROXY scheme does not match host.\n        (\n            \"http://127.0.0.1\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"1.1.1.1\"},\n            \"http://localhost:123\",\n        ),\n        # Not proxied if NO_PROXY matches host domain suffix.\n        (\n            \"http://courses.mit.edu\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"mit.edu\"},\n            None,\n        ),\n        # Proxied even though NO_PROXY matches host domain *prefix*.\n        (\n            \"https://mit.edu.info\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"mit.edu\"},\n            \"http://localhost:123\",\n        ),\n        # Not proxied if one item in NO_PROXY case matches host domain suffix.\n        (\n            \"https://mit.edu.info\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"mit.edu,edu.info\"},\n            None,\n        ),\n        # Not proxied if one item in NO_PROXY case matches host domain suffix.\n        # May include whitespace.\n        (\n            \"https://mit.edu.info\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"mit.edu, edu.info\"},\n            None,\n        ),\n        # Proxied if no items in NO_PROXY match.\n        (\n            \"https://mit.edu.info\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"mit.edu,mit.info\"},\n            \"http://localhost:123\",\n        ),\n        # Proxied if NO_PROXY domain doesn't match.\n        (\n            \"https://foo.example.com\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"www.example.com\"},\n            \"http://localhost:123\",\n        ),\n        # Not proxied for subdomains matching NO_PROXY, with a leading \".\".\n        (\n            \"https://www.example1.com\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \".example1.com\"},\n            None,\n        ),\n        # Proxied, because NO_PROXY subdomains only match if \".\" separated.\n        (\n            \"https://www.example2.com\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"ample2.com\"},\n            \"http://localhost:123\",\n        ),\n        # No requests are proxied if NO_PROXY=\"*\" is set.\n        (\n            \"https://www.example3.com\",\n            {\"ALL_PROXY\": \"http://localhost:123\", \"NO_PROXY\": \"*\"},\n            None,\n        ),\n    ],\n)\n@pytest.mark.parametrize(\"client_class\", [httpx.Client, httpx.AsyncClient])\ndef test_proxies_environ(monkeypatch, client_class, url, env, expected):\n    for name, value in env.items():\n        monkeypatch.setenv(name, value)\n\n    client = client_class()\n    transport = client._transport_for_url(httpx.URL(url))\n\n    if expected is None:\n        assert transport == client._transport\n    else:\n        assert transport._pool._proxy_url == url_to_origin(expected)\n\n\n@pytest.mark.parametrize(\n    [\"proxies\", \"is_valid\"],\n    [\n        ({\"http\": \"http://127.0.0.1\"}, False),\n        ({\"https\": \"http://127.0.0.1\"}, False),\n        ({\"all\": \"http://127.0.0.1\"}, False),\n        ({\"http://\": \"http://127.0.0.1\"}, True),\n        ({\"https://\": \"http://127.0.0.1\"}, True),\n        ({\"all://\": \"http://127.0.0.1\"}, True),\n    ],\n)\ndef test_for_deprecated_proxy_params(proxies, is_valid):\n    mounts = {key: httpx.HTTPTransport(proxy=value) for key, value in proxies.items()}\n\n    if not is_valid:\n        with pytest.raises(ValueError):\n            httpx.Client(mounts=mounts)\n    else:\n        httpx.Client(mounts=mounts)\n\n\ndef test_proxy_with_mounts():\n    proxy_transport = httpx.HTTPTransport(proxy=\"http://127.0.0.1\")\n    client = httpx.Client(mounts={\"http://\": proxy_transport})\n\n    transport = client._transport_for_url(httpx.URL(\"http://example.com\"))\n    assert transport == proxy_transport\n"
  },
  {
    "path": "tests/client/test_queryparams.py",
    "content": "import httpx\n\n\ndef hello_world(request: httpx.Request) -> httpx.Response:\n    return httpx.Response(200, text=\"Hello, world\")\n\n\ndef test_client_queryparams():\n    client = httpx.Client(params={\"a\": \"b\"})\n    assert isinstance(client.params, httpx.QueryParams)\n    assert client.params[\"a\"] == \"b\"\n\n\ndef test_client_queryparams_string():\n    client = httpx.Client(params=\"a=b\")\n    assert isinstance(client.params, httpx.QueryParams)\n    assert client.params[\"a\"] == \"b\"\n\n    client = httpx.Client()\n    client.params = \"a=b\"\n    assert isinstance(client.params, httpx.QueryParams)\n    assert client.params[\"a\"] == \"b\"\n\n\ndef test_client_queryparams_echo():\n    url = \"http://example.org/echo_queryparams\"\n    client_queryparams = \"first=str\"\n    request_queryparams = {\"second\": \"dict\"}\n    client = httpx.Client(\n        transport=httpx.MockTransport(hello_world), params=client_queryparams\n    )\n    response = client.get(url, params=request_queryparams)\n\n    assert response.status_code == 200\n    assert response.url == \"http://example.org/echo_queryparams?first=str&second=dict\"\n"
  },
  {
    "path": "tests/client/test_redirects.py",
    "content": "import typing\n\nimport pytest\n\nimport httpx\n\n\ndef redirects(request: httpx.Request) -> httpx.Response:\n    if request.url.scheme not in (\"http\", \"https\"):\n        raise httpx.UnsupportedProtocol(f\"Scheme {request.url.scheme!r} not supported.\")\n\n    if request.url.path == \"/redirect_301\":\n        status_code = httpx.codes.MOVED_PERMANENTLY\n        content = b\"<a href='https://example.org/'>here</a>\"\n        headers = {\"location\": \"https://example.org/\"}\n        return httpx.Response(status_code, headers=headers, content=content)\n\n    elif request.url.path == \"/redirect_302\":\n        status_code = httpx.codes.FOUND\n        headers = {\"location\": \"https://example.org/\"}\n        return httpx.Response(status_code, headers=headers)\n\n    elif request.url.path == \"/redirect_303\":\n        status_code = httpx.codes.SEE_OTHER\n        headers = {\"location\": \"https://example.org/\"}\n        return httpx.Response(status_code, headers=headers)\n\n    elif request.url.path == \"/relative_redirect\":\n        status_code = httpx.codes.SEE_OTHER\n        headers = {\"location\": \"/\"}\n        return httpx.Response(status_code, headers=headers)\n\n    elif request.url.path == \"/malformed_redirect\":\n        status_code = httpx.codes.SEE_OTHER\n        headers = {\"location\": \"https://:443/\"}\n        return httpx.Response(status_code, headers=headers)\n\n    elif request.url.path == \"/invalid_redirect\":\n        status_code = httpx.codes.SEE_OTHER\n        raw_headers = [(b\"location\", \"https://😇/\".encode(\"utf-8\"))]\n        return httpx.Response(status_code, headers=raw_headers)\n\n    elif request.url.path == \"/no_scheme_redirect\":\n        status_code = httpx.codes.SEE_OTHER\n        headers = {\"location\": \"//example.org/\"}\n        return httpx.Response(status_code, headers=headers)\n\n    elif request.url.path == \"/multiple_redirects\":\n        params = httpx.QueryParams(request.url.query)\n        count = int(params.get(\"count\", \"0\"))\n        redirect_count = count - 1\n        status_code = httpx.codes.SEE_OTHER if count else httpx.codes.OK\n        if count:\n            location = \"/multiple_redirects\"\n            if redirect_count:\n                location += f\"?count={redirect_count}\"\n            headers = {\"location\": location}\n        else:\n            headers = {}\n        return httpx.Response(status_code, headers=headers)\n\n    if request.url.path == \"/redirect_loop\":\n        status_code = httpx.codes.SEE_OTHER\n        headers = {\"location\": \"/redirect_loop\"}\n        return httpx.Response(status_code, headers=headers)\n\n    elif request.url.path == \"/cross_domain\":\n        status_code = httpx.codes.SEE_OTHER\n        headers = {\"location\": \"https://example.org/cross_domain_target\"}\n        return httpx.Response(status_code, headers=headers)\n\n    elif request.url.path == \"/cross_domain_target\":\n        status_code = httpx.codes.OK\n        data = {\n            \"body\": request.content.decode(\"ascii\"),\n            \"headers\": dict(request.headers),\n        }\n        return httpx.Response(status_code, json=data)\n\n    elif request.url.path == \"/redirect_body\":\n        status_code = httpx.codes.PERMANENT_REDIRECT\n        headers = {\"location\": \"/redirect_body_target\"}\n        return httpx.Response(status_code, headers=headers)\n\n    elif request.url.path == \"/redirect_no_body\":\n        status_code = httpx.codes.SEE_OTHER\n        headers = {\"location\": \"/redirect_body_target\"}\n        return httpx.Response(status_code, headers=headers)\n\n    elif request.url.path == \"/redirect_body_target\":\n        data = {\n            \"body\": request.content.decode(\"ascii\"),\n            \"headers\": dict(request.headers),\n        }\n        return httpx.Response(200, json=data)\n\n    elif request.url.path == \"/cross_subdomain\":\n        if request.headers[\"Host\"] != \"www.example.org\":\n            status_code = httpx.codes.PERMANENT_REDIRECT\n            headers = {\"location\": \"https://www.example.org/cross_subdomain\"}\n            return httpx.Response(status_code, headers=headers)\n        else:\n            return httpx.Response(200, text=\"Hello, world!\")\n\n    elif request.url.path == \"/redirect_custom_scheme\":\n        status_code = httpx.codes.MOVED_PERMANENTLY\n        headers = {\"location\": \"market://details?id=42\"}\n        return httpx.Response(status_code, headers=headers)\n\n    if request.method == \"HEAD\":\n        return httpx.Response(200)\n\n    return httpx.Response(200, html=\"<html><body>Hello, world!</body></html>\")\n\n\ndef test_redirect_301():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    response = client.post(\"https://example.org/redirect_301\", follow_redirects=True)\n    assert response.status_code == httpx.codes.OK\n    assert response.url == \"https://example.org/\"\n    assert len(response.history) == 1\n\n\ndef test_redirect_302():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    response = client.post(\"https://example.org/redirect_302\", follow_redirects=True)\n    assert response.status_code == httpx.codes.OK\n    assert response.url == \"https://example.org/\"\n    assert len(response.history) == 1\n\n\ndef test_redirect_303():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    response = client.get(\"https://example.org/redirect_303\", follow_redirects=True)\n    assert response.status_code == httpx.codes.OK\n    assert response.url == \"https://example.org/\"\n    assert len(response.history) == 1\n\n\ndef test_next_request():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    request = client.build_request(\"POST\", \"https://example.org/redirect_303\")\n    response = client.send(request, follow_redirects=False)\n    assert response.status_code == httpx.codes.SEE_OTHER\n    assert response.url == \"https://example.org/redirect_303\"\n    assert response.next_request is not None\n\n    response = client.send(response.next_request, follow_redirects=False)\n    assert response.status_code == httpx.codes.OK\n    assert response.url == \"https://example.org/\"\n    assert response.next_request is None\n\n\n@pytest.mark.anyio\nasync def test_async_next_request():\n    async with httpx.AsyncClient(transport=httpx.MockTransport(redirects)) as client:\n        request = client.build_request(\"POST\", \"https://example.org/redirect_303\")\n        response = await client.send(request, follow_redirects=False)\n        assert response.status_code == httpx.codes.SEE_OTHER\n        assert response.url == \"https://example.org/redirect_303\"\n        assert response.next_request is not None\n\n        response = await client.send(response.next_request, follow_redirects=False)\n        assert response.status_code == httpx.codes.OK\n        assert response.url == \"https://example.org/\"\n        assert response.next_request is None\n\n\ndef test_head_redirect():\n    \"\"\"\n    Contrary to Requests, redirects remain enabled by default for HEAD requests.\n    \"\"\"\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    response = client.head(\"https://example.org/redirect_302\", follow_redirects=True)\n    assert response.status_code == httpx.codes.OK\n    assert response.url == \"https://example.org/\"\n    assert response.request.method == \"HEAD\"\n    assert len(response.history) == 1\n    assert response.text == \"\"\n\n\ndef test_relative_redirect():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    response = client.get(\n        \"https://example.org/relative_redirect\", follow_redirects=True\n    )\n    assert response.status_code == httpx.codes.OK\n    assert response.url == \"https://example.org/\"\n    assert len(response.history) == 1\n\n\ndef test_malformed_redirect():\n    # https://github.com/encode/httpx/issues/771\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    response = client.get(\n        \"http://example.org/malformed_redirect\", follow_redirects=True\n    )\n    assert response.status_code == httpx.codes.OK\n    assert response.url == \"https://example.org:443/\"\n    assert len(response.history) == 1\n\n\ndef test_invalid_redirect():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    with pytest.raises(httpx.RemoteProtocolError):\n        client.get(\"http://example.org/invalid_redirect\", follow_redirects=True)\n\n\ndef test_no_scheme_redirect():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    response = client.get(\n        \"https://example.org/no_scheme_redirect\", follow_redirects=True\n    )\n    assert response.status_code == httpx.codes.OK\n    assert response.url == \"https://example.org/\"\n    assert len(response.history) == 1\n\n\ndef test_fragment_redirect():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    response = client.get(\n        \"https://example.org/relative_redirect#fragment\", follow_redirects=True\n    )\n    assert response.status_code == httpx.codes.OK\n    assert response.url == \"https://example.org/#fragment\"\n    assert len(response.history) == 1\n\n\ndef test_multiple_redirects():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    response = client.get(\n        \"https://example.org/multiple_redirects?count=20\", follow_redirects=True\n    )\n    assert response.status_code == httpx.codes.OK\n    assert response.url == \"https://example.org/multiple_redirects\"\n    assert len(response.history) == 20\n    assert response.history[0].url == \"https://example.org/multiple_redirects?count=20\"\n    assert response.history[1].url == \"https://example.org/multiple_redirects?count=19\"\n    assert len(response.history[0].history) == 0\n    assert len(response.history[1].history) == 1\n\n\n@pytest.mark.anyio\nasync def test_async_too_many_redirects():\n    async with httpx.AsyncClient(transport=httpx.MockTransport(redirects)) as client:\n        with pytest.raises(httpx.TooManyRedirects):\n            await client.get(\n                \"https://example.org/multiple_redirects?count=21\", follow_redirects=True\n            )\n\n\ndef test_sync_too_many_redirects():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    with pytest.raises(httpx.TooManyRedirects):\n        client.get(\n            \"https://example.org/multiple_redirects?count=21\", follow_redirects=True\n        )\n\n\ndef test_redirect_loop():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    with pytest.raises(httpx.TooManyRedirects):\n        client.get(\"https://example.org/redirect_loop\", follow_redirects=True)\n\n\ndef test_cross_domain_redirect_with_auth_header():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    url = \"https://example.com/cross_domain\"\n    headers = {\"Authorization\": \"abc\"}\n    response = client.get(url, headers=headers, follow_redirects=True)\n    assert response.url == \"https://example.org/cross_domain_target\"\n    assert \"authorization\" not in response.json()[\"headers\"]\n\n\ndef test_cross_domain_https_redirect_with_auth_header():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    url = \"http://example.com/cross_domain\"\n    headers = {\"Authorization\": \"abc\"}\n    response = client.get(url, headers=headers, follow_redirects=True)\n    assert response.url == \"https://example.org/cross_domain_target\"\n    assert \"authorization\" not in response.json()[\"headers\"]\n\n\ndef test_cross_domain_redirect_with_auth():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    url = \"https://example.com/cross_domain\"\n    response = client.get(url, auth=(\"user\", \"pass\"), follow_redirects=True)\n    assert response.url == \"https://example.org/cross_domain_target\"\n    assert \"authorization\" not in response.json()[\"headers\"]\n\n\ndef test_same_domain_redirect():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    url = \"https://example.org/cross_domain\"\n    headers = {\"Authorization\": \"abc\"}\n    response = client.get(url, headers=headers, follow_redirects=True)\n    assert response.url == \"https://example.org/cross_domain_target\"\n    assert response.json()[\"headers\"][\"authorization\"] == \"abc\"\n\n\ndef test_same_domain_https_redirect_with_auth_header():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    url = \"http://example.org/cross_domain\"\n    headers = {\"Authorization\": \"abc\"}\n    response = client.get(url, headers=headers, follow_redirects=True)\n    assert response.url == \"https://example.org/cross_domain_target\"\n    assert response.json()[\"headers\"][\"authorization\"] == \"abc\"\n\n\ndef test_body_redirect():\n    \"\"\"\n    A 308 redirect should preserve the request body.\n    \"\"\"\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    url = \"https://example.org/redirect_body\"\n    content = b\"Example request body\"\n    response = client.post(url, content=content, follow_redirects=True)\n    assert response.url == \"https://example.org/redirect_body_target\"\n    assert response.json()[\"body\"] == \"Example request body\"\n    assert \"content-length\" in response.json()[\"headers\"]\n\n\ndef test_no_body_redirect():\n    \"\"\"\n    A 303 redirect should remove the request body.\n    \"\"\"\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    url = \"https://example.org/redirect_no_body\"\n    content = b\"Example request body\"\n    response = client.post(url, content=content, follow_redirects=True)\n    assert response.url == \"https://example.org/redirect_body_target\"\n    assert response.json()[\"body\"] == \"\"\n    assert \"content-length\" not in response.json()[\"headers\"]\n\n\ndef test_can_stream_if_no_redirect():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    url = \"https://example.org/redirect_301\"\n    with client.stream(\"GET\", url, follow_redirects=False) as response:\n        pass\n    assert response.status_code == httpx.codes.MOVED_PERMANENTLY\n    assert response.headers[\"location\"] == \"https://example.org/\"\n\n\nclass ConsumeBodyTransport(httpx.MockTransport):\n    def handle_request(self, request: httpx.Request) -> httpx.Response:\n        assert isinstance(request.stream, httpx.SyncByteStream)\n        list(request.stream)\n        return self.handler(request)  # type: ignore[return-value]\n\n\ndef test_cannot_redirect_streaming_body():\n    client = httpx.Client(transport=ConsumeBodyTransport(redirects))\n    url = \"https://example.org/redirect_body\"\n\n    def streaming_body() -> typing.Iterator[bytes]:\n        yield b\"Example request body\"  # pragma: no cover\n\n    with pytest.raises(httpx.StreamConsumed):\n        client.post(url, content=streaming_body(), follow_redirects=True)\n\n\ndef test_cross_subdomain_redirect():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    url = \"https://example.com/cross_subdomain\"\n    response = client.get(url, follow_redirects=True)\n    assert response.url == \"https://www.example.org/cross_subdomain\"\n\n\ndef cookie_sessions(request: httpx.Request) -> httpx.Response:\n    if request.url.path == \"/\":\n        cookie = request.headers.get(\"Cookie\")\n        if cookie is not None:\n            content = b\"Logged in\"\n        else:\n            content = b\"Not logged in\"\n        return httpx.Response(200, content=content)\n\n    elif request.url.path == \"/login\":\n        status_code = httpx.codes.SEE_OTHER\n        headers = {\n            \"location\": \"/\",\n            \"set-cookie\": (\n                \"session=eyJ1c2VybmFtZSI6ICJ0b21; path=/; Max-Age=1209600; \"\n                \"httponly; samesite=lax\"\n            ),\n        }\n        return httpx.Response(status_code, headers=headers)\n\n    else:\n        assert request.url.path == \"/logout\"\n        status_code = httpx.codes.SEE_OTHER\n        headers = {\n            \"location\": \"/\",\n            \"set-cookie\": (\n                \"session=null; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; \"\n                \"httponly; samesite=lax\"\n            ),\n        }\n        return httpx.Response(status_code, headers=headers)\n\n\ndef test_redirect_cookie_behavior():\n    client = httpx.Client(\n        transport=httpx.MockTransport(cookie_sessions), follow_redirects=True\n    )\n\n    # The client is not logged in.\n    response = client.get(\"https://example.com/\")\n    assert response.url == \"https://example.com/\"\n    assert response.text == \"Not logged in\"\n\n    # Login redirects to the homepage, setting a session cookie.\n    response = client.post(\"https://example.com/login\")\n    assert response.url == \"https://example.com/\"\n    assert response.text == \"Logged in\"\n\n    # The client is logged in.\n    response = client.get(\"https://example.com/\")\n    assert response.url == \"https://example.com/\"\n    assert response.text == \"Logged in\"\n\n    # Logout redirects to the homepage, expiring the session cookie.\n    response = client.post(\"https://example.com/logout\")\n    assert response.url == \"https://example.com/\"\n    assert response.text == \"Not logged in\"\n\n    # The client is not logged in.\n    response = client.get(\"https://example.com/\")\n    assert response.url == \"https://example.com/\"\n    assert response.text == \"Not logged in\"\n\n\ndef test_redirect_custom_scheme():\n    client = httpx.Client(transport=httpx.MockTransport(redirects))\n    with pytest.raises(httpx.UnsupportedProtocol) as e:\n        client.post(\"https://example.org/redirect_custom_scheme\", follow_redirects=True)\n    assert str(e.value) == \"Scheme 'market' not supported.\"\n\n\n@pytest.mark.anyio\nasync def test_async_invalid_redirect():\n    async with httpx.AsyncClient(transport=httpx.MockTransport(redirects)) as client:\n        with pytest.raises(httpx.RemoteProtocolError):\n            await client.get(\n                \"http://example.org/invalid_redirect\", follow_redirects=True\n            )\n"
  },
  {
    "path": "tests/common.py",
    "content": "import pathlib\n\nTESTS_DIR = pathlib.Path(__file__).parent\nFIXTURES_DIR = TESTS_DIR / \"fixtures\"\n"
  },
  {
    "path": "tests/concurrency.py",
    "content": "\"\"\"\nAsync environment-agnostic concurrency utilities that are only used in tests.\n\"\"\"\n\nimport asyncio\n\nimport sniffio\nimport trio\n\n\nasync def sleep(seconds: float) -> None:\n    if sniffio.current_async_library() == \"trio\":\n        await trio.sleep(seconds)  # pragma: no cover\n    else:\n        await asyncio.sleep(seconds)\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "import asyncio\nimport json\nimport os\nimport threading\nimport time\nimport typing\n\nimport pytest\nimport trustme\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives.serialization import (\n    BestAvailableEncryption,\n    Encoding,\n    PrivateFormat,\n    load_pem_private_key,\n)\nfrom uvicorn.config import Config\nfrom uvicorn.server import Server\n\nimport httpx\nfrom tests.concurrency import sleep\n\nENVIRONMENT_VARIABLES = {\n    \"SSL_CERT_FILE\",\n    \"SSL_CERT_DIR\",\n    \"HTTP_PROXY\",\n    \"HTTPS_PROXY\",\n    \"ALL_PROXY\",\n    \"NO_PROXY\",\n    \"SSLKEYLOGFILE\",\n}\n\n\n@pytest.fixture(scope=\"function\", autouse=True)\ndef clean_environ():\n    \"\"\"Keeps os.environ clean for every test without having to mock os.environ\"\"\"\n    original_environ = os.environ.copy()\n    os.environ.clear()\n    os.environ.update(\n        {\n            k: v\n            for k, v in original_environ.items()\n            if k not in ENVIRONMENT_VARIABLES and k.lower() not in ENVIRONMENT_VARIABLES\n        }\n    )\n    yield\n    os.environ.clear()\n    os.environ.update(original_environ)\n\n\nMessage = typing.Dict[str, typing.Any]\nReceive = typing.Callable[[], typing.Awaitable[Message]]\nSend = typing.Callable[\n    [typing.Dict[str, typing.Any]], typing.Coroutine[None, None, None]\n]\nScope = typing.Dict[str, typing.Any]\n\n\nasync def app(scope: Scope, receive: Receive, send: Send) -> None:\n    assert scope[\"type\"] == \"http\"\n    if scope[\"path\"].startswith(\"/slow_response\"):\n        await slow_response(scope, receive, send)\n    elif scope[\"path\"].startswith(\"/status\"):\n        await status_code(scope, receive, send)\n    elif scope[\"path\"].startswith(\"/echo_body\"):\n        await echo_body(scope, receive, send)\n    elif scope[\"path\"].startswith(\"/echo_binary\"):\n        await echo_binary(scope, receive, send)\n    elif scope[\"path\"].startswith(\"/echo_headers\"):\n        await echo_headers(scope, receive, send)\n    elif scope[\"path\"].startswith(\"/redirect_301\"):\n        await redirect_301(scope, receive, send)\n    elif scope[\"path\"].startswith(\"/json\"):\n        await hello_world_json(scope, receive, send)\n    else:\n        await hello_world(scope, receive, send)\n\n\nasync def hello_world(scope: Scope, receive: Receive, send: Send) -> None:\n    await send(\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 200,\n            \"headers\": [[b\"content-type\", b\"text/plain\"]],\n        }\n    )\n    await send({\"type\": \"http.response.body\", \"body\": b\"Hello, world!\"})\n\n\nasync def hello_world_json(scope: Scope, receive: Receive, send: Send) -> None:\n    await send(\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 200,\n            \"headers\": [[b\"content-type\", b\"application/json\"]],\n        }\n    )\n    await send({\"type\": \"http.response.body\", \"body\": b'{\"Hello\": \"world!\"}'})\n\n\nasync def slow_response(scope: Scope, receive: Receive, send: Send) -> None:\n    await send(\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 200,\n            \"headers\": [[b\"content-type\", b\"text/plain\"]],\n        }\n    )\n    await sleep(1.0)  # Allow triggering a read timeout.\n    await send({\"type\": \"http.response.body\", \"body\": b\"Hello, world!\"})\n\n\nasync def status_code(scope: Scope, receive: Receive, send: Send) -> None:\n    status_code = int(scope[\"path\"].replace(\"/status/\", \"\"))\n    await send(\n        {\n            \"type\": \"http.response.start\",\n            \"status\": status_code,\n            \"headers\": [[b\"content-type\", b\"text/plain\"]],\n        }\n    )\n    await send({\"type\": \"http.response.body\", \"body\": b\"Hello, world!\"})\n\n\nasync def echo_body(scope: Scope, receive: Receive, send: Send) -> None:\n    body = b\"\"\n    more_body = True\n\n    while more_body:\n        message = await receive()\n        body += message.get(\"body\", b\"\")\n        more_body = message.get(\"more_body\", False)\n\n    await send(\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 200,\n            \"headers\": [[b\"content-type\", b\"text/plain\"]],\n        }\n    )\n    await send({\"type\": \"http.response.body\", \"body\": body})\n\n\nasync def echo_binary(scope: Scope, receive: Receive, send: Send) -> None:\n    body = b\"\"\n    more_body = True\n\n    while more_body:\n        message = await receive()\n        body += message.get(\"body\", b\"\")\n        more_body = message.get(\"more_body\", False)\n\n    await send(\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 200,\n            \"headers\": [[b\"content-type\", b\"application/octet-stream\"]],\n        }\n    )\n    await send({\"type\": \"http.response.body\", \"body\": body})\n\n\nasync def echo_headers(scope: Scope, receive: Receive, send: Send) -> None:\n    body = {\n        name.capitalize().decode(): value.decode()\n        for name, value in scope.get(\"headers\", [])\n    }\n    await send(\n        {\n            \"type\": \"http.response.start\",\n            \"status\": 200,\n            \"headers\": [[b\"content-type\", b\"application/json\"]],\n        }\n    )\n    await send({\"type\": \"http.response.body\", \"body\": json.dumps(body).encode()})\n\n\nasync def redirect_301(scope: Scope, receive: Receive, send: Send) -> None:\n    await send(\n        {\"type\": \"http.response.start\", \"status\": 301, \"headers\": [[b\"location\", b\"/\"]]}\n    )\n    await send({\"type\": \"http.response.body\"})\n\n\n@pytest.fixture(scope=\"session\")\ndef cert_authority():\n    return trustme.CA()\n\n\n@pytest.fixture(scope=\"session\")\ndef localhost_cert(cert_authority):\n    return cert_authority.issue_cert(\"localhost\")\n\n\n@pytest.fixture(scope=\"session\")\ndef cert_pem_file(localhost_cert):\n    with localhost_cert.cert_chain_pems[0].tempfile() as tmp:\n        yield tmp\n\n\n@pytest.fixture(scope=\"session\")\ndef cert_private_key_file(localhost_cert):\n    with localhost_cert.private_key_pem.tempfile() as tmp:\n        yield tmp\n\n\n@pytest.fixture(scope=\"session\")\ndef cert_encrypted_private_key_file(localhost_cert):\n    # Deserialize the private key and then reserialize with a password\n    private_key = load_pem_private_key(\n        localhost_cert.private_key_pem.bytes(), password=None, backend=default_backend()\n    )\n    encrypted_private_key_pem = trustme.Blob(\n        private_key.private_bytes(\n            Encoding.PEM,\n            PrivateFormat.TraditionalOpenSSL,\n            BestAvailableEncryption(password=b\"password\"),\n        )\n    )\n    with encrypted_private_key_pem.tempfile() as tmp:\n        yield tmp\n\n\nclass TestServer(Server):\n    @property\n    def url(self) -> httpx.URL:\n        protocol = \"https\" if self.config.is_ssl else \"http\"\n        return httpx.URL(f\"{protocol}://{self.config.host}:{self.config.port}/\")\n\n    def install_signal_handlers(self) -> None:\n        # Disable the default installation of handlers for signals such as SIGTERM,\n        # because it can only be done in the main thread.\n        pass  # pragma: nocover\n\n    async def serve(self, sockets=None):\n        self.restart_requested = asyncio.Event()\n\n        loop = asyncio.get_event_loop()\n        tasks = {\n            loop.create_task(super().serve(sockets=sockets)),\n            loop.create_task(self.watch_restarts()),\n        }\n        await asyncio.wait(tasks)\n\n    async def restart(self) -> None:  # pragma: no cover\n        # This coroutine may be called from a different thread than the one the\n        # server is running on, and from an async environment that's not asyncio.\n        # For this reason, we use an event to coordinate with the server\n        # instead of calling shutdown()/startup() directly, and should not make\n        # any asyncio-specific operations.\n        self.started = False\n        self.restart_requested.set()\n        while not self.started:\n            await sleep(0.2)\n\n    async def watch_restarts(self) -> None:  # pragma: no cover\n        while True:\n            if self.should_exit:\n                return\n\n            try:\n                await asyncio.wait_for(self.restart_requested.wait(), timeout=0.1)\n            except asyncio.TimeoutError:\n                continue\n\n            self.restart_requested.clear()\n            await self.shutdown()\n            await self.startup()\n\n\ndef serve_in_thread(server: TestServer) -> typing.Iterator[TestServer]:\n    thread = threading.Thread(target=server.run)\n    thread.start()\n    try:\n        while not server.started:\n            time.sleep(1e-3)\n        yield server\n    finally:\n        server.should_exit = True\n        thread.join()\n\n\n@pytest.fixture(scope=\"session\")\ndef server() -> typing.Iterator[TestServer]:\n    config = Config(app=app, lifespan=\"off\", loop=\"asyncio\")\n    server = TestServer(config=config)\n    yield from serve_in_thread(server)\n"
  },
  {
    "path": "tests/fixtures/.netrc",
    "content": "machine netrcexample.org\nlogin example-username\npassword example-password"
  },
  {
    "path": "tests/fixtures/.netrc-nopassword",
    "content": "machine netrcexample.org\nlogin example-username\n"
  },
  {
    "path": "tests/models/__init__.py",
    "content": ""
  },
  {
    "path": "tests/models/test_cookies.py",
    "content": "import http\n\nimport pytest\n\nimport httpx\n\n\ndef test_cookies():\n    cookies = httpx.Cookies({\"name\": \"value\"})\n    assert cookies[\"name\"] == \"value\"\n    assert \"name\" in cookies\n    assert len(cookies) == 1\n    assert dict(cookies) == {\"name\": \"value\"}\n    assert bool(cookies) is True\n\n    del cookies[\"name\"]\n    assert \"name\" not in cookies\n    assert len(cookies) == 0\n    assert dict(cookies) == {}\n    assert bool(cookies) is False\n\n\ndef test_cookies_update():\n    cookies = httpx.Cookies()\n    more_cookies = httpx.Cookies()\n    more_cookies.set(\"name\", \"value\", domain=\"example.com\")\n\n    cookies.update(more_cookies)\n    assert dict(cookies) == {\"name\": \"value\"}\n    assert cookies.get(\"name\", domain=\"example.com\") == \"value\"\n\n\ndef test_cookies_with_domain():\n    cookies = httpx.Cookies()\n    cookies.set(\"name\", \"value\", domain=\"example.com\")\n    cookies.set(\"name\", \"value\", domain=\"example.org\")\n\n    with pytest.raises(httpx.CookieConflict):\n        cookies[\"name\"]\n\n    cookies.clear(domain=\"example.com\")\n    assert len(cookies) == 1\n\n\ndef test_cookies_with_domain_and_path():\n    cookies = httpx.Cookies()\n    cookies.set(\"name\", \"value\", domain=\"example.com\", path=\"/subpath/1\")\n    cookies.set(\"name\", \"value\", domain=\"example.com\", path=\"/subpath/2\")\n    cookies.clear(domain=\"example.com\", path=\"/subpath/1\")\n    assert len(cookies) == 1\n    cookies.delete(\"name\", domain=\"example.com\", path=\"/subpath/2\")\n    assert len(cookies) == 0\n\n\ndef test_multiple_set_cookie():\n    jar = http.cookiejar.CookieJar()\n    headers = [\n        (\n            b\"Set-Cookie\",\n            b\"1P_JAR=2020-08-09-18; expires=Tue, 08-Sep-2099 18:33:35 GMT; \"\n            b\"path=/; domain=.example.org; Secure\",\n        ),\n        (\n            b\"Set-Cookie\",\n            b\"NID=204=KWdXOuypc86YvRfBSiWoW1dEXfSl_5qI7sxZY4umlk4J35yNTeNEkw15\"\n            b\"MRaujK6uYCwkrtjihTTXZPp285z_xDOUzrdHt4dj0Z5C0VOpbvdLwRdHatHAzQs7\"\n            b\"7TsaiWY78a3qU9r7KP_RbSLvLl2hlhnWFR2Hp5nWKPsAcOhQgSg; expires=Mon, \"\n            b\"08-Feb-2099 18:33:35 GMT; path=/; domain=.example.org; HttpOnly\",\n        ),\n    ]\n    request = httpx.Request(\"GET\", \"https://www.example.org\")\n    response = httpx.Response(200, request=request, headers=headers)\n\n    cookies = httpx.Cookies(jar)\n    cookies.extract_cookies(response)\n\n    assert len(cookies) == 2\n\n\ndef test_cookies_can_be_a_list_of_tuples():\n    cookies_val = [(\"name1\", \"val1\"), (\"name2\", \"val2\")]\n\n    cookies = httpx.Cookies(cookies_val)\n\n    assert len(cookies.items()) == 2\n    for k, v in cookies_val:\n        assert cookies[k] == v\n\n\ndef test_cookies_repr():\n    cookies = httpx.Cookies()\n    cookies.set(name=\"foo\", value=\"bar\", domain=\"http://blah.com\")\n    cookies.set(name=\"fizz\", value=\"buzz\", domain=\"http://hello.com\")\n\n    assert repr(cookies) == (\n        \"<Cookies[<Cookie foo=bar for http://blah.com />,\"\n        \" <Cookie fizz=buzz for http://hello.com />]>\"\n    )\n"
  },
  {
    "path": "tests/models/test_headers.py",
    "content": "import pytest\n\nimport httpx\n\n\ndef test_headers():\n    h = httpx.Headers([(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")])\n    assert \"a\" in h\n    assert \"A\" in h\n    assert \"b\" in h\n    assert \"B\" in h\n    assert \"c\" not in h\n    assert h[\"a\"] == \"123, 456\"\n    assert h.get(\"a\") == \"123, 456\"\n    assert h.get(\"nope\", default=None) is None\n    assert h.get_list(\"a\") == [\"123\", \"456\"]\n\n    assert list(h.keys()) == [\"a\", \"b\"]\n    assert list(h.values()) == [\"123, 456\", \"789\"]\n    assert list(h.items()) == [(\"a\", \"123, 456\"), (\"b\", \"789\")]\n    assert h.multi_items() == [(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")]\n    assert list(h) == [\"a\", \"b\"]\n    assert dict(h) == {\"a\": \"123, 456\", \"b\": \"789\"}\n    assert repr(h) == \"Headers([('a', '123'), ('a', '456'), ('b', '789')])\"\n    assert h == [(\"a\", \"123\"), (\"b\", \"789\"), (\"a\", \"456\")]\n    assert h == [(\"a\", \"123\"), (\"A\", \"456\"), (\"b\", \"789\")]\n    assert h == {\"a\": \"123\", \"A\": \"456\", \"b\": \"789\"}\n    assert h != \"a: 123\\nA: 456\\nb: 789\"\n\n    h = httpx.Headers({\"a\": \"123\", \"b\": \"789\"})\n    assert h[\"A\"] == \"123\"\n    assert h[\"B\"] == \"789\"\n    assert h.raw == [(b\"a\", b\"123\"), (b\"b\", b\"789\")]\n    assert repr(h) == \"Headers({'a': '123', 'b': '789'})\"\n\n\ndef test_header_mutations():\n    h = httpx.Headers()\n    assert dict(h) == {}\n    h[\"a\"] = \"1\"\n    assert dict(h) == {\"a\": \"1\"}\n    h[\"a\"] = \"2\"\n    assert dict(h) == {\"a\": \"2\"}\n    h.setdefault(\"a\", \"3\")\n    assert dict(h) == {\"a\": \"2\"}\n    h.setdefault(\"b\", \"4\")\n    assert dict(h) == {\"a\": \"2\", \"b\": \"4\"}\n    del h[\"a\"]\n    assert dict(h) == {\"b\": \"4\"}\n    assert h.raw == [(b\"b\", b\"4\")]\n\n\ndef test_copy_headers_method():\n    headers = httpx.Headers({\"custom\": \"example\"})\n    headers_copy = headers.copy()\n    assert headers == headers_copy\n    assert headers is not headers_copy\n\n\ndef test_copy_headers_init():\n    headers = httpx.Headers({\"custom\": \"example\"})\n    headers_copy = httpx.Headers(headers)\n    assert headers == headers_copy\n\n\ndef test_headers_insert_retains_ordering():\n    headers = httpx.Headers({\"a\": \"a\", \"b\": \"b\", \"c\": \"c\"})\n    headers[\"b\"] = \"123\"\n    assert list(headers.values()) == [\"a\", \"123\", \"c\"]\n\n\ndef test_headers_insert_appends_if_new():\n    headers = httpx.Headers({\"a\": \"a\", \"b\": \"b\", \"c\": \"c\"})\n    headers[\"d\"] = \"123\"\n    assert list(headers.values()) == [\"a\", \"b\", \"c\", \"123\"]\n\n\ndef test_headers_insert_removes_all_existing():\n    headers = httpx.Headers([(\"a\", \"123\"), (\"a\", \"456\")])\n    headers[\"a\"] = \"789\"\n    assert dict(headers) == {\"a\": \"789\"}\n\n\ndef test_headers_delete_removes_all_existing():\n    headers = httpx.Headers([(\"a\", \"123\"), (\"a\", \"456\")])\n    del headers[\"a\"]\n    assert dict(headers) == {}\n\n\ndef test_headers_dict_repr():\n    \"\"\"\n    Headers should display with a dict repr by default.\n    \"\"\"\n    headers = httpx.Headers({\"custom\": \"example\"})\n    assert repr(headers) == \"Headers({'custom': 'example'})\"\n\n\ndef test_headers_encoding_in_repr():\n    \"\"\"\n    Headers should display an encoding in the repr if required.\n    \"\"\"\n    headers = httpx.Headers({b\"custom\": \"example ☃\".encode(\"utf-8\")})\n    assert repr(headers) == \"Headers({'custom': 'example ☃'}, encoding='utf-8')\"\n\n\ndef test_headers_list_repr():\n    \"\"\"\n    Headers should display with a list repr if they include multiple identical keys.\n    \"\"\"\n    headers = httpx.Headers([(\"custom\", \"example 1\"), (\"custom\", \"example 2\")])\n    assert (\n        repr(headers) == \"Headers([('custom', 'example 1'), ('custom', 'example 2')])\"\n    )\n\n\ndef test_headers_decode_ascii():\n    \"\"\"\n    Headers should decode as ascii by default.\n    \"\"\"\n    raw_headers = [(b\"Custom\", b\"Example\")]\n    headers = httpx.Headers(raw_headers)\n    assert dict(headers) == {\"custom\": \"Example\"}\n    assert headers.encoding == \"ascii\"\n\n\ndef test_headers_decode_utf_8():\n    \"\"\"\n    Headers containing non-ascii codepoints should default to decoding as utf-8.\n    \"\"\"\n    raw_headers = [(b\"Custom\", \"Code point: ☃\".encode(\"utf-8\"))]\n    headers = httpx.Headers(raw_headers)\n    assert dict(headers) == {\"custom\": \"Code point: ☃\"}\n    assert headers.encoding == \"utf-8\"\n\n\ndef test_headers_decode_iso_8859_1():\n    \"\"\"\n    Headers containing non-UTF-8 codepoints should default to decoding as iso-8859-1.\n    \"\"\"\n    raw_headers = [(b\"Custom\", \"Code point: ÿ\".encode(\"iso-8859-1\"))]\n    headers = httpx.Headers(raw_headers)\n    assert dict(headers) == {\"custom\": \"Code point: ÿ\"}\n    assert headers.encoding == \"iso-8859-1\"\n\n\ndef test_headers_decode_explicit_encoding():\n    \"\"\"\n    An explicit encoding may be set on headers in order to force a\n    particular decoding.\n    \"\"\"\n    raw_headers = [(b\"Custom\", \"Code point: ☃\".encode(\"utf-8\"))]\n    headers = httpx.Headers(raw_headers)\n    headers.encoding = \"iso-8859-1\"\n    assert dict(headers) == {\"custom\": \"Code point: â\\x98\\x83\"}\n    assert headers.encoding == \"iso-8859-1\"\n\n\ndef test_multiple_headers():\n    \"\"\"\n    `Headers.get_list` should support both split_commas=False and split_commas=True.\n    \"\"\"\n    h = httpx.Headers([(\"set-cookie\", \"a, b\"), (\"set-cookie\", \"c\")])\n    assert h.get_list(\"Set-Cookie\") == [\"a, b\", \"c\"]\n\n    h = httpx.Headers([(\"vary\", \"a, b\"), (\"vary\", \"c\")])\n    assert h.get_list(\"Vary\", split_commas=True) == [\"a\", \"b\", \"c\"]\n\n\n@pytest.mark.parametrize(\"header\", [\"authorization\", \"proxy-authorization\"])\ndef test_sensitive_headers(header):\n    \"\"\"\n    Some headers should be obfuscated because they contain sensitive data.\n    \"\"\"\n    value = \"s3kr3t\"\n    h = httpx.Headers({header: value})\n    assert repr(h) == \"Headers({'%s': '[secure]'})\" % header\n\n\n@pytest.mark.parametrize(\n    \"headers, output\",\n    [\n        ([(\"content-type\", \"text/html\")], [(\"content-type\", \"text/html\")]),\n        ([(\"authorization\", \"s3kr3t\")], [(\"authorization\", \"[secure]\")]),\n        ([(\"proxy-authorization\", \"s3kr3t\")], [(\"proxy-authorization\", \"[secure]\")]),\n    ],\n)\ndef test_obfuscate_sensitive_headers(headers, output):\n    as_dict = {k: v for k, v in output}\n    headers_class = httpx.Headers({k: v for k, v in headers})\n    assert repr(headers_class) == f\"Headers({as_dict!r})\"\n\n\n@pytest.mark.parametrize(\n    \"value, expected\",\n    (\n        (\n            '<http:/.../front.jpeg>; rel=front; type=\"image/jpeg\"',\n            [{\"url\": \"http:/.../front.jpeg\", \"rel\": \"front\", \"type\": \"image/jpeg\"}],\n        ),\n        (\"<http:/.../front.jpeg>\", [{\"url\": \"http:/.../front.jpeg\"}]),\n        (\"<http:/.../front.jpeg>;\", [{\"url\": \"http:/.../front.jpeg\"}]),\n        (\n            '<http:/.../front.jpeg>; type=\"image/jpeg\",<http://.../back.jpeg>;',\n            [\n                {\"url\": \"http:/.../front.jpeg\", \"type\": \"image/jpeg\"},\n                {\"url\": \"http://.../back.jpeg\"},\n            ],\n        ),\n        (\"\", []),\n    ),\n)\ndef test_parse_header_links(value, expected):\n    all_links = httpx.Response(200, headers={\"link\": value}).links.values()\n    assert all(link in all_links for link in expected)\n\n\ndef test_parse_header_links_no_link():\n    all_links = httpx.Response(200).links\n    assert all_links == {}\n"
  },
  {
    "path": "tests/models/test_queryparams.py",
    "content": "import pytest\n\nimport httpx\n\n\n@pytest.mark.parametrize(\n    \"source\",\n    [\n        \"a=123&a=456&b=789\",\n        {\"a\": [\"123\", \"456\"], \"b\": 789},\n        {\"a\": (\"123\", \"456\"), \"b\": 789},\n        [(\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")],\n        ((\"a\", \"123\"), (\"a\", \"456\"), (\"b\", \"789\")),\n    ],\n)\ndef test_queryparams(source):\n    q = httpx.QueryParams(source)\n    assert \"a\" in q\n    assert \"A\" not in q\n    assert \"c\" not in q\n    assert q[\"a\"] == \"123\"\n    assert q.get(\"a\") == \"123\"\n    assert q.get(\"nope\", default=None) is None\n    assert q.get_list(\"a\") == [\"123\", \"456\"]\n\n    assert list(q.keys()) == [\"a\", \"b\"]\n    assert list(q.values()) == [\"123\", \"789\"]\n    assert list(q.items()) == [(\"a\", \"123\"), (\"b\", \"789\")]\n    assert len(q) == 2\n    assert list(q) == [\"a\", \"b\"]\n    assert dict(q) == {\"a\": \"123\", \"b\": \"789\"}\n    assert str(q) == \"a=123&a=456&b=789\"\n    assert repr(q) == \"QueryParams('a=123&a=456&b=789')\"\n    assert httpx.QueryParams({\"a\": \"123\", \"b\": \"456\"}) == httpx.QueryParams(\n        [(\"a\", \"123\"), (\"b\", \"456\")]\n    )\n    assert httpx.QueryParams({\"a\": \"123\", \"b\": \"456\"}) == httpx.QueryParams(\n        \"a=123&b=456\"\n    )\n    assert httpx.QueryParams({\"a\": \"123\", \"b\": \"456\"}) == httpx.QueryParams(\n        {\"b\": \"456\", \"a\": \"123\"}\n    )\n    assert httpx.QueryParams() == httpx.QueryParams({})\n    assert httpx.QueryParams([(\"a\", \"123\"), (\"a\", \"456\")]) == httpx.QueryParams(\n        \"a=123&a=456\"\n    )\n    assert httpx.QueryParams({\"a\": \"123\", \"b\": \"456\"}) != \"invalid\"\n\n    q = httpx.QueryParams([(\"a\", \"123\"), (\"a\", \"456\")])\n    assert httpx.QueryParams(q) == q\n\n\ndef test_queryparam_types():\n    q = httpx.QueryParams(None)\n    assert str(q) == \"\"\n\n    q = httpx.QueryParams({\"a\": True})\n    assert str(q) == \"a=true\"\n\n    q = httpx.QueryParams({\"a\": False})\n    assert str(q) == \"a=false\"\n\n    q = httpx.QueryParams({\"a\": \"\"})\n    assert str(q) == \"a=\"\n\n    q = httpx.QueryParams({\"a\": None})\n    assert str(q) == \"a=\"\n\n    q = httpx.QueryParams({\"a\": 1.23})\n    assert str(q) == \"a=1.23\"\n\n    q = httpx.QueryParams({\"a\": 123})\n    assert str(q) == \"a=123\"\n\n    q = httpx.QueryParams({\"a\": [1, 2]})\n    assert str(q) == \"a=1&a=2\"\n\n\ndef test_empty_query_params():\n    q = httpx.QueryParams({\"a\": \"\"})\n    assert str(q) == \"a=\"\n\n    q = httpx.QueryParams(\"a=\")\n    assert str(q) == \"a=\"\n\n    q = httpx.QueryParams(\"a\")\n    assert str(q) == \"a=\"\n\n\ndef test_queryparam_update_is_hard_deprecated():\n    q = httpx.QueryParams(\"a=123\")\n    with pytest.raises(RuntimeError):\n        q.update({\"a\": \"456\"})\n\n\ndef test_queryparam_setter_is_hard_deprecated():\n    q = httpx.QueryParams(\"a=123\")\n    with pytest.raises(RuntimeError):\n        q[\"a\"] = \"456\"\n\n\ndef test_queryparam_set():\n    q = httpx.QueryParams(\"a=123\")\n    q = q.set(\"a\", \"456\")\n    assert q == httpx.QueryParams(\"a=456\")\n\n\ndef test_queryparam_add():\n    q = httpx.QueryParams(\"a=123\")\n    q = q.add(\"a\", \"456\")\n    assert q == httpx.QueryParams(\"a=123&a=456\")\n\n\ndef test_queryparam_remove():\n    q = httpx.QueryParams(\"a=123\")\n    q = q.remove(\"a\")\n    assert q == httpx.QueryParams(\"\")\n\n\ndef test_queryparam_merge():\n    q = httpx.QueryParams(\"a=123\")\n    q = q.merge({\"b\": \"456\"})\n    assert q == httpx.QueryParams(\"a=123&b=456\")\n    q = q.merge({\"a\": \"000\", \"c\": \"789\"})\n    assert q == httpx.QueryParams(\"a=000&b=456&c=789\")\n\n\ndef test_queryparams_are_hashable():\n    params = (\n        httpx.QueryParams(\"a=123\"),\n        httpx.QueryParams({\"a\": 123}),\n        httpx.QueryParams(\"b=456\"),\n        httpx.QueryParams({\"b\": 456}),\n    )\n\n    assert len(set(params)) == 2\n"
  },
  {
    "path": "tests/models/test_requests.py",
    "content": "import pickle\nimport typing\n\nimport pytest\n\nimport httpx\n\n\ndef test_request_repr():\n    request = httpx.Request(\"GET\", \"http://example.org\")\n    assert repr(request) == \"<Request('GET', 'http://example.org')>\"\n\n\ndef test_no_content():\n    request = httpx.Request(\"GET\", \"http://example.org\")\n    assert \"Content-Length\" not in request.headers\n\n\ndef test_content_length_header():\n    request = httpx.Request(\"POST\", \"http://example.org\", content=b\"test 123\")\n    assert request.headers[\"Content-Length\"] == \"8\"\n\n\ndef test_iterable_content():\n    class Content:\n        def __iter__(self):\n            yield b\"test 123\"  # pragma: no cover\n\n    request = httpx.Request(\"POST\", \"http://example.org\", content=Content())\n    assert request.headers == {\"Host\": \"example.org\", \"Transfer-Encoding\": \"chunked\"}\n\n\ndef test_generator_with_transfer_encoding_header():\n    def content() -> typing.Iterator[bytes]:\n        yield b\"test 123\"  # pragma: no cover\n\n    request = httpx.Request(\"POST\", \"http://example.org\", content=content())\n    assert request.headers == {\"Host\": \"example.org\", \"Transfer-Encoding\": \"chunked\"}\n\n\ndef test_generator_with_content_length_header():\n    def content() -> typing.Iterator[bytes]:\n        yield b\"test 123\"  # pragma: no cover\n\n    headers = {\"Content-Length\": \"8\"}\n    request = httpx.Request(\n        \"POST\", \"http://example.org\", content=content(), headers=headers\n    )\n    assert request.headers == {\"Host\": \"example.org\", \"Content-Length\": \"8\"}\n\n\ndef test_url_encoded_data():\n    request = httpx.Request(\"POST\", \"http://example.org\", data={\"test\": \"123\"})\n    request.read()\n\n    assert request.headers[\"Content-Type\"] == \"application/x-www-form-urlencoded\"\n    assert request.content == b\"test=123\"\n\n\ndef test_json_encoded_data():\n    request = httpx.Request(\"POST\", \"http://example.org\", json={\"test\": 123})\n    request.read()\n\n    assert request.headers[\"Content-Type\"] == \"application/json\"\n    assert request.content == b'{\"test\":123}'\n\n\ndef test_headers():\n    request = httpx.Request(\"POST\", \"http://example.org\", json={\"test\": 123})\n\n    assert request.headers == {\n        \"Host\": \"example.org\",\n        \"Content-Type\": \"application/json\",\n        \"Content-Length\": \"12\",\n    }\n\n\ndef test_read_and_stream_data():\n    # Ensure a request may still be streamed if it has been read.\n    # Needed for cases such as authentication classes that read the request body.\n    request = httpx.Request(\"POST\", \"http://example.org\", json={\"test\": 123})\n    request.read()\n    assert request.stream is not None\n    assert isinstance(request.stream, typing.Iterable)\n    content = b\"\".join(list(request.stream))\n    assert content == request.content\n\n\n@pytest.mark.anyio\nasync def test_aread_and_stream_data():\n    # Ensure a request may still be streamed if it has been read.\n    # Needed for cases such as authentication classes that read the request body.\n    request = httpx.Request(\"POST\", \"http://example.org\", json={\"test\": 123})\n    await request.aread()\n    assert request.stream is not None\n    assert isinstance(request.stream, typing.AsyncIterable)\n    content = b\"\".join([part async for part in request.stream])\n    assert content == request.content\n\n\ndef test_cannot_access_streaming_content_without_read():\n    # Ensure that streaming requests\n    def streaming_body() -> typing.Iterator[bytes]:  # pragma: no cover\n        yield b\"\"\n\n    request = httpx.Request(\"POST\", \"http://example.org\", content=streaming_body())\n    with pytest.raises(httpx.RequestNotRead):\n        request.content  # noqa: B018\n\n\ndef test_transfer_encoding_header():\n    async def streaming_body(data: bytes) -> typing.AsyncIterator[bytes]:\n        yield data  # pragma: no cover\n\n    data = streaming_body(b\"test 123\")\n\n    request = httpx.Request(\"POST\", \"http://example.org\", content=data)\n    assert \"Content-Length\" not in request.headers\n    assert request.headers[\"Transfer-Encoding\"] == \"chunked\"\n\n\ndef test_ignore_transfer_encoding_header_if_content_length_exists():\n    \"\"\"\n    `Transfer-Encoding` should be ignored if `Content-Length` has been set explicitly.\n    See https://github.com/encode/httpx/issues/1168\n    \"\"\"\n\n    def streaming_body(data: bytes) -> typing.Iterator[bytes]:\n        yield data  # pragma: no cover\n\n    data = streaming_body(b\"abcd\")\n\n    headers = {\"Content-Length\": \"4\"}\n    request = httpx.Request(\"POST\", \"http://example.org\", content=data, headers=headers)\n    assert \"Transfer-Encoding\" not in request.headers\n    assert request.headers[\"Content-Length\"] == \"4\"\n\n\ndef test_override_host_header():\n    headers = {\"host\": \"1.2.3.4:80\"}\n\n    request = httpx.Request(\"GET\", \"http://example.org\", headers=headers)\n    assert request.headers[\"Host\"] == \"1.2.3.4:80\"\n\n\ndef test_override_accept_encoding_header():\n    headers = {\"Accept-Encoding\": \"identity\"}\n\n    request = httpx.Request(\"GET\", \"http://example.org\", headers=headers)\n    assert request.headers[\"Accept-Encoding\"] == \"identity\"\n\n\ndef test_override_content_length_header():\n    async def streaming_body(data: bytes) -> typing.AsyncIterator[bytes]:\n        yield data  # pragma: no cover\n\n    data = streaming_body(b\"test 123\")\n    headers = {\"Content-Length\": \"8\"}\n\n    request = httpx.Request(\"POST\", \"http://example.org\", content=data, headers=headers)\n    assert request.headers[\"Content-Length\"] == \"8\"\n\n\ndef test_url():\n    url = \"http://example.org\"\n    request = httpx.Request(\"GET\", url)\n    assert request.url.scheme == \"http\"\n    assert request.url.port is None\n    assert request.url.path == \"/\"\n    assert request.url.raw_path == b\"/\"\n\n    url = \"https://example.org/abc?foo=bar\"\n    request = httpx.Request(\"GET\", url)\n    assert request.url.scheme == \"https\"\n    assert request.url.port is None\n    assert request.url.path == \"/abc\"\n    assert request.url.raw_path == b\"/abc?foo=bar\"\n\n\ndef test_request_picklable():\n    request = httpx.Request(\"POST\", \"http://example.org\", json={\"test\": 123})\n    pickle_request = pickle.loads(pickle.dumps(request))\n    assert pickle_request.method == \"POST\"\n    assert pickle_request.url.path == \"/\"\n    assert pickle_request.headers[\"Content-Type\"] == \"application/json\"\n    assert pickle_request.content == b'{\"test\":123}'\n    assert pickle_request.stream is not None\n    assert request.headers == {\n        \"Host\": \"example.org\",\n        \"Content-Type\": \"application/json\",\n        \"content-length\": \"12\",\n    }\n\n\n@pytest.mark.anyio\nasync def test_request_async_streaming_content_picklable():\n    async def streaming_body(data: bytes) -> typing.AsyncIterator[bytes]:\n        yield data\n\n    data = streaming_body(b\"test 123\")\n    request = httpx.Request(\"POST\", \"http://example.org\", content=data)\n    pickle_request = pickle.loads(pickle.dumps(request))\n    with pytest.raises(httpx.RequestNotRead):\n        pickle_request.content  # noqa: B018\n    with pytest.raises(httpx.StreamClosed):\n        await pickle_request.aread()\n\n    request = httpx.Request(\"POST\", \"http://example.org\", content=data)\n    await request.aread()\n    pickle_request = pickle.loads(pickle.dumps(request))\n    assert pickle_request.content == b\"test 123\"\n\n\ndef test_request_generator_content_picklable():\n    def content() -> typing.Iterator[bytes]:\n        yield b\"test 123\"  # pragma: no cover\n\n    request = httpx.Request(\"POST\", \"http://example.org\", content=content())\n    pickle_request = pickle.loads(pickle.dumps(request))\n    with pytest.raises(httpx.RequestNotRead):\n        pickle_request.content  # noqa: B018\n    with pytest.raises(httpx.StreamClosed):\n        pickle_request.read()\n\n    request = httpx.Request(\"POST\", \"http://example.org\", content=content())\n    request.read()\n    pickle_request = pickle.loads(pickle.dumps(request))\n    assert pickle_request.content == b\"test 123\"\n\n\ndef test_request_params():\n    request = httpx.Request(\"GET\", \"http://example.com\", params={})\n    assert str(request.url) == \"http://example.com\"\n\n    request = httpx.Request(\n        \"GET\", \"http://example.com?c=3\", params={\"a\": \"1\", \"b\": \"2\"}\n    )\n    assert str(request.url) == \"http://example.com?a=1&b=2\"\n\n    request = httpx.Request(\"GET\", \"http://example.com?a=1\", params={})\n    assert str(request.url) == \"http://example.com\"\n"
  },
  {
    "path": "tests/models/test_responses.py",
    "content": "import json\nimport pickle\nimport typing\n\nimport chardet\nimport pytest\n\nimport httpx\n\n\nclass StreamingBody:\n    def __iter__(self):\n        yield b\"Hello, \"\n        yield b\"world!\"\n\n\ndef streaming_body() -> typing.Iterator[bytes]:\n    yield b\"Hello, \"\n    yield b\"world!\"\n\n\nasync def async_streaming_body() -> typing.AsyncIterator[bytes]:\n    yield b\"Hello, \"\n    yield b\"world!\"\n\n\ndef autodetect(content):\n    return chardet.detect(content).get(\"encoding\")\n\n\ndef test_response():\n    response = httpx.Response(\n        200,\n        content=b\"Hello, world!\",\n        request=httpx.Request(\"GET\", \"https://example.org\"),\n    )\n\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    assert response.text == \"Hello, world!\"\n    assert response.request.method == \"GET\"\n    assert response.request.url == \"https://example.org\"\n    assert not response.is_error\n\n\ndef test_response_content():\n    response = httpx.Response(200, content=\"Hello, world!\")\n\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    assert response.text == \"Hello, world!\"\n    assert response.headers == {\"Content-Length\": \"13\"}\n\n\ndef test_response_text():\n    response = httpx.Response(200, text=\"Hello, world!\")\n\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    assert response.text == \"Hello, world!\"\n    assert response.headers == {\n        \"Content-Length\": \"13\",\n        \"Content-Type\": \"text/plain; charset=utf-8\",\n    }\n\n\ndef test_response_html():\n    response = httpx.Response(200, html=\"<html><body>Hello, world!</html></body>\")\n\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    assert response.text == \"<html><body>Hello, world!</html></body>\"\n    assert response.headers == {\n        \"Content-Length\": \"39\",\n        \"Content-Type\": \"text/html; charset=utf-8\",\n    }\n\n\ndef test_response_json():\n    response = httpx.Response(200, json={\"hello\": \"world\"})\n\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    assert str(response.json()) == \"{'hello': 'world'}\"\n    assert response.headers == {\n        \"Content-Length\": \"17\",\n        \"Content-Type\": \"application/json\",\n    }\n\n\ndef test_raise_for_status():\n    request = httpx.Request(\"GET\", \"https://example.org\")\n\n    # 2xx status codes are not an error.\n    response = httpx.Response(200, request=request)\n    response.raise_for_status()\n\n    # 1xx status codes are informational responses.\n    response = httpx.Response(101, request=request)\n    assert response.is_informational\n    with pytest.raises(httpx.HTTPStatusError) as exc_info:\n        response.raise_for_status()\n    assert str(exc_info.value) == (\n        \"Informational response '101 Switching Protocols' for url 'https://example.org'\\n\"\n        \"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101\"\n    )\n\n    # 3xx status codes are redirections.\n    headers = {\"location\": \"https://other.org\"}\n    response = httpx.Response(303, headers=headers, request=request)\n    assert response.is_redirect\n    with pytest.raises(httpx.HTTPStatusError) as exc_info:\n        response.raise_for_status()\n    assert str(exc_info.value) == (\n        \"Redirect response '303 See Other' for url 'https://example.org'\\n\"\n        \"Redirect location: 'https://other.org'\\n\"\n        \"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303\"\n    )\n\n    # 4xx status codes are a client error.\n    response = httpx.Response(403, request=request)\n    assert response.is_client_error\n    assert response.is_error\n    with pytest.raises(httpx.HTTPStatusError) as exc_info:\n        response.raise_for_status()\n    assert str(exc_info.value) == (\n        \"Client error '403 Forbidden' for url 'https://example.org'\\n\"\n        \"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403\"\n    )\n\n    # 5xx status codes are a server error.\n    response = httpx.Response(500, request=request)\n    assert response.is_server_error\n    assert response.is_error\n    with pytest.raises(httpx.HTTPStatusError) as exc_info:\n        response.raise_for_status()\n    assert str(exc_info.value) == (\n        \"Server error '500 Internal Server Error' for url 'https://example.org'\\n\"\n        \"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500\"\n    )\n\n    # Calling .raise_for_status without setting a request instance is\n    # not valid. Should raise a runtime error.\n    response = httpx.Response(200)\n    with pytest.raises(RuntimeError):\n        response.raise_for_status()\n\n\ndef test_response_repr():\n    response = httpx.Response(\n        200,\n        content=b\"Hello, world!\",\n    )\n    assert repr(response) == \"<Response [200 OK]>\"\n\n\ndef test_response_content_type_encoding():\n    \"\"\"\n    Use the charset encoding in the Content-Type header if possible.\n    \"\"\"\n    headers = {\"Content-Type\": \"text-plain; charset=latin-1\"}\n    content = \"Latin 1: ÿ\".encode(\"latin-1\")\n    response = httpx.Response(\n        200,\n        content=content,\n        headers=headers,\n    )\n    assert response.text == \"Latin 1: ÿ\"\n    assert response.encoding == \"latin-1\"\n\n\ndef test_response_default_to_utf8_encoding():\n    \"\"\"\n    Default to utf-8 encoding if there is no Content-Type header.\n    \"\"\"\n    content = \"おはようございます。\".encode(\"utf-8\")\n    response = httpx.Response(\n        200,\n        content=content,\n    )\n    assert response.text == \"おはようございます。\"\n    assert response.encoding == \"utf-8\"\n\n\ndef test_response_fallback_to_utf8_encoding():\n    \"\"\"\n    Fallback to utf-8 if we get an invalid charset in the Content-Type header.\n    \"\"\"\n    headers = {\"Content-Type\": \"text-plain; charset=invalid-codec-name\"}\n    content = \"おはようございます。\".encode(\"utf-8\")\n    response = httpx.Response(\n        200,\n        content=content,\n        headers=headers,\n    )\n    assert response.text == \"おはようございます。\"\n    assert response.encoding == \"utf-8\"\n\n\ndef test_response_no_charset_with_ascii_content():\n    \"\"\"\n    A response with ascii encoded content should decode correctly,\n    even with no charset specified.\n    \"\"\"\n    content = b\"Hello, world!\"\n    headers = {\"Content-Type\": \"text/plain\"}\n    response = httpx.Response(\n        200,\n        content=content,\n        headers=headers,\n    )\n    assert response.status_code == 200\n    assert response.encoding == \"utf-8\"\n    assert response.text == \"Hello, world!\"\n\n\ndef test_response_no_charset_with_utf8_content():\n    \"\"\"\n    A response with UTF-8 encoded content should decode correctly,\n    even with no charset specified.\n    \"\"\"\n    content = \"Unicode Snowman: ☃\".encode(\"utf-8\")\n    headers = {\"Content-Type\": \"text/plain\"}\n    response = httpx.Response(\n        200,\n        content=content,\n        headers=headers,\n    )\n    assert response.text == \"Unicode Snowman: ☃\"\n    assert response.encoding == \"utf-8\"\n\n\ndef test_response_no_charset_with_iso_8859_1_content():\n    \"\"\"\n    A response with ISO 8859-1 encoded content should decode correctly,\n    even with no charset specified, if autodetect is enabled.\n    \"\"\"\n    content = \"Accented: Österreich abcdefghijklmnopqrstuzwxyz\".encode(\"iso-8859-1\")\n    headers = {\"Content-Type\": \"text/plain\"}\n    response = httpx.Response(\n        200, content=content, headers=headers, default_encoding=autodetect\n    )\n    assert response.text == \"Accented: Österreich abcdefghijklmnopqrstuzwxyz\"\n    assert response.charset_encoding is None\n\n\ndef test_response_no_charset_with_cp_1252_content():\n    \"\"\"\n    A response with Windows 1252 encoded content should decode correctly,\n    even with no charset specified, if autodetect is enabled.\n    \"\"\"\n    content = \"Euro Currency: € abcdefghijklmnopqrstuzwxyz\".encode(\"cp1252\")\n    headers = {\"Content-Type\": \"text/plain\"}\n    response = httpx.Response(\n        200, content=content, headers=headers, default_encoding=autodetect\n    )\n    assert response.text == \"Euro Currency: € abcdefghijklmnopqrstuzwxyz\"\n    assert response.charset_encoding is None\n\n\ndef test_response_non_text_encoding():\n    \"\"\"\n    Default to attempting utf-8 encoding for non-text content-type headers.\n    \"\"\"\n    headers = {\"Content-Type\": \"image/png\"}\n    response = httpx.Response(\n        200,\n        content=b\"xyz\",\n        headers=headers,\n    )\n    assert response.text == \"xyz\"\n    assert response.encoding == \"utf-8\"\n\n\ndef test_response_set_explicit_encoding():\n    headers = {\n        \"Content-Type\": \"text-plain; charset=utf-8\"\n    }  # Deliberately incorrect charset\n    response = httpx.Response(\n        200,\n        content=\"Latin 1: ÿ\".encode(\"latin-1\"),\n        headers=headers,\n    )\n    response.encoding = \"latin-1\"\n    assert response.text == \"Latin 1: ÿ\"\n    assert response.encoding == \"latin-1\"\n\n\ndef test_response_force_encoding():\n    response = httpx.Response(\n        200,\n        content=\"Snowman: ☃\".encode(\"utf-8\"),\n    )\n    response.encoding = \"iso-8859-1\"\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    assert response.text == \"Snowman: â\\x98\\x83\"\n    assert response.encoding == \"iso-8859-1\"\n\n\ndef test_response_force_encoding_after_text_accessed():\n    response = httpx.Response(\n        200,\n        content=b\"Hello, world!\",\n    )\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    assert response.text == \"Hello, world!\"\n    assert response.encoding == \"utf-8\"\n\n    with pytest.raises(ValueError):\n        response.encoding = \"UTF8\"\n\n    with pytest.raises(ValueError):\n        response.encoding = \"iso-8859-1\"\n\n\ndef test_read():\n    response = httpx.Response(\n        200,\n        content=b\"Hello, world!\",\n    )\n\n    assert response.status_code == 200\n    assert response.text == \"Hello, world!\"\n    assert response.encoding == \"utf-8\"\n    assert response.is_closed\n\n    content = response.read()\n\n    assert content == b\"Hello, world!\"\n    assert response.content == b\"Hello, world!\"\n    assert response.is_closed\n\n\ndef test_empty_read():\n    response = httpx.Response(200)\n\n    assert response.status_code == 200\n    assert response.text == \"\"\n    assert response.encoding == \"utf-8\"\n    assert response.is_closed\n\n    content = response.read()\n\n    assert content == b\"\"\n    assert response.content == b\"\"\n    assert response.is_closed\n\n\n@pytest.mark.anyio\nasync def test_aread():\n    response = httpx.Response(\n        200,\n        content=b\"Hello, world!\",\n    )\n\n    assert response.status_code == 200\n    assert response.text == \"Hello, world!\"\n    assert response.encoding == \"utf-8\"\n    assert response.is_closed\n\n    content = await response.aread()\n\n    assert content == b\"Hello, world!\"\n    assert response.content == b\"Hello, world!\"\n    assert response.is_closed\n\n\n@pytest.mark.anyio\nasync def test_empty_aread():\n    response = httpx.Response(200)\n\n    assert response.status_code == 200\n    assert response.text == \"\"\n    assert response.encoding == \"utf-8\"\n    assert response.is_closed\n\n    content = await response.aread()\n\n    assert content == b\"\"\n    assert response.content == b\"\"\n    assert response.is_closed\n\n\ndef test_iter_raw():\n    response = httpx.Response(\n        200,\n        content=streaming_body(),\n    )\n\n    raw = b\"\"\n    for part in response.iter_raw():\n        raw += part\n    assert raw == b\"Hello, world!\"\n\n\ndef test_iter_raw_with_chunksize():\n    response = httpx.Response(200, content=streaming_body())\n    parts = list(response.iter_raw(chunk_size=5))\n    assert parts == [b\"Hello\", b\", wor\", b\"ld!\"]\n\n    response = httpx.Response(200, content=streaming_body())\n    parts = list(response.iter_raw(chunk_size=7))\n    assert parts == [b\"Hello, \", b\"world!\"]\n\n    response = httpx.Response(200, content=streaming_body())\n    parts = list(response.iter_raw(chunk_size=13))\n    assert parts == [b\"Hello, world!\"]\n\n    response = httpx.Response(200, content=streaming_body())\n    parts = list(response.iter_raw(chunk_size=20))\n    assert parts == [b\"Hello, world!\"]\n\n\ndef test_iter_raw_doesnt_return_empty_chunks():\n    def streaming_body_with_empty_chunks() -> typing.Iterator[bytes]:\n        yield b\"Hello, \"\n        yield b\"\"\n        yield b\"world!\"\n        yield b\"\"\n\n    response = httpx.Response(200, content=streaming_body_with_empty_chunks())\n\n    parts = list(response.iter_raw())\n    assert parts == [b\"Hello, \", b\"world!\"]\n\n\ndef test_iter_raw_on_iterable():\n    response = httpx.Response(\n        200,\n        content=StreamingBody(),\n    )\n\n    raw = b\"\"\n    for part in response.iter_raw():\n        raw += part\n    assert raw == b\"Hello, world!\"\n\n\ndef test_iter_raw_on_async():\n    response = httpx.Response(\n        200,\n        content=async_streaming_body(),\n    )\n\n    with pytest.raises(RuntimeError):\n        list(response.iter_raw())\n\n\ndef test_close_on_async():\n    response = httpx.Response(\n        200,\n        content=async_streaming_body(),\n    )\n\n    with pytest.raises(RuntimeError):\n        response.close()\n\n\ndef test_iter_raw_increments_updates_counter():\n    response = httpx.Response(200, content=streaming_body())\n\n    num_downloaded = response.num_bytes_downloaded\n    for part in response.iter_raw():\n        assert len(part) == (response.num_bytes_downloaded - num_downloaded)\n        num_downloaded = response.num_bytes_downloaded\n\n\n@pytest.mark.anyio\nasync def test_aiter_raw():\n    response = httpx.Response(200, content=async_streaming_body())\n\n    raw = b\"\"\n    async for part in response.aiter_raw():\n        raw += part\n    assert raw == b\"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_aiter_raw_with_chunksize():\n    response = httpx.Response(200, content=async_streaming_body())\n\n    parts = [part async for part in response.aiter_raw(chunk_size=5)]\n    assert parts == [b\"Hello\", b\", wor\", b\"ld!\"]\n\n    response = httpx.Response(200, content=async_streaming_body())\n\n    parts = [part async for part in response.aiter_raw(chunk_size=13)]\n    assert parts == [b\"Hello, world!\"]\n\n    response = httpx.Response(200, content=async_streaming_body())\n\n    parts = [part async for part in response.aiter_raw(chunk_size=20)]\n    assert parts == [b\"Hello, world!\"]\n\n\n@pytest.mark.anyio\nasync def test_aiter_raw_on_sync():\n    response = httpx.Response(\n        200,\n        content=streaming_body(),\n    )\n\n    with pytest.raises(RuntimeError):\n        [part async for part in response.aiter_raw()]\n\n\n@pytest.mark.anyio\nasync def test_aclose_on_sync():\n    response = httpx.Response(\n        200,\n        content=streaming_body(),\n    )\n\n    with pytest.raises(RuntimeError):\n        await response.aclose()\n\n\n@pytest.mark.anyio\nasync def test_aiter_raw_increments_updates_counter():\n    response = httpx.Response(200, content=async_streaming_body())\n\n    num_downloaded = response.num_bytes_downloaded\n    async for part in response.aiter_raw():\n        assert len(part) == (response.num_bytes_downloaded - num_downloaded)\n        num_downloaded = response.num_bytes_downloaded\n\n\ndef test_iter_bytes():\n    response = httpx.Response(200, content=b\"Hello, world!\")\n\n    content = b\"\"\n    for part in response.iter_bytes():\n        content += part\n    assert content == b\"Hello, world!\"\n\n\ndef test_iter_bytes_with_chunk_size():\n    response = httpx.Response(200, content=streaming_body())\n    parts = list(response.iter_bytes(chunk_size=5))\n    assert parts == [b\"Hello\", b\", wor\", b\"ld!\"]\n\n    response = httpx.Response(200, content=streaming_body())\n    parts = list(response.iter_bytes(chunk_size=13))\n    assert parts == [b\"Hello, world!\"]\n\n    response = httpx.Response(200, content=streaming_body())\n    parts = list(response.iter_bytes(chunk_size=20))\n    assert parts == [b\"Hello, world!\"]\n\n\ndef test_iter_bytes_with_empty_response():\n    response = httpx.Response(200, content=b\"\")\n    parts = list(response.iter_bytes())\n    assert parts == []\n\n\ndef test_iter_bytes_doesnt_return_empty_chunks():\n    def streaming_body_with_empty_chunks() -> typing.Iterator[bytes]:\n        yield b\"Hello, \"\n        yield b\"\"\n        yield b\"world!\"\n        yield b\"\"\n\n    response = httpx.Response(200, content=streaming_body_with_empty_chunks())\n\n    parts = list(response.iter_bytes())\n    assert parts == [b\"Hello, \", b\"world!\"]\n\n\n@pytest.mark.anyio\nasync def test_aiter_bytes():\n    response = httpx.Response(\n        200,\n        content=b\"Hello, world!\",\n    )\n\n    content = b\"\"\n    async for part in response.aiter_bytes():\n        content += part\n    assert content == b\"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_aiter_bytes_with_chunk_size():\n    response = httpx.Response(200, content=async_streaming_body())\n    parts = [part async for part in response.aiter_bytes(chunk_size=5)]\n    assert parts == [b\"Hello\", b\", wor\", b\"ld!\"]\n\n    response = httpx.Response(200, content=async_streaming_body())\n    parts = [part async for part in response.aiter_bytes(chunk_size=13)]\n    assert parts == [b\"Hello, world!\"]\n\n    response = httpx.Response(200, content=async_streaming_body())\n    parts = [part async for part in response.aiter_bytes(chunk_size=20)]\n    assert parts == [b\"Hello, world!\"]\n\n\ndef test_iter_text():\n    response = httpx.Response(\n        200,\n        content=b\"Hello, world!\",\n    )\n\n    content = \"\"\n    for part in response.iter_text():\n        content += part\n    assert content == \"Hello, world!\"\n\n\ndef test_iter_text_with_chunk_size():\n    response = httpx.Response(200, content=b\"Hello, world!\")\n    parts = list(response.iter_text(chunk_size=5))\n    assert parts == [\"Hello\", \", wor\", \"ld!\"]\n\n    response = httpx.Response(200, content=b\"Hello, world!!\")\n    parts = list(response.iter_text(chunk_size=7))\n    assert parts == [\"Hello, \", \"world!!\"]\n\n    response = httpx.Response(200, content=b\"Hello, world!\")\n    parts = list(response.iter_text(chunk_size=7))\n    assert parts == [\"Hello, \", \"world!\"]\n\n    response = httpx.Response(200, content=b\"Hello, world!\")\n    parts = list(response.iter_text(chunk_size=13))\n    assert parts == [\"Hello, world!\"]\n\n    response = httpx.Response(200, content=b\"Hello, world!\")\n    parts = list(response.iter_text(chunk_size=20))\n    assert parts == [\"Hello, world!\"]\n\n\n@pytest.mark.anyio\nasync def test_aiter_text():\n    response = httpx.Response(\n        200,\n        content=b\"Hello, world!\",\n    )\n\n    content = \"\"\n    async for part in response.aiter_text():\n        content += part\n    assert content == \"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_aiter_text_with_chunk_size():\n    response = httpx.Response(200, content=b\"Hello, world!\")\n    parts = [part async for part in response.aiter_text(chunk_size=5)]\n    assert parts == [\"Hello\", \", wor\", \"ld!\"]\n\n    response = httpx.Response(200, content=b\"Hello, world!\")\n    parts = [part async for part in response.aiter_text(chunk_size=13)]\n    assert parts == [\"Hello, world!\"]\n\n    response = httpx.Response(200, content=b\"Hello, world!\")\n    parts = [part async for part in response.aiter_text(chunk_size=20)]\n    assert parts == [\"Hello, world!\"]\n\n\ndef test_iter_lines():\n    response = httpx.Response(\n        200,\n        content=b\"Hello,\\nworld!\",\n    )\n    content = list(response.iter_lines())\n    assert content == [\"Hello,\", \"world!\"]\n\n\n@pytest.mark.anyio\nasync def test_aiter_lines():\n    response = httpx.Response(\n        200,\n        content=b\"Hello,\\nworld!\",\n    )\n\n    content = []\n    async for line in response.aiter_lines():\n        content.append(line)\n    assert content == [\"Hello,\", \"world!\"]\n\n\ndef test_sync_streaming_response():\n    response = httpx.Response(\n        200,\n        content=streaming_body(),\n    )\n\n    assert response.status_code == 200\n    assert not response.is_closed\n\n    content = response.read()\n\n    assert content == b\"Hello, world!\"\n    assert response.content == b\"Hello, world!\"\n    assert response.is_closed\n\n\n@pytest.mark.anyio\nasync def test_async_streaming_response():\n    response = httpx.Response(\n        200,\n        content=async_streaming_body(),\n    )\n\n    assert response.status_code == 200\n    assert not response.is_closed\n\n    content = await response.aread()\n\n    assert content == b\"Hello, world!\"\n    assert response.content == b\"Hello, world!\"\n    assert response.is_closed\n\n\ndef test_cannot_read_after_stream_consumed():\n    response = httpx.Response(\n        200,\n        content=streaming_body(),\n    )\n\n    content = b\"\"\n    for part in response.iter_bytes():\n        content += part\n\n    with pytest.raises(httpx.StreamConsumed):\n        response.read()\n\n\n@pytest.mark.anyio\nasync def test_cannot_aread_after_stream_consumed():\n    response = httpx.Response(\n        200,\n        content=async_streaming_body(),\n    )\n\n    content = b\"\"\n    async for part in response.aiter_bytes():\n        content += part\n\n    with pytest.raises(httpx.StreamConsumed):\n        await response.aread()\n\n\ndef test_cannot_read_after_response_closed():\n    response = httpx.Response(\n        200,\n        content=streaming_body(),\n    )\n\n    response.close()\n    with pytest.raises(httpx.StreamClosed):\n        response.read()\n\n\n@pytest.mark.anyio\nasync def test_cannot_aread_after_response_closed():\n    response = httpx.Response(\n        200,\n        content=async_streaming_body(),\n    )\n\n    await response.aclose()\n    with pytest.raises(httpx.StreamClosed):\n        await response.aread()\n\n\n@pytest.mark.anyio\nasync def test_elapsed_not_available_until_closed():\n    response = httpx.Response(\n        200,\n        content=async_streaming_body(),\n    )\n\n    with pytest.raises(RuntimeError):\n        response.elapsed  # noqa: B018\n\n\ndef test_unknown_status_code():\n    response = httpx.Response(\n        600,\n    )\n    assert response.status_code == 600\n    assert response.reason_phrase == \"\"\n    assert response.text == \"\"\n\n\ndef test_json_with_specified_encoding():\n    data = {\"greeting\": \"hello\", \"recipient\": \"world\"}\n    content = json.dumps(data).encode(\"utf-16\")\n    headers = {\"Content-Type\": \"application/json, charset=utf-16\"}\n    response = httpx.Response(\n        200,\n        content=content,\n        headers=headers,\n    )\n    assert response.json() == data\n\n\ndef test_json_with_options():\n    data = {\"greeting\": \"hello\", \"recipient\": \"world\", \"amount\": 1}\n    content = json.dumps(data).encode(\"utf-16\")\n    headers = {\"Content-Type\": \"application/json, charset=utf-16\"}\n    response = httpx.Response(\n        200,\n        content=content,\n        headers=headers,\n    )\n    assert response.json(parse_int=str)[\"amount\"] == \"1\"\n\n\n@pytest.mark.parametrize(\n    \"encoding\",\n    [\n        \"utf-8\",\n        \"utf-8-sig\",\n        \"utf-16\",\n        \"utf-16-be\",\n        \"utf-16-le\",\n        \"utf-32\",\n        \"utf-32-be\",\n        \"utf-32-le\",\n    ],\n)\ndef test_json_without_specified_charset(encoding):\n    data = {\"greeting\": \"hello\", \"recipient\": \"world\"}\n    content = json.dumps(data).encode(encoding)\n    headers = {\"Content-Type\": \"application/json\"}\n    response = httpx.Response(\n        200,\n        content=content,\n        headers=headers,\n    )\n    assert response.json() == data\n\n\n@pytest.mark.parametrize(\n    \"encoding\",\n    [\n        \"utf-8\",\n        \"utf-8-sig\",\n        \"utf-16\",\n        \"utf-16-be\",\n        \"utf-16-le\",\n        \"utf-32\",\n        \"utf-32-be\",\n        \"utf-32-le\",\n    ],\n)\ndef test_json_with_specified_charset(encoding):\n    data = {\"greeting\": \"hello\", \"recipient\": \"world\"}\n    content = json.dumps(data).encode(encoding)\n    headers = {\"Content-Type\": f\"application/json; charset={encoding}\"}\n    response = httpx.Response(\n        200,\n        content=content,\n        headers=headers,\n    )\n    assert response.json() == data\n\n\n@pytest.mark.parametrize(\n    \"headers, expected\",\n    [\n        (\n            {\"Link\": \"<https://example.com>; rel='preload'\"},\n            {\"preload\": {\"rel\": \"preload\", \"url\": \"https://example.com\"}},\n        ),\n        (\n            {\"Link\": '</hub>; rel=\"hub\", </resource>; rel=\"self\"'},\n            {\n                \"hub\": {\"url\": \"/hub\", \"rel\": \"hub\"},\n                \"self\": {\"url\": \"/resource\", \"rel\": \"self\"},\n            },\n        ),\n    ],\n)\ndef test_link_headers(headers, expected):\n    response = httpx.Response(\n        200,\n        content=None,\n        headers=headers,\n    )\n    assert response.links == expected\n\n\n@pytest.mark.parametrize(\"header_value\", (b\"deflate\", b\"gzip\", b\"br\"))\ndef test_decode_error_with_request(header_value):\n    headers = [(b\"Content-Encoding\", header_value)]\n    broken_compressed_body = b\"xxxxxxxxxxxxxx\"\n    with pytest.raises(httpx.DecodingError):\n        httpx.Response(\n            200,\n            headers=headers,\n            content=broken_compressed_body,\n        )\n\n    with pytest.raises(httpx.DecodingError):\n        httpx.Response(\n            200,\n            headers=headers,\n            content=broken_compressed_body,\n            request=httpx.Request(\"GET\", \"https://www.example.org/\"),\n        )\n\n\n@pytest.mark.parametrize(\"header_value\", (b\"deflate\", b\"gzip\", b\"br\"))\ndef test_value_error_without_request(header_value):\n    headers = [(b\"Content-Encoding\", header_value)]\n    broken_compressed_body = b\"xxxxxxxxxxxxxx\"\n    with pytest.raises(httpx.DecodingError):\n        httpx.Response(200, headers=headers, content=broken_compressed_body)\n\n\ndef test_response_with_unset_request():\n    response = httpx.Response(200, content=b\"Hello, world!\")\n\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    assert response.text == \"Hello, world!\"\n    assert not response.is_error\n\n\ndef test_set_request_after_init():\n    response = httpx.Response(200, content=b\"Hello, world!\")\n\n    response.request = httpx.Request(\"GET\", \"https://www.example.org\")\n\n    assert response.request.method == \"GET\"\n    assert response.request.url == \"https://www.example.org\"\n\n\ndef test_cannot_access_unset_request():\n    response = httpx.Response(200, content=b\"Hello, world!\")\n\n    with pytest.raises(RuntimeError):\n        response.request  # noqa: B018\n\n\ndef test_generator_with_transfer_encoding_header():\n    def content() -> typing.Iterator[bytes]:\n        yield b\"test 123\"  # pragma: no cover\n\n    response = httpx.Response(200, content=content())\n    assert response.headers == {\"Transfer-Encoding\": \"chunked\"}\n\n\ndef test_generator_with_content_length_header():\n    def content() -> typing.Iterator[bytes]:\n        yield b\"test 123\"  # pragma: no cover\n\n    headers = {\"Content-Length\": \"8\"}\n    response = httpx.Response(200, content=content(), headers=headers)\n    assert response.headers == {\"Content-Length\": \"8\"}\n\n\ndef test_response_picklable():\n    response = httpx.Response(\n        200,\n        content=b\"Hello, world!\",\n        request=httpx.Request(\"GET\", \"https://example.org\"),\n    )\n    pickle_response = pickle.loads(pickle.dumps(response))\n    assert pickle_response.is_closed is True\n    assert pickle_response.is_stream_consumed is True\n    assert pickle_response.next_request is None\n    assert pickle_response.stream is not None\n    assert pickle_response.content == b\"Hello, world!\"\n    assert pickle_response.status_code == 200\n    assert pickle_response.request.url == response.request.url\n    assert pickle_response.extensions == {}\n    assert pickle_response.history == []\n\n\n@pytest.mark.anyio\nasync def test_response_async_streaming_picklable():\n    response = httpx.Response(200, content=async_streaming_body())\n    pickle_response = pickle.loads(pickle.dumps(response))\n    with pytest.raises(httpx.ResponseNotRead):\n        pickle_response.content  # noqa: B018\n    with pytest.raises(httpx.StreamClosed):\n        await pickle_response.aread()\n    assert pickle_response.is_stream_consumed is False\n    assert pickle_response.num_bytes_downloaded == 0\n    assert pickle_response.headers == {\"Transfer-Encoding\": \"chunked\"}\n\n    response = httpx.Response(200, content=async_streaming_body())\n    await response.aread()\n    pickle_response = pickle.loads(pickle.dumps(response))\n    assert pickle_response.is_stream_consumed is True\n    assert pickle_response.content == b\"Hello, world!\"\n    assert pickle_response.num_bytes_downloaded == 13\n\n\ndef test_response_decode_text_using_autodetect():\n    # Ensure that a 'default_encoding=\"autodetect\"' on the response allows for\n    # encoding autodetection to be used when no \"Content-Type: text/plain; charset=...\"\n    # info is present.\n    #\n    # Here we have some french text encoded with ISO-8859-1, rather than UTF-8.\n    text = (\n        \"Non-seulement Despréaux ne se trompait pas, mais de tous les écrivains \"\n        \"que la France a produits, sans excepter Voltaire lui-même, imprégné de \"\n        \"l'esprit anglais par son séjour à Londres, c'est incontestablement \"\n        \"Molière ou Poquelin qui reproduit avec l'exactitude la plus vive et la \"\n        \"plus complète le fond du génie français.\"\n    )\n    content = text.encode(\"ISO-8859-1\")\n    response = httpx.Response(200, content=content, default_encoding=autodetect)\n\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    # The encoded byte string is consistent with either ISO-8859-1 or\n    # WINDOWS-1252. Versions <6.0 of chardet claim the former, while chardet\n    # 6.0 detects the latter.\n    assert response.encoding in (\"ISO-8859-1\", \"WINDOWS-1252\")\n    assert response.text == text\n\n\ndef test_response_decode_text_using_explicit_encoding():\n    # Ensure that a 'default_encoding=\"...\"' on the response is used for text decoding\n    # when no \"Content-Type: text/plain; charset=...\"\" info is present.\n    #\n    # Here we have some french text encoded with Windows-1252, rather than UTF-8.\n    # https://en.wikipedia.org/wiki/Windows-1252\n    text = (\n        \"Non-seulement Despréaux ne se trompait pas, mais de tous les écrivains \"\n        \"que la France a produits, sans excepter Voltaire lui-même, imprégné de \"\n        \"l'esprit anglais par son séjour à Londres, c'est incontestablement \"\n        \"Molière ou Poquelin qui reproduit avec l'exactitude la plus vive et la \"\n        \"plus complète le fond du génie français.\"\n    )\n    content = text.encode(\"cp1252\")\n    response = httpx.Response(200, content=content, default_encoding=\"cp1252\")\n\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    assert response.encoding == \"cp1252\"\n    assert response.text == text\n"
  },
  {
    "path": "tests/models/test_url.py",
    "content": "import pytest\n\nimport httpx\n\n# Tests for `httpx.URL` instantiation and property accessors.\n\n\ndef test_basic_url():\n    url = httpx.URL(\"https://www.example.com/\")\n\n    assert url.scheme == \"https\"\n    assert url.userinfo == b\"\"\n    assert url.netloc == b\"www.example.com\"\n    assert url.host == \"www.example.com\"\n    assert url.port is None\n    assert url.path == \"/\"\n    assert url.query == b\"\"\n    assert url.fragment == \"\"\n\n    assert str(url) == \"https://www.example.com/\"\n    assert repr(url) == \"URL('https://www.example.com/')\"\n\n\ndef test_complete_url():\n    url = httpx.URL(\"https://example.org:123/path/to/somewhere?abc=123#anchor\")\n    assert url.scheme == \"https\"\n    assert url.host == \"example.org\"\n    assert url.port == 123\n    assert url.path == \"/path/to/somewhere\"\n    assert url.query == b\"abc=123\"\n    assert url.raw_path == b\"/path/to/somewhere?abc=123\"\n    assert url.fragment == \"anchor\"\n\n    assert str(url) == \"https://example.org:123/path/to/somewhere?abc=123#anchor\"\n    assert (\n        repr(url) == \"URL('https://example.org:123/path/to/somewhere?abc=123#anchor')\"\n    )\n\n\ndef test_url_with_empty_query():\n    \"\"\"\n    URLs with and without a trailing `?` but an empty query component\n    should preserve the information on the raw path.\n    \"\"\"\n    url = httpx.URL(\"https://www.example.com/path\")\n    assert url.path == \"/path\"\n    assert url.query == b\"\"\n    assert url.raw_path == b\"/path\"\n\n    url = httpx.URL(\"https://www.example.com/path?\")\n    assert url.path == \"/path\"\n    assert url.query == b\"\"\n    assert url.raw_path == b\"/path?\"\n\n\ndef test_url_no_scheme():\n    url = httpx.URL(\"://example.com\")\n    assert url.scheme == \"\"\n    assert url.host == \"example.com\"\n    assert url.path == \"/\"\n\n\ndef test_url_no_authority():\n    url = httpx.URL(\"http://\")\n    assert url.scheme == \"http\"\n    assert url.host == \"\"\n    assert url.path == \"/\"\n\n\n# Tests for percent encoding across path, query, and fragment...\n\n\n@pytest.mark.parametrize(\n    \"url,raw_path,path,query,fragment\",\n    [\n        # URL with unescaped chars in path.\n        (\n            \"https://example.com/!$&'()*+,;= abc ABC 123 :/[]@\",\n            b\"/!$&'()*+,;=%20abc%20ABC%20123%20:/[]@\",\n            \"/!$&'()*+,;= abc ABC 123 :/[]@\",\n            b\"\",\n            \"\",\n        ),\n        # URL with escaped chars in path.\n        (\n            \"https://example.com/!$&'()*+,;=%20abc%20ABC%20123%20:/[]@\",\n            b\"/!$&'()*+,;=%20abc%20ABC%20123%20:/[]@\",\n            \"/!$&'()*+,;= abc ABC 123 :/[]@\",\n            b\"\",\n            \"\",\n        ),\n        # URL with mix of unescaped and escaped chars in path.\n        # WARNING: This has the incorrect behaviour, adding the test as an interim step.\n        (\n            \"https://example.com/ %61%62%63\",\n            b\"/%20%61%62%63\",\n            \"/ abc\",\n            b\"\",\n            \"\",\n        ),\n        # URL with unescaped chars in query.\n        (\n            \"https://example.com/?!$&'()*+,;= abc ABC 123 :/[]@?\",\n            b\"/?!$&'()*+,;=%20abc%20ABC%20123%20:/[]@?\",\n            \"/\",\n            b\"!$&'()*+,;=%20abc%20ABC%20123%20:/[]@?\",\n            \"\",\n        ),\n        # URL with escaped chars in query.\n        (\n            \"https://example.com/?!$&%27()*+,;=%20abc%20ABC%20123%20:%2F[]@?\",\n            b\"/?!$&%27()*+,;=%20abc%20ABC%20123%20:%2F[]@?\",\n            \"/\",\n            b\"!$&%27()*+,;=%20abc%20ABC%20123%20:%2F[]@?\",\n            \"\",\n        ),\n        # URL with mix of unescaped and escaped chars in query.\n        (\n            \"https://example.com/?%20%97%98%99\",\n            b\"/?%20%97%98%99\",\n            \"/\",\n            b\"%20%97%98%99\",\n            \"\",\n        ),\n        # URL encoding characters in fragment.\n        (\n            \"https://example.com/#!$&'()*+,;= abc ABC 123 :/[]@?#\",\n            b\"/\",\n            \"/\",\n            b\"\",\n            \"!$&'()*+,;= abc ABC 123 :/[]@?#\",\n        ),\n    ],\n)\ndef test_path_query_fragment(url, raw_path, path, query, fragment):\n    url = httpx.URL(url)\n    assert url.raw_path == raw_path\n    assert url.path == path\n    assert url.query == query\n    assert url.fragment == fragment\n\n\ndef test_url_query_encoding():\n    url = httpx.URL(\"https://www.example.com/?a=b c&d=e/f\")\n    assert url.raw_path == b\"/?a=b%20c&d=e/f\"\n\n    url = httpx.URL(\"https://www.example.com/?a=b+c&d=e/f\")\n    assert url.raw_path == b\"/?a=b+c&d=e/f\"\n\n    url = httpx.URL(\"https://www.example.com/\", params={\"a\": \"b c\", \"d\": \"e/f\"})\n    assert url.raw_path == b\"/?a=b+c&d=e%2Ff\"\n\n\ndef test_url_params():\n    url = httpx.URL(\"https://example.org:123/path/to/somewhere\", params={\"a\": \"123\"})\n    assert str(url) == \"https://example.org:123/path/to/somewhere?a=123\"\n    assert url.params == httpx.QueryParams({\"a\": \"123\"})\n\n    url = httpx.URL(\n        \"https://example.org:123/path/to/somewhere?b=456\", params={\"a\": \"123\"}\n    )\n    assert str(url) == \"https://example.org:123/path/to/somewhere?a=123\"\n    assert url.params == httpx.QueryParams({\"a\": \"123\"})\n\n\n# Tests for username and password\n\n\n@pytest.mark.parametrize(\n    \"url,userinfo,username,password\",\n    [\n        # username and password in URL.\n        (\n            \"https://username:password@example.com\",\n            b\"username:password\",\n            \"username\",\n            \"password\",\n        ),\n        # username and password in URL with percent escape sequences.\n        (\n            \"https://username%40gmail.com:pa%20ssword@example.com\",\n            b\"username%40gmail.com:pa%20ssword\",\n            \"username@gmail.com\",\n            \"pa ssword\",\n        ),\n        (\n            \"https://user%20name:p%40ssword@example.com\",\n            b\"user%20name:p%40ssword\",\n            \"user name\",\n            \"p@ssword\",\n        ),\n        # username and password in URL without percent escape sequences.\n        (\n            \"https://username@gmail.com:pa ssword@example.com\",\n            b\"username%40gmail.com:pa%20ssword\",\n            \"username@gmail.com\",\n            \"pa ssword\",\n        ),\n        (\n            \"https://user name:p@ssword@example.com\",\n            b\"user%20name:p%40ssword\",\n            \"user name\",\n            \"p@ssword\",\n        ),\n    ],\n)\ndef test_url_username_and_password(url, userinfo, username, password):\n    url = httpx.URL(url)\n    assert url.userinfo == userinfo\n    assert url.username == username\n    assert url.password == password\n\n\n# Tests for different host types\n\n\ndef test_url_valid_host():\n    url = httpx.URL(\"https://example.com/\")\n    assert url.host == \"example.com\"\n\n\ndef test_url_normalized_host():\n    url = httpx.URL(\"https://EXAMPLE.com/\")\n    assert url.host == \"example.com\"\n\n\ndef test_url_percent_escape_host():\n    url = httpx.URL(\"https://exam le.com/\")\n    assert url.host == \"exam%20le.com\"\n\n\ndef test_url_ipv4_like_host():\n    \"\"\"rare host names used to quality as IPv4\"\"\"\n    url = httpx.URL(\"https://023b76x43144/\")\n    assert url.host == \"023b76x43144\"\n\n\n# Tests for different port types\n\n\ndef test_url_valid_port():\n    url = httpx.URL(\"https://example.com:123/\")\n    assert url.port == 123\n\n\ndef test_url_normalized_port():\n    # If the port matches the scheme default it is normalized to None.\n    url = httpx.URL(\"https://example.com:443/\")\n    assert url.port is None\n\n\ndef test_url_invalid_port():\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(\"https://example.com:abc/\")\n    assert str(exc.value) == \"Invalid port: 'abc'\"\n\n\n# Tests for path handling\n\n\ndef test_url_normalized_path():\n    url = httpx.URL(\"https://example.com/abc/def/../ghi/./jkl\")\n    assert url.path == \"/abc/ghi/jkl\"\n\n\ndef test_url_escaped_path():\n    url = httpx.URL(\"https://example.com/ /🌟/\")\n    assert url.raw_path == b\"/%20/%F0%9F%8C%9F/\"\n\n\ndef test_url_leading_dot_prefix_on_absolute_url():\n    url = httpx.URL(\"https://example.com/../abc\")\n    assert url.path == \"/abc\"\n\n\ndef test_url_leading_dot_prefix_on_relative_url():\n    url = httpx.URL(\"../abc\")\n    assert url.path == \"../abc\"\n\n\n# Tests for query parameter percent encoding.\n#\n# Percent-encoding in `params={}` should match browser form behavior.\n\n\ndef test_param_with_space():\n    # Params passed as form key-value pairs should be form escaped,\n    # Including the special case of \"+\" for space seperators.\n    url = httpx.URL(\"http://webservice\", params={\"u\": \"with spaces\"})\n    assert str(url) == \"http://webservice?u=with+spaces\"\n\n\ndef test_param_requires_encoding():\n    # Params passed as form key-value pairs should be escaped.\n    url = httpx.URL(\"http://webservice\", params={\"u\": \"%\"})\n    assert str(url) == \"http://webservice?u=%25\"\n\n\ndef test_param_with_percent_encoded():\n    # Params passed as form key-value pairs should always be escaped,\n    # even if they include a valid escape sequence.\n    # We want to match browser form behaviour here.\n    url = httpx.URL(\"http://webservice\", params={\"u\": \"with%20spaces\"})\n    assert str(url) == \"http://webservice?u=with%2520spaces\"\n\n\ndef test_param_with_existing_escape_requires_encoding():\n    # Params passed as form key-value pairs should always be escaped,\n    # even if they include a valid escape sequence.\n    # We want to match browser form behaviour here.\n    url = httpx.URL(\"http://webservice\", params={\"u\": \"http://example.com?q=foo%2Fa\"})\n    assert str(url) == \"http://webservice?u=http%3A%2F%2Fexample.com%3Fq%3Dfoo%252Fa\"\n\n\n# Tests for query parameter percent encoding.\n#\n# Percent-encoding in `url={}` should match browser URL bar behavior.\n\n\ndef test_query_with_existing_percent_encoding():\n    # Valid percent encoded sequences should not be double encoded.\n    url = httpx.URL(\"http://webservice?u=phrase%20with%20spaces\")\n    assert str(url) == \"http://webservice?u=phrase%20with%20spaces\"\n\n\ndef test_query_requiring_percent_encoding():\n    # Characters that require percent encoding should be encoded.\n    url = httpx.URL(\"http://webservice?u=phrase with spaces\")\n    assert str(url) == \"http://webservice?u=phrase%20with%20spaces\"\n\n\ndef test_query_with_mixed_percent_encoding():\n    # When a mix of encoded and unencoded characters are present,\n    # characters that require percent encoding should be encoded,\n    # while existing sequences should not be double encoded.\n    url = httpx.URL(\"http://webservice?u=phrase%20with spaces\")\n    assert str(url) == \"http://webservice?u=phrase%20with%20spaces\"\n\n\n# Tests for invalid URLs\n\n\ndef test_url_invalid_hostname():\n    \"\"\"\n    Ensure that invalid URLs raise an `httpx.InvalidURL` exception.\n    \"\"\"\n    with pytest.raises(httpx.InvalidURL):\n        httpx.URL(\"https://😇/\")\n\n\ndef test_url_excessively_long_url():\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(\"https://www.example.com/\" + \"x\" * 100_000)\n    assert str(exc.value) == \"URL too long\"\n\n\ndef test_url_excessively_long_component():\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(\"https://www.example.com\", path=\"/\" + \"x\" * 100_000)\n    assert str(exc.value) == \"URL component 'path' too long\"\n\n\ndef test_url_non_printing_character_in_url():\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(\"https://www.example.com/\\n\")\n    assert str(exc.value) == (\n        \"Invalid non-printable ASCII character in URL, '\\\\n' at position 24.\"\n    )\n\n\ndef test_url_non_printing_character_in_component():\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(\"https://www.example.com\", path=\"/\\n\")\n    assert str(exc.value) == (\n        \"Invalid non-printable ASCII character in URL path component, \"\n        \"'\\\\n' at position 1.\"\n    )\n\n\n# Test for url components\n\n\ndef test_url_with_components():\n    url = httpx.URL(scheme=\"https\", host=\"www.example.com\", path=\"/\")\n\n    assert url.scheme == \"https\"\n    assert url.userinfo == b\"\"\n    assert url.host == \"www.example.com\"\n    assert url.port is None\n    assert url.path == \"/\"\n    assert url.query == b\"\"\n    assert url.fragment == \"\"\n\n    assert str(url) == \"https://www.example.com/\"\n\n\ndef test_urlparse_with_invalid_component():\n    with pytest.raises(TypeError) as exc:\n        httpx.URL(scheme=\"https\", host=\"www.example.com\", incorrect=\"/\")\n    assert str(exc.value) == \"'incorrect' is an invalid keyword argument for URL()\"\n\n\ndef test_urlparse_with_invalid_scheme():\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(scheme=\"~\", host=\"www.example.com\", path=\"/\")\n    assert str(exc.value) == \"Invalid URL component 'scheme'\"\n\n\ndef test_urlparse_with_invalid_path():\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(scheme=\"https\", host=\"www.example.com\", path=\"abc\")\n    assert str(exc.value) == \"For absolute URLs, path must be empty or begin with '/'\"\n\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(path=\"//abc\")\n    assert str(exc.value) == \"Relative URLs cannot have a path starting with '//'\"\n\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(path=\":abc\")\n    assert str(exc.value) == \"Relative URLs cannot have a path starting with ':'\"\n\n\ndef test_url_with_relative_path():\n    # This path would be invalid for an absolute URL, but is valid as a relative URL.\n    url = httpx.URL(path=\"abc\")\n    assert url.path == \"abc\"\n\n\n# Tests for `httpx.URL` python built-in operators.\n\n\ndef test_url_eq_str():\n    \"\"\"\n    Ensure that `httpx.URL` supports the equality operator.\n    \"\"\"\n    url = httpx.URL(\"https://example.org:123/path/to/somewhere?abc=123#anchor\")\n    assert url == \"https://example.org:123/path/to/somewhere?abc=123#anchor\"\n    assert str(url) == url\n\n\ndef test_url_set():\n    \"\"\"\n    Ensure that `httpx.URL` instances can be used in sets.\n    \"\"\"\n    urls = (\n        httpx.URL(\"http://example.org:123/path/to/somewhere\"),\n        httpx.URL(\"http://example.org:123/path/to/somewhere/else\"),\n    )\n\n    url_set = set(urls)\n\n    assert all(url in urls for url in url_set)\n\n\n# Tests for TypeErrors when instantiating `httpx.URL`.\n\n\ndef test_url_invalid_type():\n    \"\"\"\n    Ensure that invalid types on `httpx.URL()` raise a `TypeError`.\n    \"\"\"\n\n    class ExternalURLClass:  # representing external URL class\n        pass\n\n    with pytest.raises(TypeError):\n        httpx.URL(ExternalURLClass())  # type: ignore\n\n\ndef test_url_with_invalid_component():\n    with pytest.raises(TypeError) as exc:\n        httpx.URL(scheme=\"https\", host=\"www.example.com\", incorrect=\"/\")\n    assert str(exc.value) == \"'incorrect' is an invalid keyword argument for URL()\"\n\n\n# Tests for `URL.join()`.\n\n\ndef test_url_join():\n    \"\"\"\n    Some basic URL joining tests.\n    \"\"\"\n    url = httpx.URL(\"https://example.org:123/path/to/somewhere\")\n    assert url.join(\"/somewhere-else\") == \"https://example.org:123/somewhere-else\"\n    assert (\n        url.join(\"somewhere-else\") == \"https://example.org:123/path/to/somewhere-else\"\n    )\n    assert (\n        url.join(\"../somewhere-else\") == \"https://example.org:123/path/somewhere-else\"\n    )\n    assert url.join(\"../../somewhere-else\") == \"https://example.org:123/somewhere-else\"\n\n\ndef test_relative_url_join():\n    url = httpx.URL(\"/path/to/somewhere\")\n    assert url.join(\"/somewhere-else\") == \"/somewhere-else\"\n    assert url.join(\"somewhere-else\") == \"/path/to/somewhere-else\"\n    assert url.join(\"../somewhere-else\") == \"/path/somewhere-else\"\n    assert url.join(\"../../somewhere-else\") == \"/somewhere-else\"\n\n\ndef test_url_join_rfc3986():\n    \"\"\"\n    URL joining tests, as-per reference examples in RFC 3986.\n\n    https://tools.ietf.org/html/rfc3986#section-5.4\n    \"\"\"\n\n    url = httpx.URL(\"http://example.com/b/c/d;p?q\")\n\n    assert url.join(\"g\") == \"http://example.com/b/c/g\"\n    assert url.join(\"./g\") == \"http://example.com/b/c/g\"\n    assert url.join(\"g/\") == \"http://example.com/b/c/g/\"\n    assert url.join(\"/g\") == \"http://example.com/g\"\n    assert url.join(\"//g\") == \"http://g\"\n    assert url.join(\"?y\") == \"http://example.com/b/c/d;p?y\"\n    assert url.join(\"g?y\") == \"http://example.com/b/c/g?y\"\n    assert url.join(\"#s\") == \"http://example.com/b/c/d;p?q#s\"\n    assert url.join(\"g#s\") == \"http://example.com/b/c/g#s\"\n    assert url.join(\"g?y#s\") == \"http://example.com/b/c/g?y#s\"\n    assert url.join(\";x\") == \"http://example.com/b/c/;x\"\n    assert url.join(\"g;x\") == \"http://example.com/b/c/g;x\"\n    assert url.join(\"g;x?y#s\") == \"http://example.com/b/c/g;x?y#s\"\n    assert url.join(\"\") == \"http://example.com/b/c/d;p?q\"\n    assert url.join(\".\") == \"http://example.com/b/c/\"\n    assert url.join(\"./\") == \"http://example.com/b/c/\"\n    assert url.join(\"..\") == \"http://example.com/b/\"\n    assert url.join(\"../\") == \"http://example.com/b/\"\n    assert url.join(\"../g\") == \"http://example.com/b/g\"\n    assert url.join(\"../..\") == \"http://example.com/\"\n    assert url.join(\"../../\") == \"http://example.com/\"\n    assert url.join(\"../../g\") == \"http://example.com/g\"\n\n    assert url.join(\"../../../g\") == \"http://example.com/g\"\n    assert url.join(\"../../../../g\") == \"http://example.com/g\"\n\n    assert url.join(\"/./g\") == \"http://example.com/g\"\n    assert url.join(\"/../g\") == \"http://example.com/g\"\n    assert url.join(\"g.\") == \"http://example.com/b/c/g.\"\n    assert url.join(\".g\") == \"http://example.com/b/c/.g\"\n    assert url.join(\"g..\") == \"http://example.com/b/c/g..\"\n    assert url.join(\"..g\") == \"http://example.com/b/c/..g\"\n\n    assert url.join(\"./../g\") == \"http://example.com/b/g\"\n    assert url.join(\"./g/.\") == \"http://example.com/b/c/g/\"\n    assert url.join(\"g/./h\") == \"http://example.com/b/c/g/h\"\n    assert url.join(\"g/../h\") == \"http://example.com/b/c/h\"\n    assert url.join(\"g;x=1/./y\") == \"http://example.com/b/c/g;x=1/y\"\n    assert url.join(\"g;x=1/../y\") == \"http://example.com/b/c/y\"\n\n    assert url.join(\"g?y/./x\") == \"http://example.com/b/c/g?y/./x\"\n    assert url.join(\"g?y/../x\") == \"http://example.com/b/c/g?y/../x\"\n    assert url.join(\"g#s/./x\") == \"http://example.com/b/c/g#s/./x\"\n    assert url.join(\"g#s/../x\") == \"http://example.com/b/c/g#s/../x\"\n\n\ndef test_resolution_error_1833():\n    \"\"\"\n    See https://github.com/encode/httpx/issues/1833\n    \"\"\"\n    url = httpx.URL(\"https://example.com/?[]\")\n    assert url.join(\"/\") == \"https://example.com/\"\n\n\n# Tests for `URL.copy_with()`.\n\n\ndef test_copy_with():\n    url = httpx.URL(\"https://www.example.com/\")\n    assert str(url) == \"https://www.example.com/\"\n\n    url = url.copy_with()\n    assert str(url) == \"https://www.example.com/\"\n\n    url = url.copy_with(scheme=\"http\")\n    assert str(url) == \"http://www.example.com/\"\n\n    url = url.copy_with(netloc=b\"example.com\")\n    assert str(url) == \"http://example.com/\"\n\n    url = url.copy_with(path=\"/abc\")\n    assert str(url) == \"http://example.com/abc\"\n\n\ndef test_url_copywith_authority_subcomponents():\n    copy_with_kwargs = {\n        \"username\": \"username\",\n        \"password\": \"password\",\n        \"port\": 444,\n        \"host\": \"example.net\",\n    }\n    url = httpx.URL(\"https://example.org\")\n    new = url.copy_with(**copy_with_kwargs)\n    assert str(new) == \"https://username:password@example.net:444\"\n\n\ndef test_url_copywith_netloc():\n    copy_with_kwargs = {\n        \"netloc\": b\"example.net:444\",\n    }\n    url = httpx.URL(\"https://example.org\")\n    new = url.copy_with(**copy_with_kwargs)\n    assert str(new) == \"https://example.net:444\"\n\n\ndef test_url_copywith_userinfo_subcomponents():\n    copy_with_kwargs = {\n        \"username\": \"tom@example.org\",\n        \"password\": \"abc123@ %\",\n    }\n    url = httpx.URL(\"https://example.org\")\n    new = url.copy_with(**copy_with_kwargs)\n    assert str(new) == \"https://tom%40example.org:abc123%40%20%@example.org\"\n    assert new.username == \"tom@example.org\"\n    assert new.password == \"abc123@ %\"\n    assert new.userinfo == b\"tom%40example.org:abc123%40%20%\"\n\n\ndef test_url_copywith_invalid_component():\n    url = httpx.URL(\"https://example.org\")\n    with pytest.raises(TypeError):\n        url.copy_with(pathh=\"/incorrect-spelling\")\n    with pytest.raises(TypeError):\n        url.copy_with(userinfo=\"should be bytes\")\n\n\ndef test_url_copywith_urlencoded_path():\n    url = httpx.URL(\"https://example.org\")\n    url = url.copy_with(path=\"/path to somewhere\")\n    assert url.path == \"/path to somewhere\"\n    assert url.query == b\"\"\n    assert url.raw_path == b\"/path%20to%20somewhere\"\n\n\ndef test_url_copywith_query():\n    url = httpx.URL(\"https://example.org\")\n    url = url.copy_with(query=b\"a=123\")\n    assert url.path == \"/\"\n    assert url.query == b\"a=123\"\n    assert url.raw_path == b\"/?a=123\"\n\n\ndef test_url_copywith_raw_path():\n    url = httpx.URL(\"https://example.org\")\n    url = url.copy_with(raw_path=b\"/some/path\")\n    assert url.path == \"/some/path\"\n    assert url.query == b\"\"\n    assert url.raw_path == b\"/some/path\"\n\n    url = httpx.URL(\"https://example.org\")\n    url = url.copy_with(raw_path=b\"/some/path?\")\n    assert url.path == \"/some/path\"\n    assert url.query == b\"\"\n    assert url.raw_path == b\"/some/path?\"\n\n    url = httpx.URL(\"https://example.org\")\n    url = url.copy_with(raw_path=b\"/some/path?a=123\")\n    assert url.path == \"/some/path\"\n    assert url.query == b\"a=123\"\n    assert url.raw_path == b\"/some/path?a=123\"\n\n\ndef test_url_copywith_security():\n    \"\"\"\n    Prevent unexpected changes on URL after calling copy_with (CVE-2021-41945)\n    \"\"\"\n    with pytest.raises(httpx.InvalidURL):\n        httpx.URL(\"https://u:p@[invalid!]//evilHost/path?t=w#tw\")\n\n    url = httpx.URL(\"https://example.com/path?t=w#tw\")\n    bad = \"https://xxxx:xxxx@xxxxxxx/xxxxx/xxx?x=x#xxxxx\"\n    with pytest.raises(httpx.InvalidURL):\n        url.copy_with(scheme=bad)\n\n\n# Tests for copy-modifying-parameters methods.\n#\n# `URL.copy_set_param()`\n# `URL.copy_add_param()`\n# `URL.copy_remove_param()`\n# `URL.copy_merge_params()`\n\n\ndef test_url_set_param_manipulation():\n    \"\"\"\n    Some basic URL query parameter manipulation.\n    \"\"\"\n    url = httpx.URL(\"https://example.org:123/?a=123\")\n    assert url.copy_set_param(\"a\", \"456\") == \"https://example.org:123/?a=456\"\n\n\ndef test_url_add_param_manipulation():\n    \"\"\"\n    Some basic URL query parameter manipulation.\n    \"\"\"\n    url = httpx.URL(\"https://example.org:123/?a=123\")\n    assert url.copy_add_param(\"a\", \"456\") == \"https://example.org:123/?a=123&a=456\"\n\n\ndef test_url_remove_param_manipulation():\n    \"\"\"\n    Some basic URL query parameter manipulation.\n    \"\"\"\n    url = httpx.URL(\"https://example.org:123/?a=123\")\n    assert url.copy_remove_param(\"a\") == \"https://example.org:123/\"\n\n\ndef test_url_merge_params_manipulation():\n    \"\"\"\n    Some basic URL query parameter manipulation.\n    \"\"\"\n    url = httpx.URL(\"https://example.org:123/?a=123\")\n    assert url.copy_merge_params({\"b\": \"456\"}) == \"https://example.org:123/?a=123&b=456\"\n\n\n# Tests for IDNA hostname support.\n\n\n@pytest.mark.parametrize(\n    \"given,idna,host,raw_host,scheme,port\",\n    [\n        (\n            \"http://中国.icom.museum:80/\",\n            \"http://xn--fiqs8s.icom.museum:80/\",\n            \"中国.icom.museum\",\n            b\"xn--fiqs8s.icom.museum\",\n            \"http\",\n            None,\n        ),\n        (\n            \"http://Königsgäßchen.de\",\n            \"http://xn--knigsgchen-b4a3dun.de\",\n            \"königsgäßchen.de\",\n            b\"xn--knigsgchen-b4a3dun.de\",\n            \"http\",\n            None,\n        ),\n        (\n            \"https://faß.de\",\n            \"https://xn--fa-hia.de\",\n            \"faß.de\",\n            b\"xn--fa-hia.de\",\n            \"https\",\n            None,\n        ),\n        (\n            \"https://βόλος.com:443\",\n            \"https://xn--nxasmm1c.com:443\",\n            \"βόλος.com\",\n            b\"xn--nxasmm1c.com\",\n            \"https\",\n            None,\n        ),\n        (\n            \"http://ශ්‍රී.com:444\",\n            \"http://xn--10cl1a0b660p.com:444\",\n            \"ශ්‍රී.com\",\n            b\"xn--10cl1a0b660p.com\",\n            \"http\",\n            444,\n        ),\n        (\n            \"https://نامه‌ای.com:4433\",\n            \"https://xn--mgba3gch31f060k.com:4433\",\n            \"نامه‌ای.com\",\n            b\"xn--mgba3gch31f060k.com\",\n            \"https\",\n            4433,\n        ),\n    ],\n    ids=[\n        \"http_with_port\",\n        \"unicode_tr46_compat\",\n        \"https_without_port\",\n        \"https_with_port\",\n        \"http_with_custom_port\",\n        \"https_with_custom_port\",\n    ],\n)\ndef test_idna_url(given, idna, host, raw_host, scheme, port):\n    url = httpx.URL(given)\n    assert url == httpx.URL(idna)\n    assert url.host == host\n    assert url.raw_host == raw_host\n    assert url.scheme == scheme\n    assert url.port == port\n\n\ndef test_url_unescaped_idna_host():\n    url = httpx.URL(\"https://中国.icom.museum/\")\n    assert url.raw_host == b\"xn--fiqs8s.icom.museum\"\n\n\ndef test_url_escaped_idna_host():\n    url = httpx.URL(\"https://xn--fiqs8s.icom.museum/\")\n    assert url.raw_host == b\"xn--fiqs8s.icom.museum\"\n\n\ndef test_url_invalid_idna_host():\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(\"https://☃.com/\")\n    assert str(exc.value) == \"Invalid IDNA hostname: '☃.com'\"\n\n\n# Tests for IPv4 hostname support.\n\n\ndef test_url_valid_ipv4():\n    url = httpx.URL(\"https://1.2.3.4/\")\n    assert url.host == \"1.2.3.4\"\n\n\ndef test_url_invalid_ipv4():\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(\"https://999.999.999.999/\")\n    assert str(exc.value) == \"Invalid IPv4 address: '999.999.999.999'\"\n\n\n# Tests for IPv6 hostname support.\n\n\ndef test_ipv6_url():\n    url = httpx.URL(\"http://[::ffff:192.168.0.1]:5678/\")\n\n    assert url.host == \"::ffff:192.168.0.1\"\n    assert url.netloc == b\"[::ffff:192.168.0.1]:5678\"\n\n\ndef test_url_valid_ipv6():\n    url = httpx.URL(\"https://[2001:db8::ff00:42:8329]/\")\n    assert url.host == \"2001:db8::ff00:42:8329\"\n\n\ndef test_url_invalid_ipv6():\n    with pytest.raises(httpx.InvalidURL) as exc:\n        httpx.URL(\"https://[2001]/\")\n    assert str(exc.value) == \"Invalid IPv6 address: '[2001]'\"\n\n\n@pytest.mark.parametrize(\"host\", [\"[::ffff:192.168.0.1]\", \"::ffff:192.168.0.1\"])\ndef test_ipv6_url_from_raw_url(host):\n    url = httpx.URL(scheme=\"https\", host=host, port=443, path=\"/\")\n\n    assert url.host == \"::ffff:192.168.0.1\"\n    assert url.netloc == b\"[::ffff:192.168.0.1]\"\n    assert str(url) == \"https://[::ffff:192.168.0.1]/\"\n\n\n@pytest.mark.parametrize(\n    \"url_str\",\n    [\n        \"http://127.0.0.1:1234\",\n        \"http://example.com:1234\",\n        \"http://[::ffff:127.0.0.1]:1234\",\n    ],\n)\n@pytest.mark.parametrize(\"new_host\", [\"[::ffff:192.168.0.1]\", \"::ffff:192.168.0.1\"])\ndef test_ipv6_url_copy_with_host(url_str, new_host):\n    url = httpx.URL(url_str).copy_with(host=new_host)\n\n    assert url.host == \"::ffff:192.168.0.1\"\n    assert url.netloc == b\"[::ffff:192.168.0.1]:1234\"\n    assert str(url) == \"http://[::ffff:192.168.0.1]:1234\"\n"
  },
  {
    "path": "tests/models/test_whatwg.py",
    "content": "# The WHATWG have various tests that can be used to validate the URL parsing.\n#\n# https://url.spec.whatwg.org/\n\nimport json\n\nimport pytest\n\nfrom httpx._urlparse import urlparse\n\n# URL test cases from...\n# https://github.com/web-platform-tests/wpt/blob/master/url/resources/urltestdata.json\nwith open(\"tests/models/whatwg.json\", \"r\", encoding=\"utf-8\") as input:\n    test_cases = json.load(input)\n    test_cases = [\n        item\n        for item in test_cases\n        if not isinstance(item, str) and not item.get(\"failure\")\n    ]\n\n\n@pytest.mark.parametrize(\"test_case\", test_cases)\ndef test_urlparse(test_case):\n    if test_case[\"href\"] in (\"a: foo.com\", \"lolscheme:x x#x%20x\"):\n        # Skip these two test cases.\n        # WHATWG cases where are not using percent-encoding for the space character.\n        # Anyone know what's going on here?\n        return\n\n    p = urlparse(test_case[\"href\"])\n\n    # Test cases include the protocol with the trailing \":\"\n    protocol = p.scheme + \":\"\n    # Include the square brackets for IPv6 addresses.\n    hostname = f\"[{p.host}]\" if \":\" in p.host else p.host\n    # The test cases use a string representation of the port.\n    port = \"\" if p.port is None else str(p.port)\n    # I have nothing to say about this one.\n    path = p.path\n    # The 'search' and 'hash' components in the whatwg tests are semantic, not literal.\n    # Our parsing differentiates between no query/hash and empty-string query/hash.\n    search = \"\" if p.query in (None, \"\") else \"?\" + str(p.query)\n    hash = \"\" if p.fragment in (None, \"\") else \"#\" + str(p.fragment)\n\n    # URL hostnames are case-insensitive.\n    # We normalize these, unlike the WHATWG test cases.\n    assert protocol == test_case[\"protocol\"]\n    assert hostname.lower() == test_case[\"hostname\"].lower()\n    assert port == test_case[\"port\"]\n    assert path == test_case[\"pathname\"]\n    assert search == test_case[\"search\"]\n    assert hash == test_case[\"hash\"]\n"
  },
  {
    "path": "tests/models/whatwg.json",
    "content": "[\n  \"See ../README.md for a description of the format.\",\n  {\n    \"input\": \"http://example\\t.\\norg\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://user:pass@foo:21/bar;par?b#c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://user:pass@foo:21/bar;par?b#c\",\n    \"origin\": \"http://foo:21\",\n    \"protocol\": \"http:\",\n    \"username\": \"user\",\n    \"password\": \"pass\",\n    \"host\": \"foo:21\",\n    \"hostname\": \"foo\",\n    \"port\": \"21\",\n    \"pathname\": \"/bar;par\",\n    \"search\": \"?b\",\n    \"hash\": \"#c\"\n  },\n  {\n    \"input\": \"https://test:@test\",\n    \"base\": null,\n    \"href\": \"https://test@test/\",\n    \"origin\": \"https://test\",\n    \"protocol\": \"https:\",\n    \"username\": \"test\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"https://:@test\",\n    \"base\": null,\n    \"href\": \"https://test/\",\n    \"origin\": \"https://test\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"non-special://test:@test/x\",\n    \"base\": null,\n    \"href\": \"non-special://test@test/x\",\n    \"origin\": \"null\",\n    \"protocol\": \"non-special:\",\n    \"username\": \"test\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/x\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"non-special://:@test/x\",\n    \"base\": null,\n    \"href\": \"non-special://test/x\",\n    \"origin\": \"null\",\n    \"protocol\": \"non-special:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/x\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:foo.com\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/foo.com\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/foo.com\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"\\t   :foo.com   \\n\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/:foo.com\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/:foo.com\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \" foo.com  \",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/foo.com\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/foo.com\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"a:\\t foo.com\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"a: foo.com\",\n    \"origin\": \"null\",\n    \"protocol\": \"a:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \" foo.com\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://f:21/ b ? d # e \",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://f:21/%20b%20?%20d%20#%20e\",\n    \"origin\": \"http://f:21\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"f:21\",\n    \"hostname\": \"f\",\n    \"port\": \"21\",\n    \"pathname\": \"/%20b%20\",\n    \"search\": \"?%20d%20\",\n    \"hash\": \"#%20e\"\n  },\n  {\n    \"input\": \"lolscheme:x x#x x\",\n    \"base\": null,\n    \"href\": \"lolscheme:x x#x%20x\",\n    \"protocol\": \"lolscheme:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"x x\",\n    \"search\": \"\",\n    \"hash\": \"#x%20x\"\n  },\n  {\n    \"input\": \"http://f:/c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://f/c\",\n    \"origin\": \"http://f\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"f\",\n    \"hostname\": \"f\",\n    \"port\": \"\",\n    \"pathname\": \"/c\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://f:0/c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://f:0/c\",\n    \"origin\": \"http://f:0\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"f:0\",\n    \"hostname\": \"f\",\n    \"port\": \"0\",\n    \"pathname\": \"/c\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://f:00000000000000/c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://f:0/c\",\n    \"origin\": \"http://f:0\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"f:0\",\n    \"hostname\": \"f\",\n    \"port\": \"0\",\n    \"pathname\": \"/c\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://f:00000000000000000000080/c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://f/c\",\n    \"origin\": \"http://f\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"f\",\n    \"hostname\": \"f\",\n    \"port\": \"\",\n    \"pathname\": \"/c\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://f:b/c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://f: /c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://f:\\n/c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://f/c\",\n    \"origin\": \"http://f\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"f\",\n    \"hostname\": \"f\",\n    \"port\": \"\",\n    \"pathname\": \"/c\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://f:fifty-two/c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://f:999999/c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"non-special://f:999999/c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://f: 21 / b ? d # e \",\n    \"base\": \"http://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/bar\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"  \\t\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/bar\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \":foo.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/:foo.com/\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/:foo.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \":foo.com\\\\\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/:foo.com/\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/:foo.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \":\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/:\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \":a\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/:a\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/:a\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \":/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/:/\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \":\\\\\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/:/\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \":#\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/:#\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"#\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/bar#\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"#/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/bar#/\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"#/\"\n  },\n  {\n    \"input\": \"#\\\\\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/bar#\\\\\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"#\\\\\"\n  },\n  {\n    \"input\": \"#;?\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/bar#;?\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"#;?\"\n  },\n  {\n    \"input\": \"?\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/bar?\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \":23\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/:23\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/:23\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/:23\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/:23\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/:23\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"\\\\x\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/x\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/x\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"\\\\\\\\x\\\\hello\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://x/hello\",\n    \"origin\": \"http://x\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"x\",\n    \"hostname\": \"x\",\n    \"port\": \"\",\n    \"pathname\": \"/hello\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"::\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/::\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/::\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"::23\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/::23\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/::23\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"foo://\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"foo://\",\n    \"origin\": \"null\",\n    \"protocol\": \"foo:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://a:b@c:29/d\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://a:b@c:29/d\",\n    \"origin\": \"http://c:29\",\n    \"protocol\": \"http:\",\n    \"username\": \"a\",\n    \"password\": \"b\",\n    \"host\": \"c:29\",\n    \"hostname\": \"c\",\n    \"port\": \"29\",\n    \"pathname\": \"/d\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http::@c:29\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/:@c:29\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/:@c:29\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://&a:foo(b]c@d:2/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://&a:foo(b%5Dc@d:2/\",\n    \"origin\": \"http://d:2\",\n    \"protocol\": \"http:\",\n    \"username\": \"&a\",\n    \"password\": \"foo(b%5Dc\",\n    \"host\": \"d:2\",\n    \"hostname\": \"d\",\n    \"port\": \"2\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://::@c@d:2\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://:%3A%40c@d:2/\",\n    \"origin\": \"http://d:2\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n   \"password\": \"%3A%40c\",\n    \"host\": \"d:2\",\n    \"hostname\": \"d\",\n    \"port\": \"2\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://foo.com:b@d/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://foo.com:b@d/\",\n    \"origin\": \"http://d\",\n    \"protocol\": \"http:\",\n    \"username\": \"foo.com\",\n    \"password\": \"b\",\n    \"host\": \"d\",\n    \"hostname\": \"d\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://foo.com/\\\\@\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://foo.com//@\",\n    \"origin\": \"http://foo.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo.com\",\n    \"hostname\": \"foo.com\",\n    \"port\": \"\",\n    \"pathname\": \"//@\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:\\\\\\\\foo.com\\\\\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://foo.com/\",\n    \"origin\": \"http://foo.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo.com\",\n    \"hostname\": \"foo.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:\\\\\\\\a\\\\b:c\\\\d@foo.com\\\\\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://a/b:c/d@foo.com/\",\n    \"origin\": \"http://a\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"a\",\n    \"hostname\": \"a\",\n    \"port\": \"\",\n    \"pathname\": \"/b:c/d@foo.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://a:b@c\\\\\",\n    \"base\": null,\n    \"href\": \"http://a:b@c/\",\n    \"origin\": \"http://c\",\n    \"protocol\": \"http:\",\n    \"username\": \"a\",\n    \"password\": \"b\",\n    \"host\": \"c\",\n    \"hostname\": \"c\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ws://a@b\\\\c\",\n    \"base\": null,\n    \"href\": \"ws://a@b/c\",\n    \"origin\": \"ws://b\",\n    \"protocol\": \"ws:\",\n    \"username\": \"a\",\n    \"password\": \"\",\n    \"host\": \"b\",\n    \"hostname\": \"b\",\n    \"port\": \"\",\n    \"pathname\": \"/c\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"foo:/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"foo:/\",\n    \"origin\": \"null\",\n    \"protocol\": \"foo:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"foo:/bar.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"foo:/bar.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"foo:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/bar.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"foo://///////\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"foo://///////\",\n    \"origin\": \"null\",\n    \"protocol\": \"foo:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"///////\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"foo://///////bar.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"foo://///////bar.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"foo:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"///////bar.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"foo:////://///\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"foo:////://///\",\n    \"origin\": \"null\",\n    \"protocol\": \"foo:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//://///\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"c:/foo\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"c:/foo\",\n    \"origin\": \"null\",\n    \"protocol\": \"c:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"//foo/bar\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://foo/bar\",\n    \"origin\": \"http://foo\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo\",\n    \"hostname\": \"foo\",\n    \"port\": \"\",\n    \"pathname\": \"/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://foo/path;a??e#f#g\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://foo/path;a??e#f#g\",\n    \"origin\": \"http://foo\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo\",\n    \"hostname\": \"foo\",\n    \"port\": \"\",\n    \"pathname\": \"/path;a\",\n    \"search\": \"??e\",\n    \"hash\": \"#f#g\"\n  },\n  {\n    \"input\": \"http://foo/abcd?efgh?ijkl\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://foo/abcd?efgh?ijkl\",\n    \"origin\": \"http://foo\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo\",\n    \"hostname\": \"foo\",\n    \"port\": \"\",\n    \"pathname\": \"/abcd\",\n    \"search\": \"?efgh?ijkl\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://foo/abcd#foo?bar\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://foo/abcd#foo?bar\",\n    \"origin\": \"http://foo\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo\",\n    \"hostname\": \"foo\",\n    \"port\": \"\",\n    \"pathname\": \"/abcd\",\n    \"search\": \"\",\n    \"hash\": \"#foo?bar\"\n  },\n  {\n    \"input\": \"[61:24:74]:98\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/[61:24:74]:98\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/[61:24:74]:98\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:[61:27]/:foo\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/[61:27]/:foo\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/[61:27]/:foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://[1::2]:3:4\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://2001::1\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://2001::1]\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://2001::1]:80\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[2001::1]\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://[2001::1]/\",\n    \"origin\": \"http://[2001::1]\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"[2001::1]\",\n    \"hostname\": \"[2001::1]\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://[::127.0.0.1]\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://[::7f00:1]/\",\n    \"origin\": \"http://[::7f00:1]\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"[::7f00:1]\",\n    \"hostname\": \"[::7f00:1]\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://[::127.0.0.1.]\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[0:0:0:0:0:0:13.1.68.3]\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://[::d01:4403]/\",\n    \"origin\": \"http://[::d01:4403]\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"[::d01:4403]\",\n    \"hostname\": \"[::d01:4403]\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://[2001::1]:80\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://[2001::1]/\",\n    \"origin\": \"http://[2001::1]\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"[2001::1]\",\n    \"hostname\": \"[2001::1]\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/example.com/\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ftp:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"ftp://example.com/\",\n    \"origin\": \"ftp://example.com\",\n    \"protocol\": \"ftp:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"https:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"https://example.com/\",\n    \"origin\": \"https://example.com\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"madeupscheme:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"madeupscheme:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"madeupscheme:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"file:///example.com/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://example:1/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"file://example:test/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"file://example%/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"file://[example]/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"ftps:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"ftps:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"ftps:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"gopher:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"gopher:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"gopher:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ws:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"ws://example.com/\",\n    \"origin\": \"ws://example.com\",\n    \"protocol\": \"ws:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"wss:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"wss://example.com/\",\n    \"origin\": \"wss://example.com\",\n    \"protocol\": \"wss:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"data:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"data:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"data:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"javascript:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"javascript:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"javascript:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"mailto:/example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"mailto:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"mailto:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/example.com/\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ftp:example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"ftp://example.com/\",\n    \"origin\": \"ftp://example.com\",\n    \"protocol\": \"ftp:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"https:example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"https://example.com/\",\n    \"origin\": \"https://example.com\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"madeupscheme:example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"madeupscheme:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"madeupscheme:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ftps:example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"ftps:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"ftps:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"gopher:example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"gopher:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"gopher:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ws:example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"ws://example.com/\",\n    \"origin\": \"ws://example.com\",\n    \"protocol\": \"ws:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"wss:example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"wss://example.com/\",\n    \"origin\": \"wss://example.com\",\n    \"protocol\": \"wss:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"data:example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"data:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"data:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"javascript:example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"javascript:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"javascript:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"mailto:example.com/\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"mailto:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"mailto:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/a/b/c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/a/b/c\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/a/b/c\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/a/ /c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/a/%20/c\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/a/%20/c\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/a%2fc\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/a%2fc\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/a%2fc\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/a/%2f/c\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/a/%2f/c\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/a/%2f/c\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"#β\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/bar#%CE%B2\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"#%CE%B2\"\n  },\n  {\n    \"input\": \"data:text/html,test#test\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"data:text/html,test#test\",\n    \"origin\": \"null\",\n    \"protocol\": \"data:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"text/html,test\",\n    \"search\": \"\",\n    \"hash\": \"#test\"\n  },\n  {\n    \"input\": \"tel:1234567890\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"tel:1234567890\",\n    \"origin\": \"null\",\n    \"protocol\": \"tel:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"1234567890\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Based on https://felixfbecker.github.io/whatwg-url-custom-host-repro/\",\n  {\n    \"input\": \"ssh://example.com/foo/bar.git\",\n    \"base\": \"http://example.org/\",\n    \"href\": \"ssh://example.com/foo/bar.git\",\n    \"origin\": \"null\",\n    \"protocol\": \"ssh:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar.git\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/file.html\",\n  {\n    \"input\": \"file:c:\\\\foo\\\\bar.html\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///c:/foo/bar.html\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/c:/foo/bar.html\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"  File:c|////foo\\\\bar.html\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///c:////foo/bar.html\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/c:////foo/bar.html\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"C|/foo/bar\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///C:/foo/bar\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/C|\\\\foo\\\\bar\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///C:/foo/bar\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"//C|/foo/bar\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///C:/foo/bar\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"//server/file\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file://server/file\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"server\",\n    \"hostname\": \"server\",\n    \"port\": \"\",\n    \"pathname\": \"/file\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"\\\\\\\\server\\\\file\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file://server/file\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"server\",\n    \"hostname\": \"server\",\n    \"port\": \"\",\n    \"pathname\": \"/file\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/\\\\server/file\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file://server/file\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"server\",\n    \"hostname\": \"server\",\n    \"port\": \"\",\n    \"pathname\": \"/file\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///foo/bar.txt\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///foo/bar.txt\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar.txt\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///home/me\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///home/me\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/home/me\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"//\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///test\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///test\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://test\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file://test/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://localhost\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://localhost/\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://localhost/test\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///test\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"test\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///tmp/mock/test\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/tmp/mock/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:test\",\n    \"base\": \"file:///tmp/mock/path\",\n    \"href\": \"file:///tmp/mock/test\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/tmp/mock/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/path.js\",\n  {\n    \"input\": \"http://example.com/././foo\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/./.foo\",\n    \"base\": null,\n    \"href\": \"http://example.com/.foo\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/.foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/.\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/./\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/bar/..\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/bar/../\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/..bar\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo/..bar\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/..bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/bar/../ton\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo/ton\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/ton\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/bar/../ton/../../a\",\n    \"base\": null,\n    \"href\": \"http://example.com/a\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/a\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/../../..\",\n    \"base\": null,\n    \"href\": \"http://example.com/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/../../../ton\",\n    \"base\": null,\n    \"href\": \"http://example.com/ton\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/ton\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/%2e\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/%2e%2\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo/%2e%2\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/%2e%2\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar\",\n    \"base\": null,\n    \"href\": \"http://example.com/%2e.bar\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/%2e.bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com////../..\",\n    \"base\": null,\n    \"href\": \"http://example.com//\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"//\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/bar//../..\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo/bar//..\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo/bar/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/%20foo\",\n    \"base\": null,\n    \"href\": \"http://example.com/%20foo\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/%20foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo%\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo%\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo%\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo%2\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo%2\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo%2\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo%2zbar\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo%2zbar\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo%2zbar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo%2Â©zbar\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo%2%C3%82%C2%A9zbar\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo%2%C3%82%C2%A9zbar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo%41%7a\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo%41%7a\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo%41%7a\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo\\t\\u0091%91\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo%C2%91%91\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo%C2%91%91\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo%00%51\",\n    \"base\": null,\n    \"href\": \"http://example.com/foo%00%51\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo%00%51\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/(%28:%3A%29)\",\n    \"base\": null,\n    \"href\": \"http://example.com/(%28:%3A%29)\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/(%28:%3A%29)\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/%3A%3a%3C%3c\",\n    \"base\": null,\n    \"href\": \"http://example.com/%3A%3a%3C%3c\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/%3A%3a%3C%3c\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/foo\\tbar\",\n    \"base\": null,\n    \"href\": \"http://example.com/foobar\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foobar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com\\\\\\\\foo\\\\\\\\bar\",\n    \"base\": null,\n    \"href\": \"http://example.com//foo//bar\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"//foo//bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd\",\n    \"base\": null,\n    \"href\": \"http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/%7Ffp3%3Eju%3Dduvgw%3Dd\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/@asdf%40\",\n    \"base\": null,\n    \"href\": \"http://example.com/@asdf%40\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/@asdf%40\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/你好你好\",\n    \"base\": null,\n    \"href\": \"http://example.com/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/‥/foo\",\n    \"base\": null,\n    \"href\": \"http://example.com/%E2%80%A5/foo\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/%E2%80%A5/foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/﻿/foo\",\n    \"base\": null,\n    \"href\": \"http://example.com/%EF%BB%BF/foo\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/%EF%BB%BF/foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/‮/foo/‭/bar\",\n    \"base\": null,\n    \"href\": \"http://example.com/%E2%80%AE/foo/%E2%80%AD/bar\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/%E2%80%AE/foo/%E2%80%AD/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/relative.js\",\n  {\n    \"input\": \"http://www.google.com/foo?bar=baz#\",\n    \"base\": null,\n    \"href\": \"http://www.google.com/foo?bar=baz#\",\n    \"origin\": \"http://www.google.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.google.com\",\n    \"hostname\": \"www.google.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo\",\n    \"search\": \"?bar=baz\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://www.google.com/foo?bar=baz# »\",\n    \"base\": null,\n    \"href\": \"http://www.google.com/foo?bar=baz#%20%C2%BB\",\n    \"origin\": \"http://www.google.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.google.com\",\n    \"hostname\": \"www.google.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo\",\n    \"search\": \"?bar=baz\",\n    \"hash\": \"#%20%C2%BB\"\n  },\n  {\n    \"input\": \"data:test# »\",\n    \"base\": null,\n    \"href\": \"data:test#%20%C2%BB\",\n    \"origin\": \"null\",\n    \"protocol\": \"data:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"test\",\n    \"search\": \"\",\n    \"hash\": \"#%20%C2%BB\"\n  },\n  {\n    \"input\": \"http://www.google.com\",\n    \"base\": null,\n    \"href\": \"http://www.google.com/\",\n    \"origin\": \"http://www.google.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.google.com\",\n    \"hostname\": \"www.google.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://192.0x00A80001\",\n    \"base\": null,\n    \"href\": \"http://192.168.0.1/\",\n    \"origin\": \"http://192.168.0.1\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"192.168.0.1\",\n    \"hostname\": \"192.168.0.1\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://www/foo%2Ehtml\",\n    \"base\": null,\n    \"href\": \"http://www/foo%2Ehtml\",\n    \"origin\": \"http://www\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www\",\n    \"hostname\": \"www\",\n    \"port\": \"\",\n    \"pathname\": \"/foo%2Ehtml\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://www/foo/%2E/html\",\n    \"base\": null,\n    \"href\": \"http://www/foo/html\",\n    \"origin\": \"http://www\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www\",\n    \"hostname\": \"www\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/html\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://user:pass@/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://%25DOMAIN:foobar@foodomain.com/\",\n    \"base\": null,\n    \"href\": \"http://%25DOMAIN:foobar@foodomain.com/\",\n    \"origin\": \"http://foodomain.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"%25DOMAIN\",\n    \"password\": \"foobar\",\n    \"host\": \"foodomain.com\",\n    \"hostname\": \"foodomain.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:\\\\\\\\www.google.com\\\\foo\",\n    \"base\": null,\n    \"href\": \"http://www.google.com/foo\",\n    \"origin\": \"http://www.google.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.google.com\",\n    \"hostname\": \"www.google.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://foo:80/\",\n    \"base\": null,\n    \"href\": \"http://foo/\",\n    \"origin\": \"http://foo\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo\",\n    \"hostname\": \"foo\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://foo:81/\",\n    \"base\": null,\n    \"href\": \"http://foo:81/\",\n    \"origin\": \"http://foo:81\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:81\",\n    \"hostname\": \"foo\",\n    \"port\": \"81\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"httpa://foo:80/\",\n    \"base\": null,\n    \"href\": \"httpa://foo:80/\",\n    \"origin\": \"null\",\n    \"protocol\": \"httpa:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:80\",\n    \"hostname\": \"foo\",\n    \"port\": \"80\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://foo:-80/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://foo:443/\",\n    \"base\": null,\n    \"href\": \"https://foo/\",\n    \"origin\": \"https://foo\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo\",\n    \"hostname\": \"foo\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"https://foo:80/\",\n    \"base\": null,\n    \"href\": \"https://foo:80/\",\n    \"origin\": \"https://foo:80\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:80\",\n    \"hostname\": \"foo\",\n    \"port\": \"80\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ftp://foo:21/\",\n    \"base\": null,\n    \"href\": \"ftp://foo/\",\n    \"origin\": \"ftp://foo\",\n    \"protocol\": \"ftp:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo\",\n    \"hostname\": \"foo\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ftp://foo:80/\",\n    \"base\": null,\n    \"href\": \"ftp://foo:80/\",\n    \"origin\": \"ftp://foo:80\",\n    \"protocol\": \"ftp:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:80\",\n    \"hostname\": \"foo\",\n    \"port\": \"80\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"gopher://foo:70/\",\n    \"base\": null,\n    \"href\": \"gopher://foo:70/\",\n    \"origin\": \"null\",\n    \"protocol\": \"gopher:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:70\",\n    \"hostname\": \"foo\",\n    \"port\": \"70\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"gopher://foo:443/\",\n    \"base\": null,\n    \"href\": \"gopher://foo:443/\",\n    \"origin\": \"null\",\n    \"protocol\": \"gopher:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:443\",\n    \"hostname\": \"foo\",\n    \"port\": \"443\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ws://foo:80/\",\n    \"base\": null,\n    \"href\": \"ws://foo/\",\n    \"origin\": \"ws://foo\",\n    \"protocol\": \"ws:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo\",\n    \"hostname\": \"foo\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ws://foo:81/\",\n    \"base\": null,\n    \"href\": \"ws://foo:81/\",\n    \"origin\": \"ws://foo:81\",\n    \"protocol\": \"ws:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:81\",\n    \"hostname\": \"foo\",\n    \"port\": \"81\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ws://foo:443/\",\n    \"base\": null,\n    \"href\": \"ws://foo:443/\",\n    \"origin\": \"ws://foo:443\",\n    \"protocol\": \"ws:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:443\",\n    \"hostname\": \"foo\",\n    \"port\": \"443\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ws://foo:815/\",\n    \"base\": null,\n    \"href\": \"ws://foo:815/\",\n    \"origin\": \"ws://foo:815\",\n    \"protocol\": \"ws:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:815\",\n    \"hostname\": \"foo\",\n    \"port\": \"815\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"wss://foo:80/\",\n    \"base\": null,\n    \"href\": \"wss://foo:80/\",\n    \"origin\": \"wss://foo:80\",\n    \"protocol\": \"wss:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:80\",\n    \"hostname\": \"foo\",\n    \"port\": \"80\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"wss://foo:81/\",\n    \"base\": null,\n    \"href\": \"wss://foo:81/\",\n    \"origin\": \"wss://foo:81\",\n    \"protocol\": \"wss:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:81\",\n    \"hostname\": \"foo\",\n    \"port\": \"81\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"wss://foo:443/\",\n    \"base\": null,\n    \"href\": \"wss://foo/\",\n    \"origin\": \"wss://foo\",\n    \"protocol\": \"wss:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo\",\n    \"hostname\": \"foo\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"wss://foo:815/\",\n    \"base\": null,\n    \"href\": \"wss://foo:815/\",\n    \"origin\": \"wss://foo:815\",\n    \"protocol\": \"wss:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo:815\",\n    \"hostname\": \"foo\",\n    \"port\": \"815\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:/example.com/\",\n    \"base\": null,\n    \"href\": \"http://example.com/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ftp:/example.com/\",\n    \"base\": null,\n    \"href\": \"ftp://example.com/\",\n    \"origin\": \"ftp://example.com\",\n    \"protocol\": \"ftp:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"https:/example.com/\",\n    \"base\": null,\n    \"href\": \"https://example.com/\",\n    \"origin\": \"https://example.com\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"madeupscheme:/example.com/\",\n    \"base\": null,\n    \"href\": \"madeupscheme:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"madeupscheme:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:/example.com/\",\n    \"base\": null,\n    \"href\": \"file:///example.com/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ftps:/example.com/\",\n    \"base\": null,\n    \"href\": \"ftps:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"ftps:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"gopher:/example.com/\",\n    \"base\": null,\n    \"href\": \"gopher:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"gopher:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ws:/example.com/\",\n    \"base\": null,\n    \"href\": \"ws://example.com/\",\n    \"origin\": \"ws://example.com\",\n    \"protocol\": \"ws:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"wss:/example.com/\",\n    \"base\": null,\n    \"href\": \"wss://example.com/\",\n    \"origin\": \"wss://example.com\",\n    \"protocol\": \"wss:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"data:/example.com/\",\n    \"base\": null,\n    \"href\": \"data:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"data:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"javascript:/example.com/\",\n    \"base\": null,\n    \"href\": \"javascript:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"javascript:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"mailto:/example.com/\",\n    \"base\": null,\n    \"href\": \"mailto:/example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"mailto:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:example.com/\",\n    \"base\": null,\n    \"href\": \"http://example.com/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ftp:example.com/\",\n    \"base\": null,\n    \"href\": \"ftp://example.com/\",\n    \"origin\": \"ftp://example.com\",\n    \"protocol\": \"ftp:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"https:example.com/\",\n    \"base\": null,\n    \"href\": \"https://example.com/\",\n    \"origin\": \"https://example.com\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"madeupscheme:example.com/\",\n    \"base\": null,\n    \"href\": \"madeupscheme:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"madeupscheme:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ftps:example.com/\",\n    \"base\": null,\n    \"href\": \"ftps:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"ftps:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"gopher:example.com/\",\n    \"base\": null,\n    \"href\": \"gopher:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"gopher:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ws:example.com/\",\n    \"base\": null,\n    \"href\": \"ws://example.com/\",\n    \"origin\": \"ws://example.com\",\n    \"protocol\": \"ws:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"wss:example.com/\",\n    \"base\": null,\n    \"href\": \"wss://example.com/\",\n    \"origin\": \"wss://example.com\",\n    \"protocol\": \"wss:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"data:example.com/\",\n    \"base\": null,\n    \"href\": \"data:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"data:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"javascript:example.com/\",\n    \"base\": null,\n    \"href\": \"javascript:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"javascript:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"mailto:example.com/\",\n    \"base\": null,\n    \"href\": \"mailto:example.com/\",\n    \"origin\": \"null\",\n    \"protocol\": \"mailto:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"example.com/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/segments-userinfo-vs-host.html\",\n  {\n    \"input\": \"http:@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:/@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:a:b@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://a:b@www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"a\",\n    \"password\": \"b\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:/a:b@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://a:b@www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"a\",\n    \"password\": \"b\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://a:b@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://a:b@www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"a\",\n    \"password\": \"b\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://@pple.com\",\n    \"base\": null,\n    \"href\": \"http://pple.com/\",\n    \"origin\": \"http://pple.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"pple.com\",\n    \"hostname\": \"pple.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http::b@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://:b@www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"b\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:/:b@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://:b@www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"b\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://:b@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://:b@www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"b\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:/:@/www.example.com\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"http://user@/www.example.com\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http:@/www.example.com\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"http:/@/www.example.com\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"http://@/www.example.com\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https:@/www.example.com\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"http:a:b@/www.example.com\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"http:/a:b@/www.example.com\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"http://a:b@/www.example.com\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http::@/www.example.com\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"http:a:@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://a@www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"a\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:/a:@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://a@www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"a\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://a:@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://a@www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"a\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://www.@pple.com\",\n    \"base\": null,\n    \"href\": \"http://www.@pple.com/\",\n    \"origin\": \"http://pple.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"www.\",\n    \"password\": \"\",\n    \"host\": \"pple.com\",\n    \"hostname\": \"pple.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:@:www.example.com\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"http:/@:www.example.com\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"http://@:www.example.com\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://:@www.example.com\",\n    \"base\": null,\n    \"href\": \"http://www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Others\",\n  {\n    \"input\": \"/\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/test.txt\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example.com/test.txt\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/test.txt\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \".\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"..\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example.com/\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"test.txt\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example.com/test.txt\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/test.txt\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"./test.txt\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example.com/test.txt\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/test.txt\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"../test.txt\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example.com/test.txt\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/test.txt\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"../aaa/test.txt\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example.com/aaa/test.txt\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/aaa/test.txt\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"../../test.txt\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example.com/test.txt\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/test.txt\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"中/test.txt\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example.com/%E4%B8%AD/test.txt\",\n    \"origin\": \"http://www.example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/%E4%B8%AD/test.txt\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://www.example2.com\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example2.com/\",\n    \"origin\": \"http://www.example2.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example2.com\",\n    \"hostname\": \"www.example2.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"//www.example2.com\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"http://www.example2.com/\",\n    \"origin\": \"http://www.example2.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.example2.com\",\n    \"hostname\": \"www.example2.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:...\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"file:///...\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/...\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:..\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:a\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"file:///a\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/a\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:.\",\n    \"base\": null,\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:.\",\n    \"base\": \"http://www.example.com/test\",\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/host.html\",\n  \"Basic canonicalization, uppercase should be converted to lowercase\",\n  {\n    \"input\": \"http://ExAmPlE.CoM\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://example.com/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example example.com\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://Goo%20 goo%7C|.com\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[]\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[:]\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  \"U+3000 is mapped to U+0020 (space) which is disallowed\",\n  {\n    \"input\": \"http://GOO\\u00a0\\u3000goo.com\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  \"Other types of space (no-break, zero-width, zero-width-no-break) are name-prepped away to nothing. U+200B, U+2060, and U+FEFF, are ignored\",\n  {\n    \"input\": \"http://GOO\\u200b\\u2060\\ufeffgoo.com\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://googoo.com/\",\n    \"origin\": \"http://googoo.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"googoo.com\",\n    \"hostname\": \"googoo.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Leading and trailing C0 control or space\",\n  {\n    \"input\": \"\\u0000\\u001b\\u0004\\u0012 http://example.com/\\u001f \\u000d \",\n    \"base\": null,\n    \"href\": \"http://example.com/\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)\",\n  {\n    \"input\": \"http://www.foo。bar.com\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://www.foo.bar.com/\",\n    \"origin\": \"http://www.foo.bar.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"www.foo.bar.com\",\n    \"hostname\": \"www.foo.bar.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Invalid unicode characters should fail... U+FDD0 is disallowed; %ef%b7%90 is U+FDD0\",\n  {\n    \"input\": \"http://\\ufdd0zyx.com\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  \"This is the same as previous but escaped\",\n  {\n    \"input\": \"http://%ef%b7%90zyx.com\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  \"U+FFFD\",\n  {\n    \"input\": \"https://\\ufffd\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://%EF%BF%BD\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://x/\\ufffd?\\ufffd#\\ufffd\",\n    \"base\": null,\n    \"href\": \"https://x/%EF%BF%BD?%EF%BF%BD#%EF%BF%BD\",\n    \"origin\": \"https://x\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"x\",\n    \"hostname\": \"x\",\n    \"port\": \"\",\n    \"pathname\": \"/%EF%BF%BD\",\n    \"search\": \"?%EF%BF%BD\",\n    \"hash\": \"#%EF%BF%BD\"\n  },\n  \"Domain is ASCII, but a label is invalid IDNA\",\n  {\n    \"input\": \"http://a.b.c.xn--pokxncvks\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://10.0.0.xn--pokxncvks\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"IDNA labels should be matched case-insensitively\",\n  {\n    \"input\": \"http://a.b.c.XN--pokxncvks\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a.b.c.Xn--pokxncvks\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://10.0.0.XN--pokxncvks\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://10.0.0.xN--pokxncvks\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"Test name prepping, fullwidth input should be converted to ASCII and NOT IDN-ized. This is 'Go' in fullwidth UTF-8/UTF-16.\",\n  {\n    \"input\": \"http://Ｇｏ.com\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://go.com/\",\n    \"origin\": \"http://go.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"go.com\",\n    \"hostname\": \"go.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"URL spec forbids the following. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24257\",\n  {\n    \"input\": \"http://％４１.com\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://%ef%bc%85%ef%bc%94%ef%bc%91.com\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  \"...%00 in fullwidth should fail (also as escaped UTF-8 input)\",\n  {\n    \"input\": \"http://％００.com\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://%ef%bc%85%ef%bc%90%ef%bc%90.com\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  \"Basic IDN support, UTF-8 and UTF-16 input should be converted to IDN\",\n  {\n    \"input\": \"http://你好你好\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://xn--6qqa088eba/\",\n    \"origin\": \"http://xn--6qqa088eba\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"xn--6qqa088eba\",\n    \"hostname\": \"xn--6qqa088eba\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"https://faß.ExAmPlE/\",\n    \"base\": null,\n    \"href\": \"https://xn--fa-hia.example/\",\n    \"origin\": \"https://xn--fa-hia.example\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"xn--fa-hia.example\",\n    \"hostname\": \"xn--fa-hia.example\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"sc://faß.ExAmPlE/\",\n    \"base\": null,\n    \"href\": \"sc://fa%C3%9F.ExAmPlE/\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"fa%C3%9F.ExAmPlE\",\n    \"hostname\": \"fa%C3%9F.ExAmPlE\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Invalid escaped characters should fail and the percents should be escaped. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24191\",\n  {\n    \"input\": \"http://%zz%66%a.com\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  \"If we get an invalid character that has been escaped.\",\n  {\n    \"input\": \"http://%25\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://hello%00\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  \"Escaped numbers should be treated like IP addresses if they are.\",\n  {\n    \"input\": \"http://%30%78%63%30%2e%30%32%35%30.01\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://192.168.0.1/\",\n    \"origin\": \"http://192.168.0.1\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"192.168.0.1\",\n    \"hostname\": \"192.168.0.1\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://%30%78%63%30%2e%30%32%35%30.01%2e\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://192.168.0.1/\",\n    \"origin\": \"http://192.168.0.1\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"192.168.0.1\",\n    \"hostname\": \"192.168.0.1\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://192.168.0.257\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  \"Invalid escaping in hosts causes failure\",\n  {\n    \"input\": \"http://%3g%78%63%30%2e%30%32%35%30%2E.01\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  \"A space in a host causes failure\",\n  {\n    \"input\": \"http://192.168.0.1 hello\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://x x:12\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"Fullwidth and escaped UTF-8 fullwidth should still be treated as IP\",\n  {\n    \"input\": \"http://０Ｘｃ０．０２５０．０１\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://192.168.0.1/\",\n    \"origin\": \"http://192.168.0.1\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"192.168.0.1\",\n    \"hostname\": \"192.168.0.1\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Domains with empty labels\",\n  {\n    \"input\": \"http://./\",\n    \"base\": null,\n    \"href\": \"http://./\",\n    \"origin\": \"http://.\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \".\",\n    \"hostname\": \".\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://../\",\n    \"base\": null,\n    \"href\": \"http://../\",\n    \"origin\": \"http://..\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"..\",\n    \"hostname\": \"..\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Non-special domains with empty labels\",\n  {\n    \"input\": \"h://.\",\n    \"base\": null,\n    \"href\": \"h://.\",\n    \"origin\": \"null\",\n    \"protocol\": \"h:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \".\",\n    \"hostname\": \".\",\n    \"port\": \"\",\n    \"pathname\": \"\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Broken IPv6\",\n  {\n    \"input\": \"http://[www.google.com]/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[google.com]\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[::1.2.3.4x]\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[::1.2.3.]\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[::1.2.]\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[::.1.2]\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[::1.]\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[::.1]\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://[::%31]\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://%5B::1]\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  \"Misc Unicode\",\n  {\n    \"input\": \"http://foo:💩@example.com/bar\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://foo:%F0%9F%92%A9@example.com/bar\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"foo\",\n    \"password\": \"%F0%9F%92%A9\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# resolving a fragment against any scheme succeeds\",\n  {\n    \"input\": \"#\",\n    \"base\": \"test:test\",\n    \"href\": \"test:test#\",\n    \"origin\": \"null\",\n    \"protocol\": \"test:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"#x\",\n    \"base\": \"mailto:x@x.com\",\n    \"href\": \"mailto:x@x.com#x\",\n    \"origin\": \"null\",\n    \"protocol\": \"mailto:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"x@x.com\",\n    \"search\": \"\",\n    \"hash\": \"#x\"\n  },\n  {\n    \"input\": \"#x\",\n    \"base\": \"data:,\",\n    \"href\": \"data:,#x\",\n    \"origin\": \"null\",\n    \"protocol\": \"data:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \",\",\n    \"search\": \"\",\n    \"hash\": \"#x\"\n  },\n  {\n    \"input\": \"#x\",\n    \"base\": \"about:blank\",\n    \"href\": \"about:blank#x\",\n    \"origin\": \"null\",\n    \"protocol\": \"about:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"blank\",\n    \"search\": \"\",\n    \"hash\": \"#x\"\n  },\n  {\n    \"input\": \"#x:y\",\n    \"base\": \"about:blank\",\n    \"href\": \"about:blank#x:y\",\n    \"origin\": \"null\",\n    \"protocol\": \"about:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"blank\",\n    \"search\": \"\",\n    \"hash\": \"#x:y\"\n  },\n  {\n    \"input\": \"#\",\n    \"base\": \"test:test?test\",\n    \"href\": \"test:test?test#\",\n    \"origin\": \"null\",\n    \"protocol\": \"test:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"test\",\n    \"search\": \"?test\",\n    \"hash\": \"\"\n  },\n  \"# multiple @ in authority state\",\n  {\n    \"input\": \"https://@test@test@example:800/\",\n    \"base\": \"http://doesnotmatter/\",\n    \"href\": \"https://%40test%40test@example:800/\",\n    \"origin\": \"https://example:800\",\n    \"protocol\": \"https:\",\n    \"username\": \"%40test%40test\",\n    \"password\": \"\",\n    \"host\": \"example:800\",\n    \"hostname\": \"example\",\n    \"port\": \"800\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"https://@@@example\",\n    \"base\": \"http://doesnotmatter/\",\n    \"href\": \"https://%40%40@example/\",\n    \"origin\": \"https://example\",\n    \"protocol\": \"https:\",\n    \"username\": \"%40%40\",\n    \"password\": \"\",\n    \"host\": \"example\",\n    \"hostname\": \"example\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"non-az-09 characters\",\n  {\n    \"input\": \"http://`{}:`{}@h/`{}?`{}\",\n    \"base\": \"http://doesnotmatter/\",\n    \"href\": \"http://%60%7B%7D:%60%7B%7D@h/%60%7B%7D?`{}\",\n    \"origin\": \"http://h\",\n    \"protocol\": \"http:\",\n    \"username\": \"%60%7B%7D\",\n    \"password\": \"%60%7B%7D\",\n    \"host\": \"h\",\n    \"hostname\": \"h\",\n    \"port\": \"\",\n    \"pathname\": \"/%60%7B%7D\",\n    \"search\": \"?`{}\",\n    \"hash\": \"\"\n  },\n  \"byte is ' and url is special\",\n  {\n    \"input\": \"http://host/?'\",\n    \"base\": null,\n    \"href\": \"http://host/?%27\",\n    \"origin\": \"http://host\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"?%27\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"notspecial://host/?'\",\n    \"base\": null,\n    \"href\": \"notspecial://host/?'\",\n    \"origin\": \"null\",\n    \"protocol\": \"notspecial:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"?'\",\n    \"hash\": \"\"\n  },\n  \"# Credentials in base\",\n  {\n    \"input\": \"/some/path\",\n    \"base\": \"http://user@example.org/smth\",\n    \"href\": \"http://user@example.org/some/path\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"user\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/some/path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"\",\n    \"base\": \"http://user:pass@example.org:21/smth\",\n    \"href\": \"http://user:pass@example.org:21/smth\",\n    \"origin\": \"http://example.org:21\",\n    \"protocol\": \"http:\",\n    \"username\": \"user\",\n    \"password\": \"pass\",\n    \"host\": \"example.org:21\",\n    \"hostname\": \"example.org\",\n    \"port\": \"21\",\n    \"pathname\": \"/smth\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/some/path\",\n    \"base\": \"http://user:pass@example.org:21/smth\",\n    \"href\": \"http://user:pass@example.org:21/some/path\",\n    \"origin\": \"http://example.org:21\",\n    \"protocol\": \"http:\",\n    \"username\": \"user\",\n    \"password\": \"pass\",\n    \"host\": \"example.org:21\",\n    \"hostname\": \"example.org\",\n    \"port\": \"21\",\n    \"pathname\": \"/some/path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# a set of tests designed by zcorpan for relative URLs with unknown schemes\",\n  {\n    \"input\": \"i\",\n    \"base\": \"sc:sd\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"i\",\n    \"base\": \"sc:sd/sd\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"i\",\n    \"base\": \"sc:/pa/pa\",\n    \"href\": \"sc:/pa/i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/pa/i\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"i\",\n    \"base\": \"sc://ho/pa\",\n    \"href\": \"sc://ho/i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"ho\",\n    \"hostname\": \"ho\",\n    \"port\": \"\",\n    \"pathname\": \"/i\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"i\",\n    \"base\": \"sc:///pa/pa\",\n    \"href\": \"sc:///pa/i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/pa/i\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"../i\",\n    \"base\": \"sc:sd\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"../i\",\n    \"base\": \"sc:sd/sd\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"../i\",\n    \"base\": \"sc:/pa/pa\",\n    \"href\": \"sc:/i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/i\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"../i\",\n    \"base\": \"sc://ho/pa\",\n    \"href\": \"sc://ho/i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"ho\",\n    \"hostname\": \"ho\",\n    \"port\": \"\",\n    \"pathname\": \"/i\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"../i\",\n    \"base\": \"sc:///pa/pa\",\n    \"href\": \"sc:///i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/i\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/i\",\n    \"base\": \"sc:sd\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"/i\",\n    \"base\": \"sc:sd/sd\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"/i\",\n    \"base\": \"sc:/pa/pa\",\n    \"href\": \"sc:/i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/i\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/i\",\n    \"base\": \"sc://ho/pa\",\n    \"href\": \"sc://ho/i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"ho\",\n    \"hostname\": \"ho\",\n    \"port\": \"\",\n    \"pathname\": \"/i\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/i\",\n    \"base\": \"sc:///pa/pa\",\n    \"href\": \"sc:///i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/i\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"?i\",\n    \"base\": \"sc:sd\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"?i\",\n    \"base\": \"sc:sd/sd\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"?i\",\n    \"base\": \"sc:/pa/pa\",\n    \"href\": \"sc:/pa/pa?i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/pa/pa\",\n    \"search\": \"?i\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"?i\",\n    \"base\": \"sc://ho/pa\",\n    \"href\": \"sc://ho/pa?i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"ho\",\n    \"hostname\": \"ho\",\n    \"port\": \"\",\n    \"pathname\": \"/pa\",\n    \"search\": \"?i\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"?i\",\n    \"base\": \"sc:///pa/pa\",\n    \"href\": \"sc:///pa/pa?i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/pa/pa\",\n    \"search\": \"?i\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"#i\",\n    \"base\": \"sc:sd\",\n    \"href\": \"sc:sd#i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"sd\",\n    \"search\": \"\",\n    \"hash\": \"#i\"\n  },\n  {\n    \"input\": \"#i\",\n    \"base\": \"sc:sd/sd\",\n    \"href\": \"sc:sd/sd#i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"sd/sd\",\n    \"search\": \"\",\n    \"hash\": \"#i\"\n  },\n  {\n    \"input\": \"#i\",\n    \"base\": \"sc:/pa/pa\",\n    \"href\": \"sc:/pa/pa#i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/pa/pa\",\n    \"search\": \"\",\n    \"hash\": \"#i\"\n  },\n  {\n    \"input\": \"#i\",\n    \"base\": \"sc://ho/pa\",\n    \"href\": \"sc://ho/pa#i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"ho\",\n    \"hostname\": \"ho\",\n    \"port\": \"\",\n    \"pathname\": \"/pa\",\n    \"search\": \"\",\n    \"hash\": \"#i\"\n  },\n  {\n    \"input\": \"#i\",\n    \"base\": \"sc:///pa/pa\",\n    \"href\": \"sc:///pa/pa#i\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/pa/pa\",\n    \"search\": \"\",\n    \"hash\": \"#i\"\n  },\n  \"# make sure that relative URL logic works on known typically non-relative schemes too\",\n  {\n    \"input\": \"about:/../\",\n    \"base\": null,\n    \"href\": \"about:/\",\n    \"origin\": \"null\",\n    \"protocol\": \"about:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"data:/../\",\n    \"base\": null,\n    \"href\": \"data:/\",\n    \"origin\": \"null\",\n    \"protocol\": \"data:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"javascript:/../\",\n    \"base\": null,\n    \"href\": \"javascript:/\",\n    \"origin\": \"null\",\n    \"protocol\": \"javascript:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"mailto:/../\",\n    \"base\": null,\n    \"href\": \"mailto:/\",\n    \"origin\": \"null\",\n    \"protocol\": \"mailto:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# unknown schemes and their hosts\",\n  {\n    \"input\": \"sc://ñ.test/\",\n    \"base\": null,\n    \"href\": \"sc://%C3%B1.test/\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"%C3%B1.test\",\n    \"hostname\": \"%C3%B1.test\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"sc://%/\",\n    \"base\": null,\n    \"href\": \"sc://%/\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"%\",\n    \"hostname\": \"%\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"sc://@/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc://te@s:t@/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc://:/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc://:12/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"x\",\n    \"base\": \"sc://ñ\",\n    \"href\": \"sc://%C3%B1/x\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"%C3%B1\",\n    \"hostname\": \"%C3%B1\",\n    \"port\": \"\",\n    \"pathname\": \"/x\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# unknown schemes and backslashes\",\n  {\n    \"input\": \"sc:\\\\../\",\n    \"base\": null,\n    \"href\": \"sc:\\\\../\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"\\\\../\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# unknown scheme with path looking like a password\",\n  {\n    \"input\": \"sc::a@example.net\",\n    \"base\": null,\n    \"href\": \"sc::a@example.net\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \":a@example.net\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# unknown scheme with bogus percent-encoding\",\n  {\n    \"input\": \"wow:%NBD\",\n    \"base\": null,\n    \"href\": \"wow:%NBD\",\n    \"origin\": \"null\",\n    \"protocol\": \"wow:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"%NBD\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"wow:%1G\",\n    \"base\": null,\n    \"href\": \"wow:%1G\",\n    \"origin\": \"null\",\n    \"protocol\": \"wow:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"%1G\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# unknown scheme with non-URL characters\",\n  {\n    \"input\": \"wow:\\uFFFF\",\n    \"base\": null,\n    \"href\": \"wow:%EF%BF%BF\",\n    \"origin\": \"null\",\n    \"protocol\": \"wow:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"%EF%BF%BF\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.com/\\uD800\\uD801\\uDFFE\\uDFFF\\uFDD0\\uFDCF\\uFDEF\\uFDF0\\uFFFE\\uFFFF?\\uD800\\uD801\\uDFFE\\uDFFF\\uFDD0\\uFDCF\\uFDEF\\uFDF0\\uFFFE\\uFFFF\",\n    \"base\": null,\n    \"href\": \"http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF\",\n    \"origin\": \"http://example.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"port\": \"\",\n    \"pathname\": \"/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF\",\n    \"search\": \"?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF\",\n    \"hash\": \"\"\n  },\n  \"Forbidden host code points\",\n  {\n    \"input\": \"sc://a\\u0000b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc://a b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc://a<b\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc://a>b\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc://a[b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc://a\\\\b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc://a]b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc://a^b\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc://a|b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"Forbidden host codepoints: tabs and newlines are removed during preprocessing\",\n  {\n    \"input\": \"foo://ho\\u0009st/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\":\"foo://host/\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"foo:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"foo://ho\\u000Ast/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\":\"foo://host/\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"foo:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"foo://ho\\u000Dst/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\":\"foo://host/\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"foo:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  \"Forbidden domain code-points\",\n  {\n    \"input\": \"http://a\\u0000b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0001b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0002b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0003b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0004b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0005b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0006b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0007b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0008b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u000Bb/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u000Cb/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u000Eb/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u000Fb/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0010b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0011b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0012b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0013b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0014b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0015b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0016b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0017b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0018b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u0019b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u001Ab/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u001Bb/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u001Cb/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u001Db/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u001Eb/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u001Fb/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a%b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a<b\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a>b\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a[b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a]b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a^b\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a|b/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://a\\u007Fb/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"Forbidden domain codepoints: tabs and newlines are removed during preprocessing\",\n  {\n    \"input\": \"http://ho\\u0009st/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\":\"http://host/\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"http:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"http://ho\\u000Ast/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\":\"http://host/\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"http:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"http://ho\\u000Dst/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\":\"http://host/\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"http:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  \"Encoded forbidden domain codepoints in special URLs\",\n  {\n    \"input\": \"http://ho%00st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%01st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%02st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%03st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%04st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%05st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%06st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%07st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%08st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%09st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%0Ast/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%0Bst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%0Cst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%0Dst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%0Est/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%0Fst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%10st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%11st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%12st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%13st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%14st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%15st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%16st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%17st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%18st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%19st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%1Ast/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%1Bst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%1Cst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%1Dst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%1Est/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%1Fst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%20st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%23st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%25st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%2Fst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%3Ast/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%3Cst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%3Est/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%3Fst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%40st/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%5Bst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%5Cst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%5Dst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%7Cst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://ho%7Fst/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"Allowed host/domain code points\",\n  {\n    \"input\": \"http://!\\\"$&'()*+,-.;=_`{}~/\",\n    \"base\": null,\n    \"href\": \"http://!\\\"$&'()*+,-.;=_`{}~/\",\n    \"origin\": \"http://!\\\"$&'()*+,-.;=_`{}~\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"!\\\"$&'()*+,-.;=_`{}~\",\n    \"hostname\": \"!\\\"$&'()*+,-.;=_`{}~\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"sc://\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\u0008\\u000B\\u000C\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F\\u007F!\\\"$%&'()*+,-.;=_`{}~/\",\n    \"base\": null,\n    \"href\": \"sc://%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\\\"$%&'()*+,-.;=_`{}~/\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\\\"$%&'()*+,-.;=_`{}~\",\n    \"hostname\": \"%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\\\"$%&'()*+,-.;=_`{}~\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Hosts and percent-encoding\",\n  {\n    \"input\": \"ftp://example.com%80/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"ftp://example.com%A0/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://example.com%80/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://example.com%A0/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"ftp://%e2%98%83\",\n    \"base\": null,\n    \"href\": \"ftp://xn--n3h/\",\n    \"origin\": \"ftp://xn--n3h\",\n    \"protocol\": \"ftp:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"xn--n3h\",\n    \"hostname\": \"xn--n3h\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"https://%e2%98%83\",\n    \"base\": null,\n    \"href\": \"https://xn--n3h/\",\n    \"origin\": \"https://xn--n3h\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"xn--n3h\",\n    \"hostname\": \"xn--n3h\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# tests from jsdom/whatwg-url designed for code coverage\",\n  {\n    \"input\": \"http://127.0.0.1:10100/relative_import.html\",\n    \"base\": null,\n    \"href\": \"http://127.0.0.1:10100/relative_import.html\",\n    \"origin\": \"http://127.0.0.1:10100\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"127.0.0.1:10100\",\n    \"hostname\": \"127.0.0.1\",\n    \"port\": \"10100\",\n    \"pathname\": \"/relative_import.html\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://facebook.com/?foo=%7B%22abc%22\",\n    \"base\": null,\n    \"href\": \"http://facebook.com/?foo=%7B%22abc%22\",\n    \"origin\": \"http://facebook.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"facebook.com\",\n    \"hostname\": \"facebook.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"?foo=%7B%22abc%22\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"https://localhost:3000/jqueryui@1.2.3\",\n    \"base\": null,\n    \"href\": \"https://localhost:3000/jqueryui@1.2.3\",\n    \"origin\": \"https://localhost:3000\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"localhost:3000\",\n    \"hostname\": \"localhost\",\n    \"port\": \"3000\",\n    \"pathname\": \"/jqueryui@1.2.3\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# tab/LF/CR\",\n  {\n    \"input\": \"h\\tt\\nt\\rp://h\\to\\ns\\rt:9\\t0\\n0\\r0/p\\ta\\nt\\rh?q\\tu\\ne\\rry#f\\tr\\na\\rg\",\n    \"base\": null,\n    \"href\": \"http://host:9000/path?query#frag\",\n    \"origin\": \"http://host:9000\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host:9000\",\n    \"hostname\": \"host\",\n    \"port\": \"9000\",\n    \"pathname\": \"/path\",\n    \"search\": \"?query\",\n    \"hash\": \"#frag\"\n  },\n  \"# Stringification of URL.searchParams\",\n  {\n    \"input\": \"?a=b&c=d\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/bar?a=b&c=d\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"?a=b&c=d\",\n    \"searchParams\": \"a=b&c=d\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"??a=b&c=d\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/bar??a=b&c=d\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"??a=b&c=d\",\n    \"searchParams\": \"%3Fa=b&c=d\",\n    \"hash\": \"\"\n  },\n  \"# Scheme only\",\n  {\n    \"input\": \"http:\",\n    \"base\": \"http://example.org/foo/bar\",\n    \"href\": \"http://example.org/foo/bar\",\n    \"origin\": \"http://example.org\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"\",\n    \"searchParams\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http:\",\n    \"base\": \"https://example.org/foo/bar\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"sc:\",\n    \"base\": \"https://example.org/foo/bar\",\n    \"href\": \"sc:\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"\",\n    \"search\": \"\",\n    \"searchParams\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Percent encoding of fragments\",\n  {\n    \"input\": \"http://foo.bar/baz?qux#foo\\bbar\",\n    \"base\": null,\n    \"href\": \"http://foo.bar/baz?qux#foo%08bar\",\n    \"origin\": \"http://foo.bar\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo.bar\",\n    \"hostname\": \"foo.bar\",\n    \"port\": \"\",\n    \"pathname\": \"/baz\",\n    \"search\": \"?qux\",\n    \"searchParams\": \"qux=\",\n    \"hash\": \"#foo%08bar\"\n  },\n  {\n    \"input\": \"http://foo.bar/baz?qux#foo\\\"bar\",\n    \"base\": null,\n    \"href\": \"http://foo.bar/baz?qux#foo%22bar\",\n    \"origin\": \"http://foo.bar\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo.bar\",\n    \"hostname\": \"foo.bar\",\n    \"port\": \"\",\n    \"pathname\": \"/baz\",\n    \"search\": \"?qux\",\n    \"searchParams\": \"qux=\",\n    \"hash\": \"#foo%22bar\"\n  },\n  {\n    \"input\": \"http://foo.bar/baz?qux#foo<bar\",\n    \"base\": null,\n    \"href\": \"http://foo.bar/baz?qux#foo%3Cbar\",\n    \"origin\": \"http://foo.bar\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo.bar\",\n    \"hostname\": \"foo.bar\",\n    \"port\": \"\",\n    \"pathname\": \"/baz\",\n    \"search\": \"?qux\",\n    \"searchParams\": \"qux=\",\n    \"hash\": \"#foo%3Cbar\"\n  },\n  {\n    \"input\": \"http://foo.bar/baz?qux#foo>bar\",\n    \"base\": null,\n    \"href\": \"http://foo.bar/baz?qux#foo%3Ebar\",\n    \"origin\": \"http://foo.bar\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo.bar\",\n    \"hostname\": \"foo.bar\",\n    \"port\": \"\",\n    \"pathname\": \"/baz\",\n    \"search\": \"?qux\",\n    \"searchParams\": \"qux=\",\n    \"hash\": \"#foo%3Ebar\"\n  },\n  {\n    \"input\": \"http://foo.bar/baz?qux#foo`bar\",\n    \"base\": null,\n    \"href\": \"http://foo.bar/baz?qux#foo%60bar\",\n    \"origin\": \"http://foo.bar\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foo.bar\",\n    \"hostname\": \"foo.bar\",\n    \"port\": \"\",\n    \"pathname\": \"/baz\",\n    \"search\": \"?qux\",\n    \"searchParams\": \"qux=\",\n    \"hash\": \"#foo%60bar\"\n  },\n  \"# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)\",\n  {\n    \"input\": \"http://1.2.3.4/\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://1.2.3.4/\",\n    \"origin\": \"http://1.2.3.4\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"1.2.3.4\",\n    \"hostname\": \"1.2.3.4\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://1.2.3.4./\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://1.2.3.4/\",\n    \"origin\": \"http://1.2.3.4\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"1.2.3.4\",\n    \"hostname\": \"1.2.3.4\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://192.168.257\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://192.168.1.1/\",\n    \"origin\": \"http://192.168.1.1\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"192.168.1.1\",\n    \"hostname\": \"192.168.1.1\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://192.168.257.\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://192.168.1.1/\",\n    \"origin\": \"http://192.168.1.1\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"192.168.1.1\",\n    \"hostname\": \"192.168.1.1\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://192.168.257.com\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://192.168.257.com/\",\n    \"origin\": \"http://192.168.257.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"192.168.257.com\",\n    \"hostname\": \"192.168.257.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://256\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://0.0.1.0/\",\n    \"origin\": \"http://0.0.1.0\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"0.0.1.0\",\n    \"hostname\": \"0.0.1.0\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://256.com\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://256.com/\",\n    \"origin\": \"http://256.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"256.com\",\n    \"hostname\": \"256.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://999999999\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://59.154.201.255/\",\n    \"origin\": \"http://59.154.201.255\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"59.154.201.255\",\n    \"hostname\": \"59.154.201.255\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://999999999.\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://59.154.201.255/\",\n    \"origin\": \"http://59.154.201.255\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"59.154.201.255\",\n    \"hostname\": \"59.154.201.255\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://999999999.com\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://999999999.com/\",\n    \"origin\": \"http://999999999.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"999999999.com\",\n    \"hostname\": \"999999999.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://10000000000\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://10000000000.com\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://10000000000.com/\",\n    \"origin\": \"http://10000000000.com\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"10000000000.com\",\n    \"hostname\": \"10000000000.com\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://4294967295\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://255.255.255.255/\",\n    \"origin\": \"http://255.255.255.255\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"255.255.255.255\",\n    \"hostname\": \"255.255.255.255\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://4294967296\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://0xffffffff\",\n    \"base\": \"http://other.com/\",\n    \"href\": \"http://255.255.255.255/\",\n    \"origin\": \"http://255.255.255.255\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"255.255.255.255\",\n    \"hostname\": \"255.255.255.255\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://0xffffffff1\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://256.256.256.256\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://0x.0x.0\",\n    \"base\": null,\n    \"href\": \"https://0.0.0.0/\",\n    \"origin\": \"https://0.0.0.0\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"0.0.0.0\",\n    \"hostname\": \"0.0.0.0\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"More IPv4 parsing (via https://github.com/jsdom/whatwg-url/issues/92)\",\n  {\n    \"input\": \"https://0x100000000/test\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://256.0.0.1/test\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"# file URLs containing percent-encoded Windows drive letters (shouldn't work)\",\n  {\n    \"input\": \"file:///C%3A/\",\n    \"base\": null,\n    \"href\": \"file:///C%3A/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C%3A/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///C%7C/\",\n    \"base\": null,\n    \"href\": \"file:///C%7C/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C%7C/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://%43%3A\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"file://%43%7C\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"file://%43|\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"file://C%7C\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"file://%43%7C/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://%43%7C/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"asdf://%43|/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"asdf://%43%7C/\",\n    \"base\": null,\n    \"href\": \"asdf://%43%7C/\",\n    \"origin\": \"null\",\n    \"protocol\": \"asdf:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"%43%7C\",\n    \"hostname\": \"%43%7C\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# file URLs relative to other file URLs (via https://github.com/jsdom/whatwg-url/pull/60)\",\n  {\n    \"input\": \"pix/submit.gif\",\n    \"base\": \"file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html\",\n    \"href\": \"file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"..\",\n    \"base\": \"file:///C:/\",\n    \"href\": \"file:///C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"..\",\n    \"base\": \"file:///\",\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# More file URL tests by zcorpan and annevk\",\n  {\n    \"input\": \"/\",\n    \"base\": \"file:///C:/a/b\",\n    \"href\": \"file:///C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/\",\n    \"base\": \"file://h/C:/a/b\",\n    \"href\": \"file://h/C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"h\",\n    \"hostname\": \"h\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/\",\n    \"base\": \"file://h/a/b\",\n    \"href\": \"file://h/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"h\",\n    \"hostname\": \"h\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"//d:\",\n    \"base\": \"file:///C:/a/b\",\n    \"href\": \"file:///d:\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/d:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"//d:/..\",\n    \"base\": \"file:///C:/a/b\",\n    \"href\": \"file:///d:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/d:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"..\",\n    \"base\": \"file:///ab:/\",\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"..\",\n    \"base\": \"file:///1:/\",\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"\",\n    \"base\": \"file:///test?test#test\",\n    \"href\": \"file:///test?test\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?test\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:\",\n    \"base\": \"file:///test?test#test\",\n    \"href\": \"file:///test?test\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?test\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"?x\",\n    \"base\": \"file:///test?test#test\",\n    \"href\": \"file:///test?x\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?x\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:?x\",\n    \"base\": \"file:///test?test#test\",\n    \"href\": \"file:///test?x\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?x\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"#x\",\n    \"base\": \"file:///test?test#test\",\n    \"href\": \"file:///test?test#x\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?test\",\n    \"hash\": \"#x\"\n  },\n  {\n    \"input\": \"file:#x\",\n    \"base\": \"file:///test?test#test\",\n    \"href\": \"file:///test?test#x\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?test\",\n    \"hash\": \"#x\"\n  },\n  \"# File URLs and many (back)slashes\",\n  {\n    \"input\": \"file:\\\\\\\\//\",\n    \"base\": null,\n    \"href\": \"file:////\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:\\\\\\\\\\\\\\\\\",\n    \"base\": null,\n    \"href\": \"file:////\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:\\\\\\\\\\\\\\\\?fox\",\n    \"base\": null,\n    \"href\": \"file:////?fox\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//\",\n    \"search\": \"?fox\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:\\\\\\\\\\\\\\\\#guppy\",\n    \"base\": null,\n    \"href\": \"file:////#guppy\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//\",\n    \"search\": \"\",\n    \"hash\": \"#guppy\"\n  },\n  {\n    \"input\": \"file://spider///\",\n    \"base\": null,\n    \"href\": \"file://spider///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"spider\",\n    \"hostname\": \"spider\",\n    \"port\": \"\",\n    \"pathname\": \"///\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:\\\\\\\\localhost//\",\n    \"base\": null,\n    \"href\": \"file:////\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///localhost//cat\",\n    \"base\": null,\n    \"href\": \"file:///localhost//cat\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/localhost//cat\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://\\\\/localhost//cat\",\n    \"base\": null,\n    \"href\": \"file:////localhost//cat\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//localhost//cat\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://localhost//a//../..//\",\n    \"base\": null,\n    \"href\": \"file://///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"///\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/////mouse\",\n    \"base\": \"file:///elephant\",\n    \"href\": \"file://///mouse\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"///mouse\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"\\\\//pig\",\n    \"base\": \"file://lion/\",\n    \"href\": \"file:///pig\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/pig\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"\\\\/localhost//pig\",\n    \"base\": \"file://lion/\",\n    \"href\": \"file:////pig\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//pig\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"//localhost//pig\",\n    \"base\": \"file://lion/\",\n    \"href\": \"file:////pig\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//pig\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/..//localhost//pig\",\n    \"base\": \"file://lion/\",\n    \"href\": \"file://lion//localhost//pig\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"lion\",\n    \"hostname\": \"lion\",\n    \"port\": \"\",\n    \"pathname\": \"//localhost//pig\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://\",\n    \"base\": \"file://ape/\",\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# File URLs with non-empty hosts\",\n  {\n    \"input\": \"/rooibos\",\n    \"base\": \"file://tea/\",\n    \"href\": \"file://tea/rooibos\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"tea\",\n    \"hostname\": \"tea\",\n    \"port\": \"\",\n    \"pathname\": \"/rooibos\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/?chai\",\n    \"base\": \"file://tea/\",\n    \"href\": \"file://tea/?chai\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"tea\",\n    \"hostname\": \"tea\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"?chai\",\n    \"hash\": \"\"\n  },\n  \"# Windows drive letter handling with the 'file:' base URL\",\n  {\n    \"input\": \"C|\",\n    \"base\": \"file://host/dir/file\",\n    \"href\": \"file://host/C:\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/C:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"C|\",\n    \"base\": \"file://host/D:/dir1/dir2/file\",\n    \"href\": \"file://host/C:\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/C:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"C|#\",\n    \"base\": \"file://host/dir/file\",\n    \"href\": \"file://host/C:#\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/C:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"C|?\",\n    \"base\": \"file://host/dir/file\",\n    \"href\": \"file://host/C:?\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/C:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"C|/\",\n    \"base\": \"file://host/dir/file\",\n    \"href\": \"file://host/C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"C|\\n/\",\n    \"base\": \"file://host/dir/file\",\n    \"href\": \"file://host/C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"C|\\\\\",\n    \"base\": \"file://host/dir/file\",\n    \"href\": \"file://host/C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"C\",\n    \"base\": \"file://host/dir/file\",\n    \"href\": \"file://host/dir/C\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/dir/C\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"C|a\",\n    \"base\": \"file://host/dir/file\",\n    \"href\": \"file://host/dir/C|a\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/dir/C|a\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Windows drive letter quirk in the file slash state\",\n  {\n    \"input\": \"/c:/foo/bar\",\n    \"base\": \"file:///c:/baz/qux\",\n    \"href\": \"file:///c:/foo/bar\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/c:/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/c|/foo/bar\",\n    \"base\": \"file:///c:/baz/qux\",\n    \"href\": \"file:///c:/foo/bar\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/c:/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:\\\\c:\\\\foo\\\\bar\",\n    \"base\": \"file:///c:/baz/qux\",\n    \"href\": \"file:///c:/foo/bar\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/c:/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/c:/foo/bar\",\n    \"base\": \"file://host/path\",\n    \"href\": \"file://host/c:/foo/bar\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/c:/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Do not drop the host in the presence of a drive letter\",\n  {\n    \"input\": \"file://example.net/C:/\",\n    \"base\": null,\n    \"href\": \"file://example.net/C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.net\",\n    \"hostname\": \"example.net\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://1.2.3.4/C:/\",\n    \"base\": null,\n    \"href\": \"file://1.2.3.4/C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"1.2.3.4\",\n    \"hostname\": \"1.2.3.4\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://[1::8]/C:/\",\n    \"base\": null,\n    \"href\": \"file://[1::8]/C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"[1::8]\",\n    \"hostname\": \"[1::8]\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Copy the host from the base URL in the following cases\",\n  {\n    \"input\": \"C|/\",\n    \"base\": \"file://host/\",\n    \"href\": \"file://host/C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/C:/\",\n    \"base\": \"file://host/\",\n    \"href\": \"file://host/C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:C:/\",\n    \"base\": \"file://host/\",\n    \"href\": \"file://host/C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:/C:/\",\n    \"base\": \"file://host/\",\n    \"href\": \"file://host/C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Copy the empty host from the input in the following cases\",\n  {\n    \"input\": \"//C:/\",\n    \"base\": \"file://host/\",\n    \"href\": \"file:///C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://C:/\",\n    \"base\": \"file://host/\",\n    \"href\": \"file:///C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///C:/\",\n    \"base\": \"file://host/\",\n    \"href\": \"file:///C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///C:/\",\n    \"base\": \"file://host/\",\n    \"href\": \"file:///C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# Windows drive letter quirk (no host)\",\n  {\n    \"input\": \"file:/C|/\",\n    \"base\": null,\n    \"href\": \"file:///C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://C|/\",\n    \"base\": null,\n    \"href\": \"file:///C:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/C:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# file URLs without base URL by Rimas Misevičius\",\n  {\n    \"input\": \"file:\",\n    \"base\": null,\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:?q=v\",\n    \"base\": null,\n    \"href\": \"file:///?q=v\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"?q=v\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:#frag\",\n    \"base\": null,\n    \"href\": \"file:///#frag\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"#frag\"\n  },\n  \"# file: drive letter cases from https://crbug.com/1078698\",\n  {\n    \"input\": \"file:///Y:\",\n    \"base\": null,\n    \"href\": \"file:///Y:\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/Y:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///Y:/\",\n    \"base\": null,\n    \"href\": \"file:///Y:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/Y:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///./Y\",\n    \"base\": null,\n    \"href\": \"file:///Y\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/Y\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///./Y:\",\n    \"base\": null,\n    \"href\": \"file:///Y:\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/Y:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"\\\\\\\\\\\\.\\\\Y:\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  \"# file: drive letter cases from https://crbug.com/1078698 but lowercased\",\n  {\n    \"input\": \"file:///y:\",\n    \"base\": null,\n    \"href\": \"file:///y:\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/y:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///y:/\",\n    \"base\": null,\n    \"href\": \"file:///y:/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/y:/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///./y\",\n    \"base\": null,\n    \"href\": \"file:///y\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/y\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///./y:\",\n    \"base\": null,\n    \"href\": \"file:///y:\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/y:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"\\\\\\\\\\\\.\\\\y:\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  \"# Additional file URL tests for (https://github.com/whatwg/url/issues/405)\",\n  {\n    \"input\": \"file://localhost//a//../..//foo\",\n    \"base\": null,\n    \"href\": \"file://///foo\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"///foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://localhost////foo\",\n    \"base\": null,\n    \"href\": \"file://////foo\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"////foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:////foo\",\n    \"base\": null,\n    \"href\": \"file:////foo\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//foo\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///one/two\",\n    \"base\": \"file:///\",\n    \"href\": \"file:///one/two\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/one/two\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:////one/two\",\n    \"base\": \"file:///\",\n    \"href\": \"file:////one/two\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//one/two\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"//one/two\",\n    \"base\": \"file:///\",\n    \"href\": \"file://one/two\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"one\",\n    \"hostname\": \"one\",\n    \"port\": \"\",\n    \"pathname\": \"/two\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///one/two\",\n    \"base\": \"file:///\",\n    \"href\": \"file:///one/two\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/one/two\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"////one/two\",\n    \"base\": \"file:///\",\n    \"href\": \"file:////one/two\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//one/two\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:///.//\",\n    \"base\": \"file:////\",\n    \"href\": \"file:////\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"File URL tests for https://github.com/whatwg/url/issues/549\",\n  {\n    \"input\": \"file:.//p\",\n    \"base\": null,\n    \"href\": \"file:////p\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//p\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file:/.//p\",\n    \"base\": null,\n    \"href\": \"file:////p\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//p\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# IPv6 tests\",\n  {\n    \"input\": \"http://[1:0::]\",\n    \"base\": \"http://example.net/\",\n    \"href\": \"http://[1::]/\",\n    \"origin\": \"http://[1::]\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"[1::]\",\n    \"hostname\": \"[1::]\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://[0:1:2:3:4:5:6:7:8]\",\n    \"base\": \"http://example.net/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://[0::0::0]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://[0:.0]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://[0:0:]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://[0:1:2:3:4:5:6:7.0.0.0.1]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://[0:1.00.0.0.0]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://[0:1.290.0.0.0]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://[0:1.23.23]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"# Empty host\",\n  {\n    \"input\": \"http://?\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://#\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"Port overflow (2^32 + 81)\",\n  {\n    \"input\": \"http://f:4294967377/c\",\n    \"base\": \"http://example.org/\",\n    \"failure\": true\n  },\n  \"Port overflow (2^64 + 81)\",\n  {\n    \"input\": \"http://f:18446744073709551697/c\",\n    \"base\": \"http://example.org/\",\n    \"failure\": true\n  },\n  \"Port overflow (2^128 + 81)\",\n  {\n    \"input\": \"http://f:340282366920938463463374607431768211537/c\",\n    \"base\": \"http://example.org/\",\n    \"failure\": true\n  },\n  \"# Non-special-URL path tests\",\n  {\n    \"input\": \"sc://ñ\",\n    \"base\": null,\n    \"href\": \"sc://%C3%B1\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"%C3%B1\",\n    \"hostname\": \"%C3%B1\",\n    \"port\": \"\",\n    \"pathname\": \"\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"sc://ñ?x\",\n    \"base\": null,\n    \"href\": \"sc://%C3%B1?x\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"%C3%B1\",\n    \"hostname\": \"%C3%B1\",\n    \"port\": \"\",\n    \"pathname\": \"\",\n    \"search\": \"?x\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"sc://ñ#x\",\n    \"base\": null,\n    \"href\": \"sc://%C3%B1#x\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"%C3%B1\",\n    \"hostname\": \"%C3%B1\",\n    \"port\": \"\",\n    \"pathname\": \"\",\n    \"search\": \"\",\n    \"hash\": \"#x\"\n  },\n  {\n    \"input\": \"#x\",\n    \"base\": \"sc://ñ\",\n    \"href\": \"sc://%C3%B1#x\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"%C3%B1\",\n    \"hostname\": \"%C3%B1\",\n    \"port\": \"\",\n    \"pathname\": \"\",\n    \"search\": \"\",\n    \"hash\": \"#x\"\n  },\n  {\n    \"input\": \"?x\",\n    \"base\": \"sc://ñ\",\n    \"href\": \"sc://%C3%B1?x\",\n    \"origin\": \"null\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"%C3%B1\",\n    \"hostname\": \"%C3%B1\",\n    \"port\": \"\",\n    \"pathname\": \"\",\n    \"search\": \"?x\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"sc://?\",\n    \"base\": null,\n    \"href\": \"sc://?\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"sc://#\",\n    \"base\": null,\n    \"href\": \"sc://#\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///\",\n    \"base\": \"sc://x/\",\n    \"href\": \"sc:///\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"////\",\n    \"base\": \"sc://x/\",\n    \"href\": \"sc:////\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"////x/\",\n    \"base\": \"sc://x/\",\n    \"href\": \"sc:////x/\",\n    \"protocol\": \"sc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//x/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"tftp://foobar.com/someconfig;mode=netascii\",\n    \"base\": null,\n    \"href\": \"tftp://foobar.com/someconfig;mode=netascii\",\n    \"origin\": \"null\",\n    \"protocol\": \"tftp:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"foobar.com\",\n    \"hostname\": \"foobar.com\",\n    \"port\": \"\",\n    \"pathname\": \"/someconfig;mode=netascii\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"telnet://user:pass@foobar.com:23/\",\n    \"base\": null,\n    \"href\": \"telnet://user:pass@foobar.com:23/\",\n    \"origin\": \"null\",\n    \"protocol\": \"telnet:\",\n    \"username\": \"user\",\n    \"password\": \"pass\",\n    \"host\": \"foobar.com:23\",\n    \"hostname\": \"foobar.com\",\n    \"port\": \"23\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ut2004://10.10.10.10:7777/Index.ut2\",\n    \"base\": null,\n    \"href\": \"ut2004://10.10.10.10:7777/Index.ut2\",\n    \"origin\": \"null\",\n    \"protocol\": \"ut2004:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"10.10.10.10:7777\",\n    \"hostname\": \"10.10.10.10\",\n    \"port\": \"7777\",\n    \"pathname\": \"/Index.ut2\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"redis://foo:bar@somehost:6379/0?baz=bam&qux=baz\",\n    \"base\": null,\n    \"href\": \"redis://foo:bar@somehost:6379/0?baz=bam&qux=baz\",\n    \"origin\": \"null\",\n    \"protocol\": \"redis:\",\n    \"username\": \"foo\",\n    \"password\": \"bar\",\n    \"host\": \"somehost:6379\",\n    \"hostname\": \"somehost\",\n    \"port\": \"6379\",\n    \"pathname\": \"/0\",\n    \"search\": \"?baz=bam&qux=baz\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"rsync://foo@host:911/sup\",\n    \"base\": null,\n    \"href\": \"rsync://foo@host:911/sup\",\n    \"origin\": \"null\",\n    \"protocol\": \"rsync:\",\n    \"username\": \"foo\",\n    \"password\": \"\",\n    \"host\": \"host:911\",\n    \"hostname\": \"host\",\n    \"port\": \"911\",\n    \"pathname\": \"/sup\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"git://github.com/foo/bar.git\",\n    \"base\": null,\n    \"href\": \"git://github.com/foo/bar.git\",\n    \"origin\": \"null\",\n    \"protocol\": \"git:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"github.com\",\n    \"hostname\": \"github.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar.git\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"irc://myserver.com:6999/channel?passwd\",\n    \"base\": null,\n    \"href\": \"irc://myserver.com:6999/channel?passwd\",\n    \"origin\": \"null\",\n    \"protocol\": \"irc:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"myserver.com:6999\",\n    \"hostname\": \"myserver.com\",\n    \"port\": \"6999\",\n    \"pathname\": \"/channel\",\n    \"search\": \"?passwd\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"dns://fw.example.org:9999/foo.bar.org?type=TXT\",\n    \"base\": null,\n    \"href\": \"dns://fw.example.org:9999/foo.bar.org?type=TXT\",\n    \"origin\": \"null\",\n    \"protocol\": \"dns:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"fw.example.org:9999\",\n    \"hostname\": \"fw.example.org\",\n    \"port\": \"9999\",\n    \"pathname\": \"/foo.bar.org\",\n    \"search\": \"?type=TXT\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"ldap://localhost:389/ou=People,o=JNDITutorial\",\n    \"base\": null,\n    \"href\": \"ldap://localhost:389/ou=People,o=JNDITutorial\",\n    \"origin\": \"null\",\n    \"protocol\": \"ldap:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"localhost:389\",\n    \"hostname\": \"localhost\",\n    \"port\": \"389\",\n    \"pathname\": \"/ou=People,o=JNDITutorial\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"git+https://github.com/foo/bar\",\n    \"base\": null,\n    \"href\": \"git+https://github.com/foo/bar\",\n    \"origin\": \"null\",\n    \"protocol\": \"git+https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"github.com\",\n    \"hostname\": \"github.com\",\n    \"port\": \"\",\n    \"pathname\": \"/foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"urn:ietf:rfc:2648\",\n    \"base\": null,\n    \"href\": \"urn:ietf:rfc:2648\",\n    \"origin\": \"null\",\n    \"protocol\": \"urn:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"ietf:rfc:2648\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"tag:joe@example.org,2001:foo/bar\",\n    \"base\": null,\n    \"href\": \"tag:joe@example.org,2001:foo/bar\",\n    \"origin\": \"null\",\n    \"protocol\": \"tag:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"joe@example.org,2001:foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Serialize /. in path\",\n  {\n    \"input\": \"non-spec:/.//\",\n    \"base\": null,\n    \"href\": \"non-spec:/.//\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"non-spec:/..//\",\n    \"base\": null,\n    \"href\": \"non-spec:/.//\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"non-spec:/a/..//\",\n    \"base\": null,\n    \"href\": \"non-spec:/.//\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"non-spec:/.//path\",\n    \"base\": null,\n    \"href\": \"non-spec:/.//path\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"non-spec:/..//path\",\n    \"base\": null,\n    \"href\": \"non-spec:/.//path\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"non-spec:/a/..//path\",\n    \"base\": null,\n    \"href\": \"non-spec:/.//path\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/.//path\",\n    \"base\": \"non-spec:/p\",\n    \"href\": \"non-spec:/.//path\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/..//path\",\n    \"base\": \"non-spec:/p\",\n    \"href\": \"non-spec:/.//path\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"..//path\",\n    \"base\": \"non-spec:/p\",\n    \"href\": \"non-spec:/.//path\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"a/..//path\",\n    \"base\": \"non-spec:/p\",\n    \"href\": \"non-spec:/.//path\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"\",\n    \"base\": \"non-spec:/..//p\",\n    \"href\": \"non-spec:/.//p\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//p\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"path\",\n    \"base\": \"non-spec:/..//p\",\n    \"href\": \"non-spec:/.//path\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"//path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Do not serialize /. in path\",\n  {\n    \"input\": \"../path\",\n    \"base\": \"non-spec:/.//p\",\n    \"href\": \"non-spec:/path\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# percent encoded hosts in non-special-URLs\",\n  {\n    \"input\": \"non-special://%E2%80%A0/\",\n    \"base\": null,\n    \"href\": \"non-special://%E2%80%A0/\",\n    \"protocol\": \"non-special:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"%E2%80%A0\",\n    \"hostname\": \"%E2%80%A0\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"non-special://H%4fSt/path\",\n    \"base\": null,\n    \"href\": \"non-special://H%4fSt/path\",\n    \"protocol\": \"non-special:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"H%4fSt\",\n    \"hostname\": \"H%4fSt\",\n    \"port\": \"\",\n    \"pathname\": \"/path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"# IPv6 in non-special-URLs\",\n  {\n    \"input\": \"non-special://[1:2:0:0:5:0:0:0]/\",\n    \"base\": null,\n    \"href\": \"non-special://[1:2:0:0:5::]/\",\n    \"protocol\": \"non-special:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"[1:2:0:0:5::]\",\n    \"hostname\": \"[1:2:0:0:5::]\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"non-special://[1:2:0:0:0:0:0:3]/\",\n    \"base\": null,\n    \"href\": \"non-special://[1:2::3]/\",\n    \"protocol\": \"non-special:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"[1:2::3]\",\n    \"hostname\": \"[1:2::3]\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"non-special://[1:2::3]:80/\",\n    \"base\": null,\n    \"href\": \"non-special://[1:2::3]:80/\",\n    \"protocol\": \"non-special:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"[1:2::3]:80\",\n    \"hostname\": \"[1:2::3]\",\n    \"port\": \"80\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"non-special://[:80/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"blob:https://example.com:443/\",\n    \"base\": null,\n    \"href\": \"blob:https://example.com:443/\",\n    \"origin\": \"https://example.com\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"https://example.com:443/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"blob:http://example.org:88/\",\n    \"base\": null,\n    \"href\": \"blob:http://example.org:88/\",\n    \"origin\": \"http://example.org:88\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"http://example.org:88/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"blob:d3958f5c-0777-0845-9dcf-2cb28783acaf\",\n    \"base\": null,\n    \"href\": \"blob:d3958f5c-0777-0845-9dcf-2cb28783acaf\",\n    \"origin\": \"null\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"d3958f5c-0777-0845-9dcf-2cb28783acaf\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"blob:\",\n    \"base\": null,\n    \"href\": \"blob:\",\n    \"origin\": \"null\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"blob: in blob:\",\n  {\n    \"input\": \"blob:blob:\",\n    \"base\": null,\n    \"href\": \"blob:blob:\",\n    \"origin\": \"null\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"blob:\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"blob:blob:https://example.org/\",\n    \"base\": null,\n    \"href\": \"blob:blob:https://example.org/\",\n    \"origin\": \"null\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"blob:https://example.org/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Non-http(s): in blob:\",\n  {\n    \"input\": \"blob:about:blank\",\n    \"base\": null,\n    \"href\": \"blob:about:blank\",\n    \"origin\": \"null\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"about:blank\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"blob:file://host/path\",\n    \"base\": null,\n    \"href\": \"blob:file://host/path\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"file://host/path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"blob:ftp://host/path\",\n    \"base\": null,\n    \"href\": \"blob:ftp://host/path\",\n    \"origin\": \"null\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"ftp://host/path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"blob:ws://example.org/\",\n    \"base\": null,\n    \"href\": \"blob:ws://example.org/\",\n    \"origin\": \"null\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"ws://example.org/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"blob:wss://example.org/\",\n    \"base\": null,\n    \"href\": \"blob:wss://example.org/\",\n    \"origin\": \"null\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"wss://example.org/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Percent-encoded http: in blob:\",\n  {\n    \"input\": \"blob:http%3a//example.org/\",\n    \"base\": null,\n    \"href\": \"blob:http%3a//example.org/\",\n    \"origin\": \"null\",\n    \"protocol\": \"blob:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"http%3a//example.org/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Invalid IPv4 radix digits\",\n  {\n    \"input\": \"http://0x7f.0.0.0x7g\",\n    \"base\": null,\n    \"href\": \"http://0x7f.0.0.0x7g/\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"0x7f.0.0.0x7g\",\n    \"hostname\": \"0x7f.0.0.0x7g\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://0X7F.0.0.0X7G\",\n    \"base\": null,\n    \"href\": \"http://0x7f.0.0.0x7g/\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"0x7f.0.0.0x7g\",\n    \"hostname\": \"0x7f.0.0.0x7g\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Invalid IPv4 portion of IPv6 address\",\n  {\n    \"input\": \"http://[::127.0.0.0.1]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"Uncompressed IPv6 addresses with 0\",\n  {\n    \"input\": \"http://[0:1:0:1:0:1:0:1]\",\n    \"base\": null,\n    \"href\": \"http://[0:1:0:1:0:1:0:1]/\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"[0:1:0:1:0:1:0:1]\",\n    \"hostname\": \"[0:1:0:1:0:1:0:1]\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://[1:0:1:0:1:0:1:0]\",\n    \"base\": null,\n    \"href\": \"http://[1:0:1:0:1:0:1:0]/\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"[1:0:1:0:1:0:1:0]\",\n    \"hostname\": \"[1:0:1:0:1:0:1:0]\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Percent-encoded query and fragment\",\n  {\n    \"input\": \"http://example.org/test?\\u0022\",\n    \"base\": null,\n    \"href\": \"http://example.org/test?%22\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?%22\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.org/test?\\u0023\",\n    \"base\": null,\n    \"href\": \"http://example.org/test?#\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.org/test?\\u003C\",\n    \"base\": null,\n    \"href\": \"http://example.org/test?%3C\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?%3C\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.org/test?\\u003E\",\n    \"base\": null,\n    \"href\": \"http://example.org/test?%3E\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?%3E\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.org/test?\\u2323\",\n    \"base\": null,\n    \"href\": \"http://example.org/test?%E2%8C%A3\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?%E2%8C%A3\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.org/test?%23%23\",\n    \"base\": null,\n    \"href\": \"http://example.org/test?%23%23\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?%23%23\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.org/test?%GH\",\n    \"base\": null,\n    \"href\": \"http://example.org/test?%GH\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?%GH\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"http://example.org/test?a#%EF\",\n    \"base\": null,\n    \"href\": \"http://example.org/test?a#%EF\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?a\",\n    \"hash\": \"#%EF\"\n  },\n  {\n    \"input\": \"http://example.org/test?a#%GH\",\n    \"base\": null,\n    \"href\": \"http://example.org/test?a#%GH\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?a\",\n    \"hash\": \"#%GH\"\n  },\n  \"URLs that require a non-about:blank base. (Also serve as invalid base tests.)\",\n  {\n    \"input\": \"a\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"a/\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"a//\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  \"Bases that don't fail to parse but fail to be bases\",\n  {\n    \"input\": \"test-a-colon.html\",\n    \"base\": \"a:\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"test-a-colon-b.html\",\n    \"base\": \"a:b\",\n    \"failure\": true\n  },\n  \"Other base URL tests, that must succeed\",\n  {\n    \"input\": \"test-a-colon-slash.html\",\n    \"base\": \"a:/\",\n    \"href\": \"a:/test-a-colon-slash.html\",\n    \"protocol\": \"a:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test-a-colon-slash.html\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"test-a-colon-slash-slash.html\",\n    \"base\": \"a://\",\n    \"href\": \"a:///test-a-colon-slash-slash.html\",\n    \"protocol\": \"a:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test-a-colon-slash-slash.html\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"test-a-colon-slash-b.html\",\n    \"base\": \"a:/b\",\n    \"href\": \"a:/test-a-colon-slash-b.html\",\n    \"protocol\": \"a:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test-a-colon-slash-b.html\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"test-a-colon-slash-slash-b.html\",\n    \"base\": \"a://b\",\n    \"href\": \"a://b/test-a-colon-slash-slash-b.html\",\n    \"protocol\": \"a:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"b\",\n    \"hostname\": \"b\",\n    \"port\": \"\",\n    \"pathname\": \"/test-a-colon-slash-slash-b.html\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Null code point in fragment\",\n  {\n    \"input\": \"http://example.org/test?a#b\\u0000c\",\n    \"base\": null,\n    \"href\": \"http://example.org/test?a#b%00c\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?a\",\n    \"hash\": \"#b%00c\"\n  },\n  {\n    \"input\": \"non-spec://example.org/test?a#b\\u0000c\",\n    \"base\": null,\n    \"href\": \"non-spec://example.org/test?a#b%00c\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?a\",\n    \"hash\": \"#b%00c\"\n  },\n  {\n    \"input\": \"non-spec:/test?a#b\\u0000c\",\n    \"base\": null,\n    \"href\": \"non-spec:/test?a#b%00c\",\n    \"protocol\": \"non-spec:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"?a\",\n    \"hash\": \"#b%00c\"\n  },\n  \"First scheme char - not allowed: https://github.com/whatwg/url/issues/464\",\n  {\n    \"input\": \"10.0.0.7:8080/foo.html\",\n    \"base\": \"file:///some/dir/bar.html\",\n    \"href\": \"file:///some/dir/10.0.0.7:8080/foo.html\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/some/dir/10.0.0.7:8080/foo.html\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Subsequent scheme chars - not allowed\",\n  {\n    \"input\": \"a!@$*=/foo.html\",\n    \"base\": \"file:///some/dir/bar.html\",\n    \"href\": \"file:///some/dir/a!@$*=/foo.html\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/some/dir/a!@$*=/foo.html\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"First and subsequent scheme chars - allowed\",\n  {\n    \"input\": \"a1234567890-+.:foo/bar\",\n    \"base\": \"http://example.com/dir/file\",\n    \"href\": \"a1234567890-+.:foo/bar\",\n    \"protocol\": \"a1234567890-+.:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"foo/bar\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"IDNA ignored code points in file URLs hosts\",\n  {\n    \"input\": \"file://a\\u00ADb/p\",\n    \"base\": null,\n    \"href\": \"file://ab/p\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"ab\",\n    \"hostname\": \"ab\",\n    \"port\": \"\",\n    \"pathname\": \"/p\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"file://a%C2%ADb/p\",\n    \"base\": null,\n    \"href\": \"file://ab/p\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"ab\",\n    \"hostname\": \"ab\",\n    \"port\": \"\",\n    \"pathname\": \"/p\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"IDNA hostnames which get mapped to 'localhost'\",\n  {\n    \"input\": \"file://loC𝐀𝐋𝐇𝐨𝐬𝐭/usr/bin\",\n    \"base\": null,\n    \"href\": \"file:///usr/bin\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/usr/bin\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Empty host after the domain to ASCII\",\n  {\n    \"input\": \"file://\\u00ad/p\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"file://%C2%AD/p\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"file://xn--/p\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"https://bugzilla.mozilla.org/show_bug.cgi?id=1647058\",\n  {\n    \"input\": \"#link\",\n    \"base\": \"https://example.org/##link\",\n    \"href\": \"https://example.org/#link\",\n    \"protocol\": \"https:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"#link\"\n  },\n  \"UTF-8 percent-encode of C0 control percent-encode set and supersets\",\n  {\n    \"input\": \"non-special:cannot-be-a-base-url-\\u0000\\u0001\\u001F\\u001E\\u007E\\u007F\\u0080\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\": \"non-special:cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80\",\n    \"origin\": \"null\",\n    \"password\": \"\",\n    \"pathname\": \"cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80\",\n    \"port\": \"\",\n    \"protocol\": \"non-special:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"https://www.example.com/path{\\u007Fpath.html?query'\\u007F=query#fragment<\\u007Ffragment\",\n    \"base\": null,\n    \"hash\": \"#fragment%3C%7Ffragment\",\n    \"host\": \"www.example.com\",\n    \"hostname\": \"www.example.com\",\n    \"href\": \"https://www.example.com/path%7B%7Fpath.html?query%27%7F=query#fragment%3C%7Ffragment\",\n    \"origin\": \"https://www.example.com\",\n    \"password\": \"\",\n    \"pathname\": \"/path%7B%7Fpath.html\",\n    \"port\": \"\",\n    \"protocol\": \"https:\",\n    \"search\": \"?query%27%7F=query\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"https://user:pass[\\u007F@foo/bar\",\n    \"base\": \"http://example.org\",\n    \"hash\": \"\",\n    \"host\": \"foo\",\n    \"hostname\": \"foo\",\n    \"href\": \"https://user:pass%5B%7F@foo/bar\",\n    \"origin\": \"https://foo\",\n    \"password\": \"pass%5B%7F\",\n    \"pathname\": \"/bar\",\n    \"port\": \"\",\n    \"protocol\": \"https:\",\n    \"search\": \"\",\n    \"username\": \"user\"\n  },\n  \"Tests for the distinct percent-encode sets\",\n  {\n    \"input\": \"foo:// !\\\"$%&'()*+,-.;<=>@[\\\\]^_`{|}~@host/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\": \"foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/\",\n    \"origin\": \"null\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"foo:\",\n    \"search\": \"\",\n    \"username\": \"%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~\"\n  },\n  {\n    \"input\": \"wss:// !\\\"$%&'()*+,-.;<=>@[]^_`{|}~@host/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\": \"wss://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/\",\n    \"origin\": \"wss://host\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"wss:\",\n    \"search\": \"\",\n    \"username\": \"%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~\"\n  },\n  {\n    \"input\": \"foo://joe: !\\\"$%&'()*+,-.:;<=>@[\\\\]^_`{|}~@host/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\": \"foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/\",\n    \"origin\": \"null\",\n    \"password\": \"%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"foo:\",\n    \"search\": \"\",\n    \"username\": \"joe\"\n  },\n  {\n    \"input\": \"wss://joe: !\\\"$%&'()*+,-.:;<=>@[]^_`{|}~@host/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\": \"wss://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/\",\n    \"origin\": \"wss://host\",\n    \"password\": \"%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"wss:\",\n    \"search\": \"\",\n    \"username\": \"joe\"\n  },\n  {\n    \"input\": \"foo://!\\\"$%&'()*+,-.;=_`{}~/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"!\\\"$%&'()*+,-.;=_`{}~\",\n    \"hostname\": \"!\\\"$%&'()*+,-.;=_`{}~\",\n    \"href\":\"foo://!\\\"$%&'()*+,-.;=_`{}~/\",\n    \"origin\": \"null\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"foo:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"wss://!\\\"$&'()*+,-.;=_`{}~/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"!\\\"$&'()*+,-.;=_`{}~\",\n    \"hostname\": \"!\\\"$&'()*+,-.;=_`{}~\",\n    \"href\":\"wss://!\\\"$&'()*+,-.;=_`{}~/\",\n    \"origin\": \"wss://!\\\"$&'()*+,-.;=_`{}~\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"wss:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"foo://host/ !\\\"$%&'()*+,-./:;<=>@[\\\\]^_`{|}~\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\": \"foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\\\]^_%60%7B|%7D~\",\n    \"origin\": \"null\",\n    \"password\": \"\",\n    \"pathname\": \"/%20!%22$%&'()*+,-./:;%3C=%3E@[\\\\]^_%60%7B|%7D~\",\n    \"port\":\"\",\n    \"protocol\": \"foo:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"wss://host/ !\\\"$%&'()*+,-./:;<=>@[\\\\]^_`{|}~\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\": \"wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~\",\n    \"origin\": \"wss://host\",\n    \"password\": \"\",\n    \"pathname\": \"/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~\",\n    \"port\":\"\",\n    \"protocol\": \"wss:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"foo://host/dir/? !\\\"$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\": \"foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\\\]^_`{|}~\",\n    \"origin\": \"null\",\n    \"password\": \"\",\n    \"pathname\": \"/dir/\",\n    \"port\":\"\",\n    \"protocol\": \"foo:\",\n    \"search\": \"?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\\\]^_`{|}~\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"wss://host/dir/? !\\\"$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\": \"wss://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\\\]^_`{|}~\",\n    \"origin\": \"wss://host\",\n    \"password\": \"\",\n    \"pathname\": \"/dir/\",\n    \"port\":\"\",\n    \"protocol\": \"wss:\",\n    \"search\": \"?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\\\]^_`{|}~\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"foo://host/dir/# !\\\"#$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~\",\n    \"base\": null,\n    \"hash\": \"#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\\\]^_%60{|}~\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\": \"foo://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\\\]^_%60{|}~\",\n    \"origin\": \"null\",\n    \"password\": \"\",\n    \"pathname\": \"/dir/\",\n    \"port\":\"\",\n    \"protocol\": \"foo:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"wss://host/dir/# !\\\"#$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~\",\n    \"base\": null,\n    \"hash\": \"#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\\\]^_%60{|}~\",\n    \"host\": \"host\",\n    \"hostname\": \"host\",\n    \"href\": \"wss://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\\\]^_%60{|}~\",\n    \"origin\": \"wss://host\",\n    \"password\": \"\",\n    \"pathname\": \"/dir/\",\n    \"port\":\"\",\n    \"protocol\": \"wss:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  \"Ensure that input schemes are not ignored when resolving non-special URLs\",\n  {\n    \"input\": \"abc:rootless\",\n    \"base\": \"abc://host/path\",\n    \"hash\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\":\"abc:rootless\",\n    \"password\": \"\",\n    \"pathname\": \"rootless\",\n    \"port\":\"\",\n    \"protocol\": \"abc:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"abc:rootless\",\n    \"base\": \"abc:/path\",\n    \"hash\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\":\"abc:rootless\",\n    \"password\": \"\",\n    \"pathname\": \"rootless\",\n    \"port\":\"\",\n    \"protocol\": \"abc:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"abc:rootless\",\n    \"base\": \"abc:path\",\n    \"hash\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\":\"abc:rootless\",\n    \"password\": \"\",\n    \"pathname\": \"rootless\",\n    \"port\":\"\",\n    \"protocol\": \"abc:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"abc:/rooted\",\n    \"base\": \"abc://host/path\",\n    \"hash\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\":\"abc:/rooted\",\n    \"password\": \"\",\n    \"pathname\": \"/rooted\",\n    \"port\":\"\",\n    \"protocol\": \"abc:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  \"Empty query and fragment with blank should throw an error\",\n  {\n    \"input\": \"#\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"any-base\"\n  },\n  {\n    \"input\": \"?\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  \"Last component looks like a number, but not valid IPv4\",\n  {\n    \"input\": \"http://1.2.3.4.5\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://1.2.3.4.5.\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://0..0x300/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://0..0x300./\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://256.256.256.256.256\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://256.256.256.256.256.\",\n    \"base\": \"http://other.com/\",\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://1.2.3.08\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://1.2.3.08.\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://1.2.3.09\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://09.2.3.4\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://09.2.3.4.\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://01.2.3.4.5\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://01.2.3.4.5.\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://0x100.2.3.4\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://0x100.2.3.4.\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://0x1.2.3.4.5\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://0x1.2.3.4.5.\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://foo.1.2.3.4\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://foo.1.2.3.4.\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://foo.2.3.4\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://foo.2.3.4.\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://foo.09\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://foo.09.\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://foo.0x4\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://foo.0x4.\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://foo.09..\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"foo.09..\",\n    \"hostname\": \"foo.09..\",\n    \"href\":\"http://foo.09../\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\":\"\",\n    \"protocol\": \"http:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"http://0999999999999999999/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://foo.0x\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://foo.0XFfFfFfFfFfFfFfFfFfAcE123\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"http://💩.123/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"U+0000 and U+FFFF in various places\",\n  {\n    \"input\": \"https://\\u0000y\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://x/\\u0000y\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"x\",\n    \"hostname\": \"x\",\n    \"href\": \"https://x/%00y\",\n    \"password\": \"\",\n    \"pathname\": \"/%00y\",\n    \"port\": \"\",\n    \"protocol\": \"https:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"https://x/?\\u0000y\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"x\",\n    \"hostname\": \"x\",\n    \"href\": \"https://x/?%00y\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\": \"\",\n    \"protocol\": \"https:\",\n    \"search\": \"?%00y\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"https://x/?#\\u0000y\",\n    \"base\": null,\n    \"hash\": \"#%00y\",\n    \"host\": \"x\",\n    \"hostname\": \"x\",\n    \"href\": \"https://x/?#%00y\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\": \"\",\n    \"protocol\": \"https:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"https://\\uFFFFy\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://x/\\uFFFFy\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"x\",\n    \"hostname\": \"x\",\n    \"href\": \"https://x/%EF%BF%BFy\",\n    \"password\": \"\",\n    \"pathname\": \"/%EF%BF%BFy\",\n    \"port\": \"\",\n    \"protocol\": \"https:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"https://x/?\\uFFFFy\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"x\",\n    \"hostname\": \"x\",\n    \"href\": \"https://x/?%EF%BF%BFy\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\": \"\",\n    \"protocol\": \"https:\",\n    \"search\": \"?%EF%BF%BFy\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"https://x/?#\\uFFFFy\",\n    \"base\": null,\n    \"hash\": \"#%EF%BF%BFy\",\n    \"host\": \"x\",\n    \"hostname\": \"x\",\n    \"href\": \"https://x/?#%EF%BF%BFy\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\": \"\",\n    \"protocol\": \"https:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"non-special:\\u0000y\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\": \"non-special:%00y\",\n    \"password\": \"\",\n    \"pathname\": \"%00y\",\n    \"port\": \"\",\n    \"protocol\": \"non-special:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"non-special:x/\\u0000y\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\": \"non-special:x/%00y\",\n    \"password\": \"\",\n    \"pathname\": \"x/%00y\",\n    \"port\": \"\",\n    \"protocol\": \"non-special:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"non-special:x/?\\u0000y\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\": \"non-special:x/?%00y\",\n    \"password\": \"\",\n    \"pathname\": \"x/\",\n    \"port\": \"\",\n    \"protocol\": \"non-special:\",\n    \"search\": \"?%00y\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"non-special:x/?#\\u0000y\",\n    \"base\": null,\n    \"hash\": \"#%00y\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\": \"non-special:x/?#%00y\",\n    \"password\": \"\",\n    \"pathname\": \"x/\",\n    \"port\": \"\",\n    \"protocol\": \"non-special:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"non-special:\\uFFFFy\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\": \"non-special:%EF%BF%BFy\",\n    \"password\": \"\",\n    \"pathname\": \"%EF%BF%BFy\",\n    \"port\": \"\",\n    \"protocol\": \"non-special:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"non-special:x/\\uFFFFy\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\": \"non-special:x/%EF%BF%BFy\",\n    \"password\": \"\",\n    \"pathname\": \"x/%EF%BF%BFy\",\n    \"port\": \"\",\n    \"protocol\": \"non-special:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"non-special:x/?\\uFFFFy\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\": \"non-special:x/?%EF%BF%BFy\",\n    \"password\": \"\",\n    \"pathname\": \"x/\",\n    \"port\": \"\",\n    \"protocol\": \"non-special:\",\n    \"search\": \"?%EF%BF%BFy\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"non-special:x/?#\\uFFFFy\",\n    \"base\": null,\n    \"hash\": \"#%EF%BF%BFy\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"href\": \"non-special:x/?#%EF%BF%BFy\",\n    \"password\": \"\",\n    \"pathname\": \"x/\",\n    \"port\": \"\",\n    \"protocol\": \"non-special:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"\",\n    \"base\": null,\n    \"failure\": true,\n    \"relativeTo\": \"non-opaque-path-base\"\n  },\n  {\n    \"input\": \"https://example.com/\\\"quoted\\\"\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"example.com\",\n    \"hostname\": \"example.com\",\n    \"href\": \"https://example.com/%22quoted%22\",\n    \"origin\": \"https://example.com\",\n    \"password\": \"\",\n    \"pathname\": \"/%22quoted%22\",\n    \"port\": \"\",\n    \"protocol\": \"https:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"input\": \"https://a%C2%ADb/\",\n    \"base\": null,\n    \"hash\": \"\",\n    \"host\": \"ab\",\n    \"hostname\": \"ab\",\n    \"href\": \"https://ab/\",\n    \"origin\": \"https://ab\",\n    \"password\": \"\",\n    \"pathname\": \"/\",\n    \"port\": \"\",\n    \"protocol\": \"https:\",\n    \"search\": \"\",\n    \"username\": \"\"\n  },\n  {\n    \"comment\": \"Empty host after domain to ASCII\",\n    \"input\": \"https://\\u00AD/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://%C2%AD/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"https://xn--/\",\n    \"base\": null,\n    \"failure\": true\n  },\n  \"Non-special schemes that some implementations might incorrectly treat as special\",\n  {\n    \"input\": \"data://example.com:8080/pathname?search#hash\",\n    \"base\": null,\n    \"href\": \"data://example.com:8080/pathname?search#hash\",\n    \"origin\": \"null\",\n    \"protocol\": \"data:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com:8080\",\n    \"hostname\": \"example.com\",\n    \"port\": \"8080\",\n    \"pathname\": \"/pathname\",\n    \"search\": \"?search\",\n    \"hash\": \"#hash\"\n  },\n  {\n    \"input\": \"data:///test\",\n    \"base\": null,\n    \"href\": \"data:///test\",\n    \"origin\": \"null\",\n    \"protocol\": \"data:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"data://test/a/../b\",\n    \"base\": null,\n    \"href\": \"data://test/b\",\n    \"origin\": \"null\",\n    \"protocol\": \"data:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/b\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"data://:443\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"data://test:test\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"data://[:1]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"javascript://example.com:8080/pathname?search#hash\",\n    \"base\": null,\n    \"href\": \"javascript://example.com:8080/pathname?search#hash\",\n    \"origin\": \"null\",\n    \"protocol\": \"javascript:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com:8080\",\n    \"hostname\": \"example.com\",\n    \"port\": \"8080\",\n    \"pathname\": \"/pathname\",\n    \"search\": \"?search\",\n    \"hash\": \"#hash\"\n  },\n  {\n    \"input\": \"javascript:///test\",\n    \"base\": null,\n    \"href\": \"javascript:///test\",\n    \"origin\": \"null\",\n    \"protocol\": \"javascript:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"javascript://test/a/../b\",\n    \"base\": null,\n    \"href\": \"javascript://test/b\",\n    \"origin\": \"null\",\n    \"protocol\": \"javascript:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/b\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"javascript://:443\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"javascript://test:test\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"javascript://[:1]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"mailto://example.com:8080/pathname?search#hash\",\n    \"base\": null,\n    \"href\": \"mailto://example.com:8080/pathname?search#hash\",\n    \"origin\": \"null\",\n    \"protocol\": \"mailto:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com:8080\",\n    \"hostname\": \"example.com\",\n    \"port\": \"8080\",\n    \"pathname\": \"/pathname\",\n    \"search\": \"?search\",\n    \"hash\": \"#hash\"\n  },\n  {\n    \"input\": \"mailto:///test\",\n    \"base\": null,\n    \"href\": \"mailto:///test\",\n    \"origin\": \"null\",\n    \"protocol\": \"mailto:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"mailto://test/a/../b\",\n    \"base\": null,\n    \"href\": \"mailto://test/b\",\n    \"origin\": \"null\",\n    \"protocol\": \"mailto:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/b\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"mailto://:443\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"mailto://test:test\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"mailto://[:1]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"intent://example.com:8080/pathname?search#hash\",\n    \"base\": null,\n    \"href\": \"intent://example.com:8080/pathname?search#hash\",\n    \"origin\": \"null\",\n    \"protocol\": \"intent:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com:8080\",\n    \"hostname\": \"example.com\",\n    \"port\": \"8080\",\n    \"pathname\": \"/pathname\",\n    \"search\": \"?search\",\n    \"hash\": \"#hash\"\n  },\n  {\n    \"input\": \"intent:///test\",\n    \"base\": null,\n    \"href\": \"intent:///test\",\n    \"origin\": \"null\",\n    \"protocol\": \"intent:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"intent://test/a/../b\",\n    \"base\": null,\n    \"href\": \"intent://test/b\",\n    \"origin\": \"null\",\n    \"protocol\": \"intent:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/b\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"intent://:443\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"intent://test:test\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"intent://[:1]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"urn://example.com:8080/pathname?search#hash\",\n    \"base\": null,\n    \"href\": \"urn://example.com:8080/pathname?search#hash\",\n    \"origin\": \"null\",\n    \"protocol\": \"urn:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com:8080\",\n    \"hostname\": \"example.com\",\n    \"port\": \"8080\",\n    \"pathname\": \"/pathname\",\n    \"search\": \"?search\",\n    \"hash\": \"#hash\"\n  },\n  {\n    \"input\": \"urn:///test\",\n    \"base\": null,\n    \"href\": \"urn:///test\",\n    \"origin\": \"null\",\n    \"protocol\": \"urn:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"urn://test/a/../b\",\n    \"base\": null,\n    \"href\": \"urn://test/b\",\n    \"origin\": \"null\",\n    \"protocol\": \"urn:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/b\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"urn://:443\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"urn://test:test\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"urn://[:1]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"turn://example.com:8080/pathname?search#hash\",\n    \"base\": null,\n    \"href\": \"turn://example.com:8080/pathname?search#hash\",\n    \"origin\": \"null\",\n    \"protocol\": \"turn:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com:8080\",\n    \"hostname\": \"example.com\",\n    \"port\": \"8080\",\n    \"pathname\": \"/pathname\",\n    \"search\": \"?search\",\n    \"hash\": \"#hash\"\n  },\n  {\n    \"input\": \"turn:///test\",\n    \"base\": null,\n    \"href\": \"turn:///test\",\n    \"origin\": \"null\",\n    \"protocol\": \"turn:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"turn://test/a/../b\",\n    \"base\": null,\n    \"href\": \"turn://test/b\",\n    \"origin\": \"null\",\n    \"protocol\": \"turn:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/b\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"turn://:443\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"turn://test:test\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"turn://[:1]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"stun://example.com:8080/pathname?search#hash\",\n    \"base\": null,\n    \"href\": \"stun://example.com:8080/pathname?search#hash\",\n    \"origin\": \"null\",\n    \"protocol\": \"stun:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.com:8080\",\n    \"hostname\": \"example.com\",\n    \"port\": \"8080\",\n    \"pathname\": \"/pathname\",\n    \"search\": \"?search\",\n    \"hash\": \"#hash\"\n  },\n  {\n    \"input\": \"stun:///test\",\n    \"base\": null,\n    \"href\": \"stun:///test\",\n    \"origin\": \"null\",\n    \"protocol\": \"stun:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/test\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"stun://test/a/../b\",\n    \"base\": null,\n    \"href\": \"stun://test/b\",\n    \"origin\": \"null\",\n    \"protocol\": \"stun:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/b\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"stun://:443\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"stun://test:test\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"stun://[:1]\",\n    \"base\": null,\n    \"failure\": true\n  },\n  {\n    \"input\": \"w://x:0\",\n    \"base\": null,\n    \"href\": \"w://x:0\",\n    \"origin\": \"null\",\n    \"protocol\": \"w:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"x:0\",\n    \"hostname\": \"x\",\n    \"port\": \"0\",\n    \"pathname\": \"\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"west://x:0\",\n    \"base\": null,\n    \"href\": \"west://x:0\",\n    \"origin\": \"null\",\n    \"protocol\": \"west:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"x:0\",\n    \"hostname\": \"x\",\n    \"port\": \"0\",\n    \"pathname\": \"\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  \"Scheme relative path starting with multiple slashes\",\n  {\n    \"input\": \"///test\",\n    \"base\": \"http://example.org/\",\n    \"href\": \"http://test/\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///\\\\//\\\\//test\",\n    \"base\": \"http://example.org/\",\n    \"href\": \"http://test/\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"test\",\n    \"hostname\": \"test\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///example.org/path\",\n    \"base\": \"http://example.org/\",\n    \"href\": \"http://example.org/path\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///example.org/../path\",\n    \"base\": \"http://example.org/\",\n    \"href\": \"http://example.org/path\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///example.org/../../\",\n    \"base\": \"http://example.org/\",\n    \"href\": \"http://example.org/\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///example.org/../path/../../\",\n    \"base\": \"http://example.org/\",\n    \"href\": \"http://example.org/\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///example.org/../path/../../path\",\n    \"base\": \"http://example.org/\",\n    \"href\": \"http://example.org/path\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/\\\\/\\\\//example.org/../path\",\n    \"base\": \"http://example.org/\",\n    \"href\": \"http://example.org/path\",\n    \"protocol\": \"http:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"example.org\",\n    \"hostname\": \"example.org\",\n    \"port\": \"\",\n    \"pathname\": \"/path\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"///abcdef/../\",\n    \"base\": \"file:///\",\n    \"href\": \"file:///\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"/\\\\//\\\\/a/../\",\n    \"base\": \"file:///\",\n    \"href\": \"file://////\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"\",\n    \"hostname\": \"\",\n    \"port\": \"\",\n    \"pathname\": \"////\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  },\n  {\n    \"input\": \"//a/../\",\n    \"base\": \"file:///\",\n    \"href\": \"file://a/\",\n    \"protocol\": \"file:\",\n    \"username\": \"\",\n    \"password\": \"\",\n    \"host\": \"a\",\n    \"hostname\": \"a\",\n    \"port\": \"\",\n    \"pathname\": \"/\",\n    \"search\": \"\",\n    \"hash\": \"\"\n  }\n]\n"
  },
  {
    "path": "tests/test_api.py",
    "content": "import typing\n\nimport pytest\n\nimport httpx\n\n\ndef test_get(server):\n    response = httpx.get(server.url)\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    assert response.text == \"Hello, world!\"\n    assert response.http_version == \"HTTP/1.1\"\n\n\ndef test_post(server):\n    response = httpx.post(server.url, content=b\"Hello, world!\")\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_post_byte_iterator(server):\n    def data() -> typing.Iterator[bytes]:\n        yield b\"Hello\"\n        yield b\", \"\n        yield b\"world!\"\n\n    response = httpx.post(server.url, content=data())\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_post_byte_stream(server):\n    class Data(httpx.SyncByteStream):\n        def __iter__(self):\n            yield b\"Hello\"\n            yield b\", \"\n            yield b\"world!\"\n\n    response = httpx.post(server.url, content=Data())\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_options(server):\n    response = httpx.options(server.url)\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_head(server):\n    response = httpx.head(server.url)\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_put(server):\n    response = httpx.put(server.url, content=b\"Hello, world!\")\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_patch(server):\n    response = httpx.patch(server.url, content=b\"Hello, world!\")\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_delete(server):\n    response = httpx.delete(server.url)\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n\n\ndef test_stream(server):\n    with httpx.stream(\"GET\", server.url) as response:\n        response.read()\n\n    assert response.status_code == 200\n    assert response.reason_phrase == \"OK\"\n    assert response.text == \"Hello, world!\"\n    assert response.http_version == \"HTTP/1.1\"\n\n\ndef test_get_invalid_url():\n    with pytest.raises(httpx.UnsupportedProtocol):\n        httpx.get(\"invalid://example.org\")\n\n\n# check that httpcore isn't imported until we do a request\ndef test_httpcore_lazy_loading(server):\n    import sys\n\n    # unload our module if it is already loaded\n    if \"httpx\" in sys.modules:\n        del sys.modules[\"httpx\"]\n        del sys.modules[\"httpcore\"]\n    import httpx\n\n    assert \"httpcore\" not in sys.modules\n    _response = httpx.get(server.url)\n    assert \"httpcore\" in sys.modules\n"
  },
  {
    "path": "tests/test_asgi.py",
    "content": "import json\n\nimport pytest\n\nimport httpx\n\n\nasync def hello_world(scope, receive, send):\n    status = 200\n    output = b\"Hello, World!\"\n    headers = [(b\"content-type\", \"text/plain\"), (b\"content-length\", str(len(output)))]\n\n    await send({\"type\": \"http.response.start\", \"status\": status, \"headers\": headers})\n    await send({\"type\": \"http.response.body\", \"body\": output})\n\n\nasync def echo_path(scope, receive, send):\n    status = 200\n    output = json.dumps({\"path\": scope[\"path\"]}).encode(\"utf-8\")\n    headers = [(b\"content-type\", \"text/plain\"), (b\"content-length\", str(len(output)))]\n\n    await send({\"type\": \"http.response.start\", \"status\": status, \"headers\": headers})\n    await send({\"type\": \"http.response.body\", \"body\": output})\n\n\nasync def echo_raw_path(scope, receive, send):\n    status = 200\n    output = json.dumps({\"raw_path\": scope[\"raw_path\"].decode(\"ascii\")}).encode(\"utf-8\")\n    headers = [(b\"content-type\", \"text/plain\"), (b\"content-length\", str(len(output)))]\n\n    await send({\"type\": \"http.response.start\", \"status\": status, \"headers\": headers})\n    await send({\"type\": \"http.response.body\", \"body\": output})\n\n\nasync def echo_body(scope, receive, send):\n    status = 200\n    headers = [(b\"content-type\", \"text/plain\")]\n\n    await send({\"type\": \"http.response.start\", \"status\": status, \"headers\": headers})\n    more_body = True\n    while more_body:\n        message = await receive()\n        body = message.get(\"body\", b\"\")\n        more_body = message.get(\"more_body\", False)\n        await send({\"type\": \"http.response.body\", \"body\": body, \"more_body\": more_body})\n\n\nasync def echo_headers(scope, receive, send):\n    status = 200\n    output = json.dumps(\n        {\"headers\": [[k.decode(), v.decode()] for k, v in scope[\"headers\"]]}\n    ).encode(\"utf-8\")\n    headers = [(b\"content-type\", \"text/plain\"), (b\"content-length\", str(len(output)))]\n\n    await send({\"type\": \"http.response.start\", \"status\": status, \"headers\": headers})\n    await send({\"type\": \"http.response.body\", \"body\": output})\n\n\nasync def raise_exc(scope, receive, send):\n    raise RuntimeError()\n\n\nasync def raise_exc_after_response(scope, receive, send):\n    status = 200\n    output = b\"Hello, World!\"\n    headers = [(b\"content-type\", \"text/plain\"), (b\"content-length\", str(len(output)))]\n\n    await send({\"type\": \"http.response.start\", \"status\": status, \"headers\": headers})\n    await send({\"type\": \"http.response.body\", \"body\": output})\n    raise RuntimeError()\n\n\n@pytest.mark.anyio\nasync def test_asgi_transport():\n    async with httpx.ASGITransport(app=hello_world) as transport:\n        request = httpx.Request(\"GET\", \"http://www.example.com/\")\n        response = await transport.handle_async_request(request)\n        await response.aread()\n        assert response.status_code == 200\n        assert response.content == b\"Hello, World!\"\n\n\n@pytest.mark.anyio\nasync def test_asgi_transport_no_body():\n    async with httpx.ASGITransport(app=echo_body) as transport:\n        request = httpx.Request(\"GET\", \"http://www.example.com/\")\n        response = await transport.handle_async_request(request)\n        await response.aread()\n        assert response.status_code == 200\n        assert response.content == b\"\"\n\n\n@pytest.mark.anyio\nasync def test_asgi():\n    transport = httpx.ASGITransport(app=hello_world)\n    async with httpx.AsyncClient(transport=transport) as client:\n        response = await client.get(\"http://www.example.org/\")\n\n    assert response.status_code == 200\n    assert response.text == \"Hello, World!\"\n\n\n@pytest.mark.anyio\nasync def test_asgi_urlencoded_path():\n    transport = httpx.ASGITransport(app=echo_path)\n    async with httpx.AsyncClient(transport=transport) as client:\n        url = httpx.URL(\"http://www.example.org/\").copy_with(path=\"/user@example.org\")\n        response = await client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\"path\": \"/user@example.org\"}\n\n\n@pytest.mark.anyio\nasync def test_asgi_raw_path():\n    transport = httpx.ASGITransport(app=echo_raw_path)\n    async with httpx.AsyncClient(transport=transport) as client:\n        url = httpx.URL(\"http://www.example.org/\").copy_with(path=\"/user@example.org\")\n        response = await client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\"raw_path\": \"/user@example.org\"}\n\n\n@pytest.mark.anyio\nasync def test_asgi_raw_path_should_not_include_querystring_portion():\n    \"\"\"\n    See https://github.com/encode/httpx/issues/2810\n    \"\"\"\n    transport = httpx.ASGITransport(app=echo_raw_path)\n    async with httpx.AsyncClient(transport=transport) as client:\n        url = httpx.URL(\"http://www.example.org/path?query\")\n        response = await client.get(url)\n\n    assert response.status_code == 200\n    assert response.json() == {\"raw_path\": \"/path\"}\n\n\n@pytest.mark.anyio\nasync def test_asgi_upload():\n    transport = httpx.ASGITransport(app=echo_body)\n    async with httpx.AsyncClient(transport=transport) as client:\n        response = await client.post(\"http://www.example.org/\", content=b\"example\")\n\n    assert response.status_code == 200\n    assert response.text == \"example\"\n\n\n@pytest.mark.anyio\nasync def test_asgi_headers():\n    transport = httpx.ASGITransport(app=echo_headers)\n    async with httpx.AsyncClient(transport=transport) as client:\n        response = await client.get(\"http://www.example.org/\")\n\n    assert response.status_code == 200\n    assert response.json() == {\n        \"headers\": [\n            [\"host\", \"www.example.org\"],\n            [\"accept\", \"*/*\"],\n            [\"accept-encoding\", \"gzip, deflate, br, zstd\"],\n            [\"connection\", \"keep-alive\"],\n            [\"user-agent\", f\"python-httpx/{httpx.__version__}\"],\n        ]\n    }\n\n\n@pytest.mark.anyio\nasync def test_asgi_exc():\n    transport = httpx.ASGITransport(app=raise_exc)\n    async with httpx.AsyncClient(transport=transport) as client:\n        with pytest.raises(RuntimeError):\n            await client.get(\"http://www.example.org/\")\n\n\n@pytest.mark.anyio\nasync def test_asgi_exc_after_response():\n    transport = httpx.ASGITransport(app=raise_exc_after_response)\n    async with httpx.AsyncClient(transport=transport) as client:\n        with pytest.raises(RuntimeError):\n            await client.get(\"http://www.example.org/\")\n\n\n@pytest.mark.anyio\nasync def test_asgi_disconnect_after_response_complete():\n    disconnect = False\n\n    async def read_body(scope, receive, send):\n        nonlocal disconnect\n\n        status = 200\n        headers = [(b\"content-type\", \"text/plain\")]\n\n        await send(\n            {\"type\": \"http.response.start\", \"status\": status, \"headers\": headers}\n        )\n        more_body = True\n        while more_body:\n            message = await receive()\n            more_body = message.get(\"more_body\", False)\n\n        await send({\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False})\n\n        # The ASGI spec says of the Disconnect message:\n        # \"Sent to the application when a HTTP connection is closed or if receive is\n        # called after a response has been sent.\"\n        # So if receive() is called again, the disconnect message should be received\n        message = await receive()\n        disconnect = message.get(\"type\") == \"http.disconnect\"\n\n    transport = httpx.ASGITransport(app=read_body)\n    async with httpx.AsyncClient(transport=transport) as client:\n        response = await client.post(\"http://www.example.org/\", content=b\"example\")\n\n    assert response.status_code == 200\n    assert disconnect\n\n\n@pytest.mark.anyio\nasync def test_asgi_exc_no_raise():\n    transport = httpx.ASGITransport(app=raise_exc, raise_app_exceptions=False)\n    async with httpx.AsyncClient(transport=transport) as client:\n        response = await client.get(\"http://www.example.org/\")\n\n        assert response.status_code == 500\n"
  },
  {
    "path": "tests/test_auth.py",
    "content": "\"\"\"\nUnit tests for auth classes.\n\nIntegration tests also exist in tests/client/test_auth.py\n\"\"\"\n\nfrom urllib.request import parse_keqv_list\n\nimport pytest\n\nimport httpx\n\n\ndef test_basic_auth():\n    auth = httpx.BasicAuth(username=\"user\", password=\"pass\")\n    request = httpx.Request(\"GET\", \"https://www.example.com\")\n\n    # The initial request should include a basic auth header.\n    flow = auth.sync_auth_flow(request)\n    request = next(flow)\n    assert request.headers[\"Authorization\"].startswith(\"Basic\")\n\n    # No other requests are made.\n    response = httpx.Response(content=b\"Hello, world!\", status_code=200)\n    with pytest.raises(StopIteration):\n        flow.send(response)\n\n\ndef test_digest_auth_with_200():\n    auth = httpx.DigestAuth(username=\"user\", password=\"pass\")\n    request = httpx.Request(\"GET\", \"https://www.example.com\")\n\n    # The initial request should not include an auth header.\n    flow = auth.sync_auth_flow(request)\n    request = next(flow)\n    assert \"Authorization\" not in request.headers\n\n    # If a 200 response is returned, then no other requests are made.\n    response = httpx.Response(content=b\"Hello, world!\", status_code=200)\n    with pytest.raises(StopIteration):\n        flow.send(response)\n\n\ndef test_digest_auth_with_401():\n    auth = httpx.DigestAuth(username=\"user\", password=\"pass\")\n    request = httpx.Request(\"GET\", \"https://www.example.com\")\n\n    # The initial request should not include an auth header.\n    flow = auth.sync_auth_flow(request)\n    request = next(flow)\n    assert \"Authorization\" not in request.headers\n\n    # If a 401 response is returned, then a digest auth request is made.\n    headers = {\n        \"WWW-Authenticate\": 'Digest realm=\"...\", qop=\"auth\", nonce=\"...\", opaque=\"...\"'\n    }\n    response = httpx.Response(\n        content=b\"Auth required\", status_code=401, headers=headers, request=request\n    )\n    request = flow.send(response)\n    assert request.headers[\"Authorization\"].startswith(\"Digest\")\n\n    # No other requests are made.\n    response = httpx.Response(content=b\"Hello, world!\", status_code=200)\n    with pytest.raises(StopIteration):\n        flow.send(response)\n\n\ndef test_digest_auth_with_401_nonce_counting():\n    auth = httpx.DigestAuth(username=\"user\", password=\"pass\")\n    request = httpx.Request(\"GET\", \"https://www.example.com\")\n\n    # The initial request should not include an auth header.\n    flow = auth.sync_auth_flow(request)\n    request = next(flow)\n    assert \"Authorization\" not in request.headers\n\n    # If a 401 response is returned, then a digest auth request is made.\n    headers = {\n        \"WWW-Authenticate\": 'Digest realm=\"...\", qop=\"auth\", nonce=\"...\", opaque=\"...\"'\n    }\n    response = httpx.Response(\n        content=b\"Auth required\", status_code=401, headers=headers, request=request\n    )\n    first_request = flow.send(response)\n    assert first_request.headers[\"Authorization\"].startswith(\"Digest\")\n\n    # Each subsequent request contains the digest header by default...\n    request = httpx.Request(\"GET\", \"https://www.example.com\")\n    flow = auth.sync_auth_flow(request)\n    second_request = next(flow)\n    assert second_request.headers[\"Authorization\"].startswith(\"Digest\")\n\n    # ... and the client nonce count (nc) is increased\n    first_nc = parse_keqv_list(first_request.headers[\"Authorization\"].split(\", \"))[\"nc\"]\n    second_nc = parse_keqv_list(second_request.headers[\"Authorization\"].split(\", \"))[\n        \"nc\"\n    ]\n    assert int(first_nc, 16) + 1 == int(second_nc, 16)\n\n    # No other requests are made.\n    response = httpx.Response(content=b\"Hello, world!\", status_code=200)\n    with pytest.raises(StopIteration):\n        flow.send(response)\n\n\ndef set_cookies(request: httpx.Request) -> httpx.Response:\n    headers = {\n        \"Set-Cookie\": \"session=.session_value...\",\n        \"WWW-Authenticate\": 'Digest realm=\"...\", qop=\"auth\", nonce=\"...\", opaque=\"...\"',\n    }\n    if request.url.path == \"/auth\":\n        return httpx.Response(\n            content=b\"Auth required\", status_code=401, headers=headers\n        )\n    else:\n        raise NotImplementedError()  # pragma: no cover\n\n\ndef test_digest_auth_setting_cookie_in_request():\n    url = \"https://www.example.com/auth\"\n    client = httpx.Client(transport=httpx.MockTransport(set_cookies))\n    request = client.build_request(\"GET\", url)\n\n    auth = httpx.DigestAuth(username=\"user\", password=\"pass\")\n    flow = auth.sync_auth_flow(request)\n    request = next(flow)\n    assert \"Authorization\" not in request.headers\n\n    response = client.get(url)\n    assert len(response.cookies) > 0\n    assert response.cookies[\"session\"] == \".session_value...\"\n\n    request = flow.send(response)\n    assert request.headers[\"Authorization\"].startswith(\"Digest\")\n    assert request.headers[\"Cookie\"] == \"session=.session_value...\"\n\n    # No other requests are made.\n    response = httpx.Response(\n        content=b\"Hello, world!\", status_code=200, request=request\n    )\n    with pytest.raises(StopIteration):\n        flow.send(response)\n\n\ndef test_digest_auth_rfc_2069():\n    # Example from https://datatracker.ietf.org/doc/html/rfc2069#section-2.4\n    # with corrected response from https://www.rfc-editor.org/errata/eid749\n\n    auth = httpx.DigestAuth(username=\"Mufasa\", password=\"CircleOfLife\")\n    request = httpx.Request(\"GET\", \"https://www.example.com/dir/index.html\")\n\n    # The initial request should not include an auth header.\n    flow = auth.sync_auth_flow(request)\n    request = next(flow)\n    assert \"Authorization\" not in request.headers\n\n    # If a 401 response is returned, then a digest auth request is made.\n    headers = {\n        \"WWW-Authenticate\": (\n            'Digest realm=\"testrealm@host.com\", '\n            'nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", '\n            'opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"'\n        )\n    }\n    response = httpx.Response(\n        content=b\"Auth required\", status_code=401, headers=headers, request=request\n    )\n    request = flow.send(response)\n    assert request.headers[\"Authorization\"].startswith(\"Digest\")\n    assert 'username=\"Mufasa\"' in request.headers[\"Authorization\"]\n    assert 'realm=\"testrealm@host.com\"' in request.headers[\"Authorization\"]\n    assert (\n        'nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"' in request.headers[\"Authorization\"]\n    )\n    assert 'uri=\"/dir/index.html\"' in request.headers[\"Authorization\"]\n    assert (\n        'opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"' in request.headers[\"Authorization\"]\n    )\n    assert (\n        'response=\"1949323746fe6a43ef61f9606e7febea\"'\n        in request.headers[\"Authorization\"]\n    )\n\n    # No other requests are made.\n    response = httpx.Response(content=b\"Hello, world!\", status_code=200)\n    with pytest.raises(StopIteration):\n        flow.send(response)\n\n\ndef test_digest_auth_rfc_7616_md5(monkeypatch):\n    # Example from https://datatracker.ietf.org/doc/html/rfc7616#section-3.9.1\n\n    def mock_get_client_nonce(nonce_count: int, nonce: bytes) -> bytes:\n        return \"f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ\".encode()\n\n    auth = httpx.DigestAuth(username=\"Mufasa\", password=\"Circle of Life\")\n    monkeypatch.setattr(auth, \"_get_client_nonce\", mock_get_client_nonce)\n\n    request = httpx.Request(\"GET\", \"https://www.example.com/dir/index.html\")\n\n    # The initial request should not include an auth header.\n    flow = auth.sync_auth_flow(request)\n    request = next(flow)\n    assert \"Authorization\" not in request.headers\n\n    # If a 401 response is returned, then a digest auth request is made.\n    headers = {\n        \"WWW-Authenticate\": (\n            'Digest realm=\"http-auth@example.org\", '\n            'qop=\"auth, auth-int\", '\n            \"algorithm=MD5, \"\n            'nonce=\"7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v\", '\n            'opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\"'\n        )\n    }\n    response = httpx.Response(\n        content=b\"Auth required\", status_code=401, headers=headers, request=request\n    )\n    request = flow.send(response)\n    assert request.headers[\"Authorization\"].startswith(\"Digest\")\n    assert 'username=\"Mufasa\"' in request.headers[\"Authorization\"]\n    assert 'realm=\"http-auth@example.org\"' in request.headers[\"Authorization\"]\n    assert 'uri=\"/dir/index.html\"' in request.headers[\"Authorization\"]\n    assert \"algorithm=MD5\" in request.headers[\"Authorization\"]\n    assert (\n        'nonce=\"7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v\"'\n        in request.headers[\"Authorization\"]\n    )\n    assert \"nc=00000001\" in request.headers[\"Authorization\"]\n    assert (\n        'cnonce=\"f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ\"'\n        in request.headers[\"Authorization\"]\n    )\n    assert \"qop=auth\" in request.headers[\"Authorization\"]\n    assert (\n        'opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\"'\n        in request.headers[\"Authorization\"]\n    )\n    assert (\n        'response=\"8ca523f5e9506fed4657c9700eebdbec\"'\n        in request.headers[\"Authorization\"]\n    )\n\n    # No other requests are made.\n    response = httpx.Response(content=b\"Hello, world!\", status_code=200)\n    with pytest.raises(StopIteration):\n        flow.send(response)\n\n\ndef test_digest_auth_rfc_7616_sha_256(monkeypatch):\n    # Example from https://datatracker.ietf.org/doc/html/rfc7616#section-3.9.1\n\n    def mock_get_client_nonce(nonce_count: int, nonce: bytes) -> bytes:\n        return \"f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ\".encode()\n\n    auth = httpx.DigestAuth(username=\"Mufasa\", password=\"Circle of Life\")\n    monkeypatch.setattr(auth, \"_get_client_nonce\", mock_get_client_nonce)\n\n    request = httpx.Request(\"GET\", \"https://www.example.com/dir/index.html\")\n\n    # The initial request should not include an auth header.\n    flow = auth.sync_auth_flow(request)\n    request = next(flow)\n    assert \"Authorization\" not in request.headers\n\n    # If a 401 response is returned, then a digest auth request is made.\n    headers = {\n        \"WWW-Authenticate\": (\n            'Digest realm=\"http-auth@example.org\", '\n            'qop=\"auth, auth-int\", '\n            \"algorithm=SHA-256, \"\n            'nonce=\"7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v\", '\n            'opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\"'\n        )\n    }\n    response = httpx.Response(\n        content=b\"Auth required\", status_code=401, headers=headers, request=request\n    )\n    request = flow.send(response)\n    assert request.headers[\"Authorization\"].startswith(\"Digest\")\n    assert 'username=\"Mufasa\"' in request.headers[\"Authorization\"]\n    assert 'realm=\"http-auth@example.org\"' in request.headers[\"Authorization\"]\n    assert 'uri=\"/dir/index.html\"' in request.headers[\"Authorization\"]\n    assert \"algorithm=SHA-256\" in request.headers[\"Authorization\"]\n    assert (\n        'nonce=\"7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v\"'\n        in request.headers[\"Authorization\"]\n    )\n    assert \"nc=00000001\" in request.headers[\"Authorization\"]\n    assert (\n        'cnonce=\"f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ\"'\n        in request.headers[\"Authorization\"]\n    )\n    assert \"qop=auth\" in request.headers[\"Authorization\"]\n    assert (\n        'opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\"'\n        in request.headers[\"Authorization\"]\n    )\n    assert (\n        'response=\"753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1\"'\n        in request.headers[\"Authorization\"]\n    )\n\n    # No other requests are made.\n    response = httpx.Response(content=b\"Hello, world!\", status_code=200)\n    with pytest.raises(StopIteration):\n        flow.send(response)\n"
  },
  {
    "path": "tests/test_config.py",
    "content": "import ssl\nimport typing\nfrom pathlib import Path\n\nimport certifi\nimport pytest\n\nimport httpx\n\n\ndef test_load_ssl_config():\n    context = httpx.create_ssl_context()\n    assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED\n    assert context.check_hostname is True\n\n\ndef test_load_ssl_config_verify_non_existing_file():\n    with pytest.raises(IOError):\n        context = httpx.create_ssl_context()\n        context.load_verify_locations(cafile=\"/path/to/nowhere\")\n\n\ndef test_load_ssl_with_keylog(monkeypatch: typing.Any) -> None:\n    monkeypatch.setenv(\"SSLKEYLOGFILE\", \"test\")\n    context = httpx.create_ssl_context()\n    assert context.keylog_filename == \"test\"\n\n\ndef test_load_ssl_config_verify_existing_file():\n    context = httpx.create_ssl_context()\n    context.load_verify_locations(capath=certifi.where())\n    assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED\n    assert context.check_hostname is True\n\n\ndef test_load_ssl_config_verify_directory():\n    context = httpx.create_ssl_context()\n    context.load_verify_locations(capath=Path(certifi.where()).parent)\n    assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED\n    assert context.check_hostname is True\n\n\ndef test_load_ssl_config_cert_and_key(cert_pem_file, cert_private_key_file):\n    context = httpx.create_ssl_context()\n    context.load_cert_chain(cert_pem_file, cert_private_key_file)\n    assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED\n    assert context.check_hostname is True\n\n\n@pytest.mark.parametrize(\"password\", [b\"password\", \"password\"])\ndef test_load_ssl_config_cert_and_encrypted_key(\n    cert_pem_file, cert_encrypted_private_key_file, password\n):\n    context = httpx.create_ssl_context()\n    context.load_cert_chain(cert_pem_file, cert_encrypted_private_key_file, password)\n    assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED\n    assert context.check_hostname is True\n\n\ndef test_load_ssl_config_cert_and_key_invalid_password(\n    cert_pem_file, cert_encrypted_private_key_file\n):\n    with pytest.raises(ssl.SSLError):\n        context = httpx.create_ssl_context()\n        context.load_cert_chain(\n            cert_pem_file, cert_encrypted_private_key_file, \"password1\"\n        )\n\n\ndef test_load_ssl_config_cert_without_key_raises(cert_pem_file):\n    with pytest.raises(ssl.SSLError):\n        context = httpx.create_ssl_context()\n        context.load_cert_chain(cert_pem_file)\n\n\ndef test_load_ssl_config_no_verify():\n    context = httpx.create_ssl_context(verify=False)\n    assert context.verify_mode == ssl.VerifyMode.CERT_NONE\n    assert context.check_hostname is False\n\n\ndef test_SSLContext_with_get_request(server, cert_pem_file):\n    context = httpx.create_ssl_context()\n    context.load_verify_locations(cert_pem_file)\n    response = httpx.get(server.url, verify=context)\n    assert response.status_code == 200\n\n\ndef test_limits_repr():\n    limits = httpx.Limits(max_connections=100)\n    expected = (\n        \"Limits(max_connections=100, max_keepalive_connections=None,\"\n        \" keepalive_expiry=5.0)\"\n    )\n    assert repr(limits) == expected\n\n\ndef test_limits_eq():\n    limits = httpx.Limits(max_connections=100)\n    assert limits == httpx.Limits(max_connections=100)\n\n\ndef test_timeout_eq():\n    timeout = httpx.Timeout(timeout=5.0)\n    assert timeout == httpx.Timeout(timeout=5.0)\n\n\ndef test_timeout_all_parameters_set():\n    timeout = httpx.Timeout(connect=5.0, read=5.0, write=5.0, pool=5.0)\n    assert timeout == httpx.Timeout(timeout=5.0)\n\n\ndef test_timeout_from_nothing():\n    timeout = httpx.Timeout(None)\n    assert timeout.connect is None\n    assert timeout.read is None\n    assert timeout.write is None\n    assert timeout.pool is None\n\n\ndef test_timeout_from_none():\n    timeout = httpx.Timeout(timeout=None)\n    assert timeout == httpx.Timeout(None)\n\n\ndef test_timeout_from_one_none_value():\n    timeout = httpx.Timeout(None, read=None)\n    assert timeout == httpx.Timeout(None)\n\n\ndef test_timeout_from_one_value():\n    timeout = httpx.Timeout(None, read=5.0)\n    assert timeout == httpx.Timeout(timeout=(None, 5.0, None, None))\n\n\ndef test_timeout_from_one_value_and_default():\n    timeout = httpx.Timeout(5.0, pool=60.0)\n    assert timeout == httpx.Timeout(timeout=(5.0, 5.0, 5.0, 60.0))\n\n\ndef test_timeout_missing_default():\n    with pytest.raises(ValueError):\n        httpx.Timeout(pool=60.0)\n\n\ndef test_timeout_from_tuple():\n    timeout = httpx.Timeout(timeout=(5.0, 5.0, 5.0, 5.0))\n    assert timeout == httpx.Timeout(timeout=5.0)\n\n\ndef test_timeout_from_config_instance():\n    timeout = httpx.Timeout(timeout=5.0)\n    assert httpx.Timeout(timeout) == httpx.Timeout(timeout=5.0)\n\n\ndef test_timeout_repr():\n    timeout = httpx.Timeout(timeout=5.0)\n    assert repr(timeout) == \"Timeout(timeout=5.0)\"\n\n    timeout = httpx.Timeout(None, read=5.0)\n    assert repr(timeout) == \"Timeout(connect=None, read=5.0, write=None, pool=None)\"\n\n\ndef test_proxy_from_url():\n    proxy = httpx.Proxy(\"https://example.com\")\n\n    assert str(proxy.url) == \"https://example.com\"\n    assert proxy.auth is None\n    assert proxy.headers == {}\n    assert repr(proxy) == \"Proxy('https://example.com')\"\n\n\ndef test_proxy_with_auth_from_url():\n    proxy = httpx.Proxy(\"https://username:password@example.com\")\n\n    assert str(proxy.url) == \"https://example.com\"\n    assert proxy.auth == (\"username\", \"password\")\n    assert proxy.headers == {}\n    assert repr(proxy) == \"Proxy('https://example.com', auth=('username', '********'))\"\n\n\ndef test_invalid_proxy_scheme():\n    with pytest.raises(ValueError):\n        httpx.Proxy(\"invalid://example.com\")\n"
  },
  {
    "path": "tests/test_content.py",
    "content": "import io\nimport typing\n\nimport pytest\n\nimport httpx\n\nmethod = \"POST\"\nurl = \"https://www.example.com\"\n\n\n@pytest.mark.anyio\nasync def test_empty_content():\n    request = httpx.Request(method, url)\n    assert isinstance(request.stream, httpx.SyncByteStream)\n    assert isinstance(request.stream, httpx.AsyncByteStream)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\"Host\": \"www.example.com\", \"Content-Length\": \"0\"}\n    assert sync_content == b\"\"\n    assert async_content == b\"\"\n\n\n@pytest.mark.anyio\nasync def test_bytes_content():\n    request = httpx.Request(method, url, content=b\"Hello, world!\")\n    assert isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\"Host\": \"www.example.com\", \"Content-Length\": \"13\"}\n    assert sync_content == b\"Hello, world!\"\n    assert async_content == b\"Hello, world!\"\n\n    # Support 'data' for compat with requests.\n    with pytest.warns(DeprecationWarning):\n        request = httpx.Request(method, url, data=b\"Hello, world!\")  # type: ignore\n    assert isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\"Host\": \"www.example.com\", \"Content-Length\": \"13\"}\n    assert sync_content == b\"Hello, world!\"\n    assert async_content == b\"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_bytesio_content():\n    request = httpx.Request(method, url, content=io.BytesIO(b\"Hello, world!\"))\n    assert isinstance(request.stream, typing.Iterable)\n    assert not isinstance(request.stream, typing.AsyncIterable)\n\n    content = b\"\".join(list(request.stream))\n\n    assert request.headers == {\"Host\": \"www.example.com\", \"Content-Length\": \"13\"}\n    assert content == b\"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_async_bytesio_content():\n    class AsyncBytesIO:\n        def __init__(self, content: bytes) -> None:\n            self._idx = 0\n            self._content = content\n\n        async def aread(self, chunk_size: int) -> bytes:\n            chunk = self._content[self._idx : self._idx + chunk_size]\n            self._idx = self._idx + chunk_size\n            return chunk\n\n        async def __aiter__(self):\n            yield self._content  # pragma: no cover\n\n    request = httpx.Request(method, url, content=AsyncBytesIO(b\"Hello, world!\"))\n    assert not isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Transfer-Encoding\": \"chunked\",\n    }\n    assert content == b\"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_iterator_content():\n    def hello_world() -> typing.Iterator[bytes]:\n        yield b\"Hello, \"\n        yield b\"world!\"\n\n    request = httpx.Request(method, url, content=hello_world())\n    assert isinstance(request.stream, typing.Iterable)\n    assert not isinstance(request.stream, typing.AsyncIterable)\n\n    content = b\"\".join(list(request.stream))\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Transfer-Encoding\": \"chunked\",\n    }\n    assert content == b\"Hello, world!\"\n\n    with pytest.raises(httpx.StreamConsumed):\n        list(request.stream)\n\n    # Support 'data' for compat with requests.\n    with pytest.warns(DeprecationWarning):\n        request = httpx.Request(method, url, data=hello_world())  # type: ignore\n    assert isinstance(request.stream, typing.Iterable)\n    assert not isinstance(request.stream, typing.AsyncIterable)\n\n    content = b\"\".join(list(request.stream))\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Transfer-Encoding\": \"chunked\",\n    }\n    assert content == b\"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_aiterator_content():\n    async def hello_world() -> typing.AsyncIterator[bytes]:\n        yield b\"Hello, \"\n        yield b\"world!\"\n\n    request = httpx.Request(method, url, content=hello_world())\n    assert not isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Transfer-Encoding\": \"chunked\",\n    }\n    assert content == b\"Hello, world!\"\n\n    with pytest.raises(httpx.StreamConsumed):\n        [part async for part in request.stream]\n\n    # Support 'data' for compat with requests.\n    with pytest.warns(DeprecationWarning):\n        request = httpx.Request(method, url, data=hello_world())  # type: ignore\n    assert not isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Transfer-Encoding\": \"chunked\",\n    }\n    assert content == b\"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_json_content():\n    request = httpx.Request(method, url, json={\"Hello\": \"world!\"})\n    assert isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Length\": \"18\",\n        \"Content-Type\": \"application/json\",\n    }\n    assert sync_content == b'{\"Hello\":\"world!\"}'\n    assert async_content == b'{\"Hello\":\"world!\"}'\n\n\n@pytest.mark.anyio\nasync def test_urlencoded_content():\n    request = httpx.Request(method, url, data={\"Hello\": \"world!\"})\n    assert isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Length\": \"14\",\n        \"Content-Type\": \"application/x-www-form-urlencoded\",\n    }\n    assert sync_content == b\"Hello=world%21\"\n    assert async_content == b\"Hello=world%21\"\n\n\n@pytest.mark.anyio\nasync def test_urlencoded_boolean():\n    request = httpx.Request(method, url, data={\"example\": True})\n    assert isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Length\": \"12\",\n        \"Content-Type\": \"application/x-www-form-urlencoded\",\n    }\n    assert sync_content == b\"example=true\"\n    assert async_content == b\"example=true\"\n\n\n@pytest.mark.anyio\nasync def test_urlencoded_none():\n    request = httpx.Request(method, url, data={\"example\": None})\n    assert isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Length\": \"8\",\n        \"Content-Type\": \"application/x-www-form-urlencoded\",\n    }\n    assert sync_content == b\"example=\"\n    assert async_content == b\"example=\"\n\n\n@pytest.mark.anyio\nasync def test_urlencoded_list():\n    request = httpx.Request(method, url, data={\"example\": [\"a\", 1, True]})\n    assert isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Length\": \"32\",\n        \"Content-Type\": \"application/x-www-form-urlencoded\",\n    }\n    assert sync_content == b\"example=a&example=1&example=true\"\n    assert async_content == b\"example=a&example=1&example=true\"\n\n\n@pytest.mark.anyio\nasync def test_multipart_files_content():\n    files = {\"file\": io.BytesIO(b\"<file content>\")}\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=+++\"}\n    request = httpx.Request(\n        method,\n        url,\n        files=files,\n        headers=headers,\n    )\n    assert isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Length\": \"138\",\n        \"Content-Type\": \"multipart/form-data; boundary=+++\",\n    }\n    assert sync_content == b\"\".join(\n        [\n            b\"--+++\\r\\n\",\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"upload\"\\r\\n',\n            b\"Content-Type: application/octet-stream\\r\\n\",\n            b\"\\r\\n\",\n            b\"<file content>\\r\\n\",\n            b\"--+++--\\r\\n\",\n        ]\n    )\n    assert async_content == b\"\".join(\n        [\n            b\"--+++\\r\\n\",\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"upload\"\\r\\n',\n            b\"Content-Type: application/octet-stream\\r\\n\",\n            b\"\\r\\n\",\n            b\"<file content>\\r\\n\",\n            b\"--+++--\\r\\n\",\n        ]\n    )\n\n\n@pytest.mark.anyio\nasync def test_multipart_data_and_files_content():\n    data = {\"message\": \"Hello, world!\"}\n    files = {\"file\": io.BytesIO(b\"<file content>\")}\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=+++\"}\n    request = httpx.Request(method, url, data=data, files=files, headers=headers)\n    assert isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Length\": \"210\",\n        \"Content-Type\": \"multipart/form-data; boundary=+++\",\n    }\n    assert sync_content == b\"\".join(\n        [\n            b\"--+++\\r\\n\",\n            b'Content-Disposition: form-data; name=\"message\"\\r\\n',\n            b\"\\r\\n\",\n            b\"Hello, world!\\r\\n\",\n            b\"--+++\\r\\n\",\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"upload\"\\r\\n',\n            b\"Content-Type: application/octet-stream\\r\\n\",\n            b\"\\r\\n\",\n            b\"<file content>\\r\\n\",\n            b\"--+++--\\r\\n\",\n        ]\n    )\n    assert async_content == b\"\".join(\n        [\n            b\"--+++\\r\\n\",\n            b'Content-Disposition: form-data; name=\"message\"\\r\\n',\n            b\"\\r\\n\",\n            b\"Hello, world!\\r\\n\",\n            b\"--+++\\r\\n\",\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"upload\"\\r\\n',\n            b\"Content-Type: application/octet-stream\\r\\n\",\n            b\"\\r\\n\",\n            b\"<file content>\\r\\n\",\n            b\"--+++--\\r\\n\",\n        ]\n    )\n\n\n@pytest.mark.anyio\nasync def test_empty_request():\n    request = httpx.Request(method, url, data={}, files={})\n    assert isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\"Host\": \"www.example.com\", \"Content-Length\": \"0\"}\n    assert sync_content == b\"\"\n    assert async_content == b\"\"\n\n\ndef test_invalid_argument():\n    with pytest.raises(TypeError):\n        httpx.Request(method, url, content=123)  # type: ignore\n\n    with pytest.raises(TypeError):\n        httpx.Request(method, url, content={\"a\": \"b\"})  # type: ignore\n\n\n@pytest.mark.anyio\nasync def test_multipart_multiple_files_single_input_content():\n    files = [\n        (\"file\", io.BytesIO(b\"<file content 1>\")),\n        (\"file\", io.BytesIO(b\"<file content 2>\")),\n    ]\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=+++\"}\n    request = httpx.Request(method, url, files=files, headers=headers)\n    assert isinstance(request.stream, typing.Iterable)\n    assert isinstance(request.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(request.stream))\n    async_content = b\"\".join([part async for part in request.stream])\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Length\": \"271\",\n        \"Content-Type\": \"multipart/form-data; boundary=+++\",\n    }\n    assert sync_content == b\"\".join(\n        [\n            b\"--+++\\r\\n\",\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"upload\"\\r\\n',\n            b\"Content-Type: application/octet-stream\\r\\n\",\n            b\"\\r\\n\",\n            b\"<file content 1>\\r\\n\",\n            b\"--+++\\r\\n\",\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"upload\"\\r\\n',\n            b\"Content-Type: application/octet-stream\\r\\n\",\n            b\"\\r\\n\",\n            b\"<file content 2>\\r\\n\",\n            b\"--+++--\\r\\n\",\n        ]\n    )\n    assert async_content == b\"\".join(\n        [\n            b\"--+++\\r\\n\",\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"upload\"\\r\\n',\n            b\"Content-Type: application/octet-stream\\r\\n\",\n            b\"\\r\\n\",\n            b\"<file content 1>\\r\\n\",\n            b\"--+++\\r\\n\",\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"upload\"\\r\\n',\n            b\"Content-Type: application/octet-stream\\r\\n\",\n            b\"\\r\\n\",\n            b\"<file content 2>\\r\\n\",\n            b\"--+++--\\r\\n\",\n        ]\n    )\n\n\n@pytest.mark.anyio\nasync def test_response_empty_content():\n    response = httpx.Response(200)\n    assert isinstance(response.stream, typing.Iterable)\n    assert isinstance(response.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(response.stream))\n    async_content = b\"\".join([part async for part in response.stream])\n\n    assert response.headers == {}\n    assert sync_content == b\"\"\n    assert async_content == b\"\"\n\n\n@pytest.mark.anyio\nasync def test_response_bytes_content():\n    response = httpx.Response(200, content=b\"Hello, world!\")\n    assert isinstance(response.stream, typing.Iterable)\n    assert isinstance(response.stream, typing.AsyncIterable)\n\n    sync_content = b\"\".join(list(response.stream))\n    async_content = b\"\".join([part async for part in response.stream])\n\n    assert response.headers == {\"Content-Length\": \"13\"}\n    assert sync_content == b\"Hello, world!\"\n    assert async_content == b\"Hello, world!\"\n\n\n@pytest.mark.anyio\nasync def test_response_iterator_content():\n    def hello_world() -> typing.Iterator[bytes]:\n        yield b\"Hello, \"\n        yield b\"world!\"\n\n    response = httpx.Response(200, content=hello_world())\n    assert isinstance(response.stream, typing.Iterable)\n    assert not isinstance(response.stream, typing.AsyncIterable)\n\n    content = b\"\".join(list(response.stream))\n\n    assert response.headers == {\"Transfer-Encoding\": \"chunked\"}\n    assert content == b\"Hello, world!\"\n\n    with pytest.raises(httpx.StreamConsumed):\n        list(response.stream)\n\n\n@pytest.mark.anyio\nasync def test_response_aiterator_content():\n    async def hello_world() -> typing.AsyncIterator[bytes]:\n        yield b\"Hello, \"\n        yield b\"world!\"\n\n    response = httpx.Response(200, content=hello_world())\n    assert not isinstance(response.stream, typing.Iterable)\n    assert isinstance(response.stream, typing.AsyncIterable)\n\n    content = b\"\".join([part async for part in response.stream])\n\n    assert response.headers == {\"Transfer-Encoding\": \"chunked\"}\n    assert content == b\"Hello, world!\"\n\n    with pytest.raises(httpx.StreamConsumed):\n        [part async for part in response.stream]\n\n\ndef test_response_invalid_argument():\n    with pytest.raises(TypeError):\n        httpx.Response(200, content=123)  # type: ignore\n\n\ndef test_ensure_ascii_false_with_french_characters():\n    data = {\"greeting\": \"Bonjour, ça va ?\"}\n    response = httpx.Response(200, json=data)\n    assert \"ça va\" in response.text, (\n        \"ensure_ascii=False should preserve French accented characters\"\n    )\n    assert response.headers[\"Content-Type\"] == \"application/json\"\n\n\ndef test_separators_for_compact_json():\n    data = {\"clé\": \"valeur\", \"liste\": [1, 2, 3]}\n    response = httpx.Response(200, json=data)\n    assert response.text == '{\"clé\":\"valeur\",\"liste\":[1,2,3]}', (\n        \"separators=(',', ':') should produce a compact representation\"\n    )\n    assert response.headers[\"Content-Type\"] == \"application/json\"\n\n\ndef test_allow_nan_false():\n    data_with_nan = {\"nombre\": float(\"nan\")}\n    data_with_inf = {\"nombre\": float(\"inf\")}\n\n    with pytest.raises(\n        ValueError, match=\"Out of range float values are not JSON compliant\"\n    ):\n        httpx.Response(200, json=data_with_nan)\n    with pytest.raises(\n        ValueError, match=\"Out of range float values are not JSON compliant\"\n    ):\n        httpx.Response(200, json=data_with_inf)\n"
  },
  {
    "path": "tests/test_decoders.py",
    "content": "from __future__ import annotations\n\nimport io\nimport typing\nimport zlib\n\nimport chardet\nimport pytest\nimport zstandard as zstd\n\nimport httpx\n\n\ndef test_deflate():\n    \"\"\"\n    Deflate encoding may use either 'zlib' or 'deflate' in the wild.\n\n    https://stackoverflow.com/questions/1838699/how-can-i-decompress-a-gzip-stream-with-zlib#answer-22311297\n    \"\"\"\n    body = b\"test 123\"\n    compressor = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS)\n    compressed_body = compressor.compress(body) + compressor.flush()\n\n    headers = [(b\"Content-Encoding\", b\"deflate\")]\n    response = httpx.Response(\n        200,\n        headers=headers,\n        content=compressed_body,\n    )\n    assert response.content == body\n\n\ndef test_zlib():\n    \"\"\"\n    Deflate encoding may use either 'zlib' or 'deflate' in the wild.\n\n    https://stackoverflow.com/questions/1838699/how-can-i-decompress-a-gzip-stream-with-zlib#answer-22311297\n    \"\"\"\n    body = b\"test 123\"\n    compressed_body = zlib.compress(body)\n\n    headers = [(b\"Content-Encoding\", b\"deflate\")]\n    response = httpx.Response(\n        200,\n        headers=headers,\n        content=compressed_body,\n    )\n    assert response.content == body\n\n\ndef test_gzip():\n    body = b\"test 123\"\n    compressor = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16)\n    compressed_body = compressor.compress(body) + compressor.flush()\n\n    headers = [(b\"Content-Encoding\", b\"gzip\")]\n    response = httpx.Response(\n        200,\n        headers=headers,\n        content=compressed_body,\n    )\n    assert response.content == body\n\n\ndef test_brotli():\n    body = b\"test 123\"\n    compressed_body = b\"\\x8b\\x03\\x80test 123\\x03\"\n\n    headers = [(b\"Content-Encoding\", b\"br\")]\n    response = httpx.Response(\n        200,\n        headers=headers,\n        content=compressed_body,\n    )\n    assert response.content == body\n\n\ndef test_zstd():\n    body = b\"test 123\"\n    compressed_body = zstd.compress(body)\n\n    headers = [(b\"Content-Encoding\", b\"zstd\")]\n    response = httpx.Response(\n        200,\n        headers=headers,\n        content=compressed_body,\n    )\n    assert response.content == body\n\n\ndef test_zstd_decoding_error():\n    compressed_body = \"this_is_not_zstd_compressed_data\"\n\n    headers = [(b\"Content-Encoding\", b\"zstd\")]\n    with pytest.raises(httpx.DecodingError):\n        httpx.Response(\n            200,\n            headers=headers,\n            content=compressed_body,\n        )\n\n\ndef test_zstd_empty():\n    headers = [(b\"Content-Encoding\", b\"zstd\")]\n    response = httpx.Response(200, headers=headers, content=b\"\")\n    assert response.content == b\"\"\n\n\ndef test_zstd_truncated():\n    body = b\"test 123\"\n    compressed_body = zstd.compress(body)\n\n    headers = [(b\"Content-Encoding\", b\"zstd\")]\n    with pytest.raises(httpx.DecodingError):\n        httpx.Response(\n            200,\n            headers=headers,\n            content=compressed_body[1:3],\n        )\n\n\ndef test_zstd_multiframe():\n    # test inspired by urllib3 test suite\n    data = (\n        # Zstandard frame\n        zstd.compress(b\"foo\")\n        # skippable frame (must be ignored)\n        + bytes.fromhex(\n            \"50 2A 4D 18\"  # Magic_Number (little-endian)\n            \"07 00 00 00\"  # Frame_Size (little-endian)\n            \"00 00 00 00 00 00 00\"  # User_Data\n        )\n        # Zstandard frame\n        + zstd.compress(b\"bar\")\n    )\n    compressed_body = io.BytesIO(data)\n\n    headers = [(b\"Content-Encoding\", b\"zstd\")]\n    response = httpx.Response(200, headers=headers, content=compressed_body)\n    response.read()\n    assert response.content == b\"foobar\"\n\n\ndef test_multi():\n    body = b\"test 123\"\n\n    deflate_compressor = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS)\n    compressed_body = deflate_compressor.compress(body) + deflate_compressor.flush()\n\n    gzip_compressor = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16)\n    compressed_body = (\n        gzip_compressor.compress(compressed_body) + gzip_compressor.flush()\n    )\n\n    headers = [(b\"Content-Encoding\", b\"deflate, gzip\")]\n    response = httpx.Response(\n        200,\n        headers=headers,\n        content=compressed_body,\n    )\n    assert response.content == body\n\n\ndef test_multi_with_identity():\n    body = b\"test 123\"\n    compressed_body = b\"\\x8b\\x03\\x80test 123\\x03\"\n\n    headers = [(b\"Content-Encoding\", b\"br, identity\")]\n    response = httpx.Response(\n        200,\n        headers=headers,\n        content=compressed_body,\n    )\n    assert response.content == body\n\n    headers = [(b\"Content-Encoding\", b\"identity, br\")]\n    response = httpx.Response(\n        200,\n        headers=headers,\n        content=compressed_body,\n    )\n    assert response.content == body\n\n\n@pytest.mark.anyio\nasync def test_streaming():\n    body = b\"test 123\"\n    compressor = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16)\n\n    async def compress(body: bytes) -> typing.AsyncIterator[bytes]:\n        yield compressor.compress(body)\n        yield compressor.flush()\n\n    headers = [(b\"Content-Encoding\", b\"gzip\")]\n    response = httpx.Response(\n        200,\n        headers=headers,\n        content=compress(body),\n    )\n    assert not hasattr(response, \"body\")\n    assert await response.aread() == body\n\n\n@pytest.mark.parametrize(\"header_value\", (b\"deflate\", b\"gzip\", b\"br\", b\"identity\"))\ndef test_empty_content(header_value):\n    headers = [(b\"Content-Encoding\", header_value)]\n    response = httpx.Response(\n        200,\n        headers=headers,\n        content=b\"\",\n    )\n    assert response.content == b\"\"\n\n\n@pytest.mark.parametrize(\"header_value\", (b\"deflate\", b\"gzip\", b\"br\", b\"identity\"))\ndef test_decoders_empty_cases(header_value):\n    headers = [(b\"Content-Encoding\", header_value)]\n    response = httpx.Response(content=b\"\", status_code=200, headers=headers)\n    assert response.read() == b\"\"\n\n\n@pytest.mark.parametrize(\"header_value\", (b\"deflate\", b\"gzip\", b\"br\"))\ndef test_decoding_errors(header_value):\n    headers = [(b\"Content-Encoding\", header_value)]\n    compressed_body = b\"invalid\"\n    with pytest.raises(httpx.DecodingError):\n        request = httpx.Request(\"GET\", \"https://example.org\")\n        httpx.Response(200, headers=headers, content=compressed_body, request=request)\n\n    with pytest.raises(httpx.DecodingError):\n        httpx.Response(200, headers=headers, content=compressed_body)\n\n\n@pytest.mark.parametrize(\n    [\"data\", \"encoding\"],\n    [\n        ((b\"Hello,\", b\" world!\"), \"ascii\"),\n        ((b\"\\xe3\\x83\", b\"\\x88\\xe3\\x83\\xa9\", b\"\\xe3\", b\"\\x83\\x99\\xe3\\x83\\xab\"), \"utf-8\"),\n        ((b\"Euro character: \\x88! abcdefghijklmnopqrstuvwxyz\", b\"\"), \"cp1252\"),\n        ((b\"Accented: \\xd6sterreich abcdefghijklmnopqrstuvwxyz\", b\"\"), \"iso-8859-1\"),\n    ],\n)\n@pytest.mark.anyio\nasync def test_text_decoder_with_autodetect(data, encoding):\n    async def iterator() -> typing.AsyncIterator[bytes]:\n        nonlocal data\n        for chunk in data:\n            yield chunk\n\n    def autodetect(content):\n        return chardet.detect(content).get(\"encoding\")\n\n    # Accessing `.text` on a read response.\n    response = httpx.Response(200, content=iterator(), default_encoding=autodetect)\n    await response.aread()\n    assert response.text == (b\"\".join(data)).decode(encoding)\n\n    # Streaming `.aiter_text` iteratively.\n    # Note that if we streamed the text *without* having read it first, then\n    # we won't get a `charset_normalizer` guess, and will instead always rely\n    # on utf-8 if no charset is specified.\n    text = \"\".join([part async for part in response.aiter_text()])\n    assert text == (b\"\".join(data)).decode(encoding)\n\n\n@pytest.mark.anyio\nasync def test_text_decoder_known_encoding():\n    async def iterator() -> typing.AsyncIterator[bytes]:\n        yield b\"\\x83g\"\n        yield b\"\\x83\"\n        yield b\"\\x89\\x83x\\x83\\x8b\"\n\n    response = httpx.Response(\n        200,\n        headers=[(b\"Content-Type\", b\"text/html; charset=shift-jis\")],\n        content=iterator(),\n    )\n\n    await response.aread()\n    assert \"\".join(response.text) == \"トラベル\"\n\n\ndef test_text_decoder_empty_cases():\n    response = httpx.Response(200, content=b\"\")\n    assert response.text == \"\"\n\n    response = httpx.Response(200, content=[b\"\"])\n    response.read()\n    assert response.text == \"\"\n\n\n@pytest.mark.parametrize(\n    [\"data\", \"expected\"],\n    [((b\"Hello,\", b\" world!\"), [\"Hello,\", \" world!\"])],\n)\ndef test_streaming_text_decoder(\n    data: typing.Iterable[bytes], expected: list[str]\n) -> None:\n    response = httpx.Response(200, content=iter(data))\n    assert list(response.iter_text()) == expected\n\n\ndef test_line_decoder_nl():\n    response = httpx.Response(200, content=[b\"\"])\n    assert list(response.iter_lines()) == []\n\n    response = httpx.Response(200, content=[b\"\", b\"a\\n\\nb\\nc\"])\n    assert list(response.iter_lines()) == [\"a\", \"\", \"b\", \"c\"]\n\n    # Issue #1033\n    response = httpx.Response(\n        200, content=[b\"\", b\"12345\\n\", b\"foo \", b\"bar \", b\"baz\\n\"]\n    )\n    assert list(response.iter_lines()) == [\"12345\", \"foo bar baz\"]\n\n\ndef test_line_decoder_cr():\n    response = httpx.Response(200, content=[b\"\", b\"a\\r\\rb\\rc\"])\n    assert list(response.iter_lines()) == [\"a\", \"\", \"b\", \"c\"]\n\n    response = httpx.Response(200, content=[b\"\", b\"a\\r\\rb\\rc\\r\"])\n    assert list(response.iter_lines()) == [\"a\", \"\", \"b\", \"c\"]\n\n    # Issue #1033\n    response = httpx.Response(\n        200, content=[b\"\", b\"12345\\r\", b\"foo \", b\"bar \", b\"baz\\r\"]\n    )\n    assert list(response.iter_lines()) == [\"12345\", \"foo bar baz\"]\n\n\ndef test_line_decoder_crnl():\n    response = httpx.Response(200, content=[b\"\", b\"a\\r\\n\\r\\nb\\r\\nc\"])\n    assert list(response.iter_lines()) == [\"a\", \"\", \"b\", \"c\"]\n\n    response = httpx.Response(200, content=[b\"\", b\"a\\r\\n\\r\\nb\\r\\nc\\r\\n\"])\n    assert list(response.iter_lines()) == [\"a\", \"\", \"b\", \"c\"]\n\n    response = httpx.Response(200, content=[b\"\", b\"a\\r\", b\"\\n\\r\\nb\\r\\nc\"])\n    assert list(response.iter_lines()) == [\"a\", \"\", \"b\", \"c\"]\n\n    # Issue #1033\n    response = httpx.Response(200, content=[b\"\", b\"12345\\r\\n\", b\"foo bar baz\\r\\n\"])\n    assert list(response.iter_lines()) == [\"12345\", \"foo bar baz\"]\n\n\ndef test_invalid_content_encoding_header():\n    headers = [(b\"Content-Encoding\", b\"invalid-header\")]\n    body = b\"test 123\"\n\n    response = httpx.Response(\n        200,\n        headers=headers,\n        content=body,\n    )\n    assert response.content == body\n"
  },
  {
    "path": "tests/test_exceptions.py",
    "content": "from __future__ import annotations\n\nimport typing\n\nimport httpcore\nimport pytest\n\nimport httpx\n\nif typing.TYPE_CHECKING:  # pragma: no cover\n    from conftest import TestServer\n\n\ndef test_httpcore_all_exceptions_mapped() -> None:\n    \"\"\"\n    All exception classes exposed by HTTPCore are properly mapped to an HTTPX-specific\n    exception class.\n    \"\"\"\n    expected_mapped_httpcore_exceptions = {\n        value.__name__\n        for _, value in vars(httpcore).items()\n        if isinstance(value, type)\n        and issubclass(value, Exception)\n        and value is not httpcore.ConnectionNotAvailable\n    }\n\n    httpx_exceptions = {\n        value.__name__\n        for _, value in vars(httpx).items()\n        if isinstance(value, type) and issubclass(value, Exception)\n    }\n\n    unmapped_exceptions = expected_mapped_httpcore_exceptions - httpx_exceptions\n\n    if unmapped_exceptions:  # pragma: no cover\n        pytest.fail(f\"Unmapped httpcore exceptions: {unmapped_exceptions}\")\n\n\ndef test_httpcore_exception_mapping(server: TestServer) -> None:\n    \"\"\"\n    HTTPCore exception mapping works as expected.\n    \"\"\"\n    impossible_port = 123456\n    with pytest.raises(httpx.ConnectError):\n        httpx.get(server.url.copy_with(port=impossible_port))\n\n    with pytest.raises(httpx.ReadTimeout):\n        httpx.get(\n            server.url.copy_with(path=\"/slow_response\"),\n            timeout=httpx.Timeout(5, read=0.01),\n        )\n\n\ndef test_request_attribute() -> None:\n    # Exception without request attribute\n    exc = httpx.ReadTimeout(\"Read operation timed out\")\n    with pytest.raises(RuntimeError):\n        exc.request  # noqa: B018\n\n    # Exception with request attribute\n    request = httpx.Request(\"GET\", \"https://www.example.com\")\n    exc = httpx.ReadTimeout(\"Read operation timed out\", request=request)\n    assert exc.request == request\n"
  },
  {
    "path": "tests/test_exported_members.py",
    "content": "import httpx\n\n\ndef test_all_imports_are_exported() -> None:\n    included_private_members = [\"__description__\", \"__title__\", \"__version__\"]\n    assert httpx.__all__ == sorted(\n        (\n            member\n            for member in vars(httpx).keys()\n            if not member.startswith(\"_\") or member in included_private_members\n        ),\n        key=str.casefold,\n    )\n"
  },
  {
    "path": "tests/test_main.py",
    "content": "import os\nimport typing\n\nfrom click.testing import CliRunner\n\nimport httpx\n\n\ndef splitlines(output: str) -> typing.Iterable[str]:\n    return [line.strip() for line in output.splitlines()]\n\n\ndef remove_date_header(lines: typing.Iterable[str]) -> typing.Iterable[str]:\n    return [line for line in lines if not line.startswith(\"date:\")]\n\n\ndef test_help():\n    runner = CliRunner()\n    result = runner.invoke(httpx.main, [\"--help\"])\n    assert result.exit_code == 0\n    assert \"A next generation HTTP client.\" in result.output\n\n\ndef test_get(server):\n    url = str(server.url)\n    runner = CliRunner()\n    result = runner.invoke(httpx.main, [url])\n    assert result.exit_code == 0\n    assert remove_date_header(splitlines(result.output)) == [\n        \"HTTP/1.1 200 OK\",\n        \"server: uvicorn\",\n        \"content-type: text/plain\",\n        \"Transfer-Encoding: chunked\",\n        \"\",\n        \"Hello, world!\",\n    ]\n\n\ndef test_json(server):\n    url = str(server.url.copy_with(path=\"/json\"))\n    runner = CliRunner()\n    result = runner.invoke(httpx.main, [url])\n    assert result.exit_code == 0\n    assert remove_date_header(splitlines(result.output)) == [\n        \"HTTP/1.1 200 OK\",\n        \"server: uvicorn\",\n        \"content-type: application/json\",\n        \"Transfer-Encoding: chunked\",\n        \"\",\n        \"{\",\n        '\"Hello\": \"world!\"',\n        \"}\",\n    ]\n\n\ndef test_binary(server):\n    url = str(server.url.copy_with(path=\"/echo_binary\"))\n    runner = CliRunner()\n    content = \"Hello, world!\"\n    result = runner.invoke(httpx.main, [url, \"-c\", content])\n    assert result.exit_code == 0\n    assert remove_date_header(splitlines(result.output)) == [\n        \"HTTP/1.1 200 OK\",\n        \"server: uvicorn\",\n        \"content-type: application/octet-stream\",\n        \"Transfer-Encoding: chunked\",\n        \"\",\n        f\"<{len(content)} bytes of binary data>\",\n    ]\n\n\ndef test_redirects(server):\n    url = str(server.url.copy_with(path=\"/redirect_301\"))\n    runner = CliRunner()\n    result = runner.invoke(httpx.main, [url])\n    assert result.exit_code == 1\n    assert remove_date_header(splitlines(result.output)) == [\n        \"HTTP/1.1 301 Moved Permanently\",\n        \"server: uvicorn\",\n        \"location: /\",\n        \"Transfer-Encoding: chunked\",\n        \"\",\n    ]\n\n\ndef test_follow_redirects(server):\n    url = str(server.url.copy_with(path=\"/redirect_301\"))\n    runner = CliRunner()\n    result = runner.invoke(httpx.main, [url, \"--follow-redirects\"])\n    assert result.exit_code == 0\n    assert remove_date_header(splitlines(result.output)) == [\n        \"HTTP/1.1 301 Moved Permanently\",\n        \"server: uvicorn\",\n        \"location: /\",\n        \"Transfer-Encoding: chunked\",\n        \"\",\n        \"HTTP/1.1 200 OK\",\n        \"server: uvicorn\",\n        \"content-type: text/plain\",\n        \"Transfer-Encoding: chunked\",\n        \"\",\n        \"Hello, world!\",\n    ]\n\n\ndef test_post(server):\n    url = str(server.url.copy_with(path=\"/echo_body\"))\n    runner = CliRunner()\n    result = runner.invoke(httpx.main, [url, \"-m\", \"POST\", \"-j\", '{\"hello\": \"world\"}'])\n    assert result.exit_code == 0\n    assert remove_date_header(splitlines(result.output)) == [\n        \"HTTP/1.1 200 OK\",\n        \"server: uvicorn\",\n        \"content-type: text/plain\",\n        \"Transfer-Encoding: chunked\",\n        \"\",\n        '{\"hello\":\"world\"}',\n    ]\n\n\ndef test_verbose(server):\n    url = str(server.url)\n    runner = CliRunner()\n    result = runner.invoke(httpx.main, [url, \"-v\"])\n    assert result.exit_code == 0\n    assert remove_date_header(splitlines(result.output)) == [\n        \"* Connecting to '127.0.0.1'\",\n        \"* Connected to '127.0.0.1' on port 8000\",\n        \"GET / HTTP/1.1\",\n        f\"Host: {server.url.netloc.decode('ascii')}\",\n        \"Accept: */*\",\n        \"Accept-Encoding: gzip, deflate, br, zstd\",\n        \"Connection: keep-alive\",\n        f\"User-Agent: python-httpx/{httpx.__version__}\",\n        \"\",\n        \"HTTP/1.1 200 OK\",\n        \"server: uvicorn\",\n        \"content-type: text/plain\",\n        \"Transfer-Encoding: chunked\",\n        \"\",\n        \"Hello, world!\",\n    ]\n\n\ndef test_auth(server):\n    url = str(server.url)\n    runner = CliRunner()\n    result = runner.invoke(httpx.main, [url, \"-v\", \"--auth\", \"username\", \"password\"])\n    print(result.output)\n    assert result.exit_code == 0\n    assert remove_date_header(splitlines(result.output)) == [\n        \"* Connecting to '127.0.0.1'\",\n        \"* Connected to '127.0.0.1' on port 8000\",\n        \"GET / HTTP/1.1\",\n        f\"Host: {server.url.netloc.decode('ascii')}\",\n        \"Accept: */*\",\n        \"Accept-Encoding: gzip, deflate, br, zstd\",\n        \"Connection: keep-alive\",\n        f\"User-Agent: python-httpx/{httpx.__version__}\",\n        \"Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=\",\n        \"\",\n        \"HTTP/1.1 200 OK\",\n        \"server: uvicorn\",\n        \"content-type: text/plain\",\n        \"Transfer-Encoding: chunked\",\n        \"\",\n        \"Hello, world!\",\n    ]\n\n\ndef test_download(server):\n    url = str(server.url)\n    runner = CliRunner()\n    with runner.isolated_filesystem():\n        runner.invoke(httpx.main, [url, \"--download\", \"index.txt\"])\n        assert os.path.exists(\"index.txt\")\n        with open(\"index.txt\", \"r\") as input_file:\n            assert input_file.read() == \"Hello, world!\"\n\n\ndef test_errors():\n    runner = CliRunner()\n    result = runner.invoke(httpx.main, [\"invalid://example.org\"])\n    assert result.exit_code == 1\n    assert splitlines(result.output) == [\n        \"UnsupportedProtocol: Request URL has an unsupported protocol 'invalid://'.\",\n    ]\n"
  },
  {
    "path": "tests/test_multipart.py",
    "content": "from __future__ import annotations\n\nimport io\nimport tempfile\nimport typing\n\nimport pytest\n\nimport httpx\n\n\ndef echo_request_content(request: httpx.Request) -> httpx.Response:\n    return httpx.Response(200, content=request.content)\n\n\n@pytest.mark.parametrize((\"value,output\"), ((\"abc\", b\"abc\"), (b\"abc\", b\"abc\")))\ndef test_multipart(value, output):\n    client = httpx.Client(transport=httpx.MockTransport(echo_request_content))\n\n    # Test with a single-value 'data' argument, and a plain file 'files' argument.\n    data = {\"text\": value}\n    files = {\"file\": io.BytesIO(b\"<file content>\")}\n    response = client.post(\"http://127.0.0.1:8000/\", data=data, files=files)\n    boundary = response.request.headers[\"Content-Type\"].split(\"boundary=\")[-1]\n    boundary_bytes = boundary.encode(\"ascii\")\n\n    assert response.status_code == 200\n    assert response.content == b\"\".join(\n        [\n            b\"--\" + boundary_bytes + b\"\\r\\n\",\n            b'Content-Disposition: form-data; name=\"text\"\\r\\n',\n            b\"\\r\\n\",\n            b\"abc\\r\\n\",\n            b\"--\" + boundary_bytes + b\"\\r\\n\",\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"upload\"\\r\\n',\n            b\"Content-Type: application/octet-stream\\r\\n\",\n            b\"\\r\\n\",\n            b\"<file content>\\r\\n\",\n            b\"--\" + boundary_bytes + b\"--\\r\\n\",\n        ]\n    )\n\n\n@pytest.mark.parametrize(\n    \"header\",\n    [\n        \"multipart/form-data; boundary=+++; charset=utf-8\",\n        \"multipart/form-data; charset=utf-8; boundary=+++\",\n        \"multipart/form-data; boundary=+++\",\n        \"multipart/form-data; boundary=+++ ;\",\n        'multipart/form-data; boundary=\"+++\"; charset=utf-8',\n        'multipart/form-data; charset=utf-8; boundary=\"+++\"',\n        'multipart/form-data; boundary=\"+++\"',\n        'multipart/form-data; boundary=\"+++\" ;',\n    ],\n)\ndef test_multipart_explicit_boundary(header: str) -> None:\n    client = httpx.Client(transport=httpx.MockTransport(echo_request_content))\n\n    files = {\"file\": io.BytesIO(b\"<file content>\")}\n    headers = {\"content-type\": header}\n    response = client.post(\"http://127.0.0.1:8000/\", files=files, headers=headers)\n    boundary_bytes = b\"+++\"\n\n    assert response.status_code == 200\n    assert response.request.headers[\"Content-Type\"] == header\n    assert response.content == b\"\".join(\n        [\n            b\"--\" + boundary_bytes + b\"\\r\\n\",\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"upload\"\\r\\n',\n            b\"Content-Type: application/octet-stream\\r\\n\",\n            b\"\\r\\n\",\n            b\"<file content>\\r\\n\",\n            b\"--\" + boundary_bytes + b\"--\\r\\n\",\n        ]\n    )\n\n\n@pytest.mark.parametrize(\n    \"header\",\n    [\n        \"multipart/form-data; charset=utf-8\",\n        \"multipart/form-data; charset=utf-8; \",\n    ],\n)\ndef test_multipart_header_without_boundary(header: str) -> None:\n    client = httpx.Client(transport=httpx.MockTransport(echo_request_content))\n\n    files = {\"file\": io.BytesIO(b\"<file content>\")}\n    headers = {\"content-type\": header}\n    response = client.post(\"http://127.0.0.1:8000/\", files=files, headers=headers)\n\n    assert response.status_code == 200\n    assert response.request.headers[\"Content-Type\"] == header\n\n\n@pytest.mark.parametrize((\"key\"), (b\"abc\", 1, 2.3, None))\ndef test_multipart_invalid_key(key):\n    client = httpx.Client(transport=httpx.MockTransport(echo_request_content))\n\n    data = {key: \"abc\"}\n    files = {\"file\": io.BytesIO(b\"<file content>\")}\n    with pytest.raises(TypeError) as e:\n        client.post(\n            \"http://127.0.0.1:8000/\",\n            data=data,\n            files=files,\n        )\n    assert \"Invalid type for name\" in str(e.value)\n    assert repr(key) in str(e.value)\n\n\n@pytest.mark.parametrize((\"value\"), (object(), {\"key\": \"value\"}))\ndef test_multipart_invalid_value(value):\n    client = httpx.Client(transport=httpx.MockTransport(echo_request_content))\n\n    data = {\"text\": value}\n    files = {\"file\": io.BytesIO(b\"<file content>\")}\n    with pytest.raises(TypeError) as e:\n        client.post(\"http://127.0.0.1:8000/\", data=data, files=files)\n    assert \"Invalid type for value\" in str(e.value)\n\n\ndef test_multipart_file_tuple():\n    client = httpx.Client(transport=httpx.MockTransport(echo_request_content))\n\n    # Test with a list of values 'data' argument,\n    #     and a tuple style 'files' argument.\n    data = {\"text\": [\"abc\"]}\n    files = {\"file\": (\"name.txt\", io.BytesIO(b\"<file content>\"))}\n    response = client.post(\"http://127.0.0.1:8000/\", data=data, files=files)\n    boundary = response.request.headers[\"Content-Type\"].split(\"boundary=\")[-1]\n    boundary_bytes = boundary.encode(\"ascii\")\n\n    assert response.status_code == 200\n    assert response.content == b\"\".join(\n        [\n            b\"--\" + boundary_bytes + b\"\\r\\n\",\n            b'Content-Disposition: form-data; name=\"text\"\\r\\n',\n            b\"\\r\\n\",\n            b\"abc\\r\\n\",\n            b\"--\" + boundary_bytes + b\"\\r\\n\",\n            b'Content-Disposition: form-data; name=\"file\"; filename=\"name.txt\"\\r\\n',\n            b\"Content-Type: text/plain\\r\\n\",\n            b\"\\r\\n\",\n            b\"<file content>\\r\\n\",\n            b\"--\" + boundary_bytes + b\"--\\r\\n\",\n        ]\n    )\n\n\n@pytest.mark.parametrize(\"file_content_type\", [None, \"text/plain\"])\ndef test_multipart_file_tuple_headers(file_content_type: str | None) -> None:\n    file_name = \"test.txt\"\n    file_content = io.BytesIO(b\"<file content>\")\n    file_headers = {\"Expires\": \"0\"}\n\n    url = \"https://www.example.com/\"\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\"}\n    files = {\"file\": (file_name, file_content, file_content_type, file_headers)}\n\n    request = httpx.Request(\"POST\", url, headers=headers, files=files)\n    request.read()\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\",\n        \"Content-Length\": str(len(request.content)),\n    }\n    assert request.content == (\n        f'--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"file\"; '\n        f'filename=\"{file_name}\"\\r\\nExpires: 0\\r\\nContent-Type: '\n        f\"text/plain\\r\\n\\r\\n<file content>\\r\\n--BOUNDARY--\\r\\n\"\n        \"\".encode(\"ascii\")\n    )\n\n\ndef test_multipart_headers_include_content_type() -> None:\n    \"\"\"\n    Content-Type from 4th tuple parameter (headers) should\n    override the 3rd parameter (content_type)\n    \"\"\"\n    file_name = \"test.txt\"\n    file_content = io.BytesIO(b\"<file content>\")\n    file_content_type = \"text/plain\"\n    file_headers = {\"Content-Type\": \"image/png\"}\n\n    url = \"https://www.example.com/\"\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\"}\n    files = {\"file\": (file_name, file_content, file_content_type, file_headers)}\n\n    request = httpx.Request(\"POST\", url, headers=headers, files=files)\n    request.read()\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\",\n        \"Content-Length\": str(len(request.content)),\n    }\n    assert request.content == (\n        f'--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"file\"; '\n        f'filename=\"{file_name}\"\\r\\nContent-Type: '\n        f\"image/png\\r\\n\\r\\n<file content>\\r\\n--BOUNDARY--\\r\\n\"\n        \"\".encode(\"ascii\")\n    )\n\n\ndef test_multipart_encode(tmp_path: typing.Any) -> None:\n    path = str(tmp_path / \"name.txt\")\n    with open(path, \"wb\") as f:\n        f.write(b\"<file content>\")\n\n    url = \"https://www.example.com/\"\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\"}\n    data = {\n        \"a\": \"1\",\n        \"b\": b\"C\",\n        \"c\": [\"11\", \"22\", \"33\"],\n        \"d\": \"\",\n        \"e\": True,\n        \"f\": \"\",\n    }\n    with open(path, \"rb\") as input_file:\n        files = {\"file\": (\"name.txt\", input_file)}\n\n        request = httpx.Request(\"POST\", url, headers=headers, data=data, files=files)\n        request.read()\n\n        assert request.headers == {\n            \"Host\": \"www.example.com\",\n            \"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\",\n            \"Content-Length\": str(len(request.content)),\n        }\n        assert request.content == (\n            '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"a\"\\r\\n\\r\\n1\\r\\n'\n            '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"b\"\\r\\n\\r\\nC\\r\\n'\n            '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"c\"\\r\\n\\r\\n11\\r\\n'\n            '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"c\"\\r\\n\\r\\n22\\r\\n'\n            '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"c\"\\r\\n\\r\\n33\\r\\n'\n            '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"d\"\\r\\n\\r\\n\\r\\n'\n            '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"e\"\\r\\n\\r\\ntrue\\r\\n'\n            '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"f\"\\r\\n\\r\\n\\r\\n'\n            '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"file\";'\n            ' filename=\"name.txt\"\\r\\n'\n            \"Content-Type: text/plain\\r\\n\\r\\n<file content>\\r\\n\"\n            \"--BOUNDARY--\\r\\n\"\n            \"\".encode(\"ascii\")\n        )\n\n\ndef test_multipart_encode_unicode_file_contents() -> None:\n    url = \"https://www.example.com/\"\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\"}\n    files = {\"file\": (\"name.txt\", b\"<bytes content>\")}\n\n    request = httpx.Request(\"POST\", url, headers=headers, files=files)\n    request.read()\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\",\n        \"Content-Length\": str(len(request.content)),\n    }\n    assert request.content == (\n        b'--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"file\";'\n        b' filename=\"name.txt\"\\r\\n'\n        b\"Content-Type: text/plain\\r\\n\\r\\n<bytes content>\\r\\n\"\n        b\"--BOUNDARY--\\r\\n\"\n    )\n\n\ndef test_multipart_encode_files_allows_filenames_as_none() -> None:\n    url = \"https://www.example.com/\"\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\"}\n    files = {\"file\": (None, io.BytesIO(b\"<file content>\"))}\n\n    request = httpx.Request(\"POST\", url, headers=headers, data={}, files=files)\n    request.read()\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\",\n        \"Content-Length\": str(len(request.content)),\n    }\n    assert request.content == (\n        '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"file\"\\r\\n\\r\\n'\n        \"<file content>\\r\\n--BOUNDARY--\\r\\n\"\n        \"\".encode(\"ascii\")\n    )\n\n\n@pytest.mark.parametrize(\n    \"file_name,expected_content_type\",\n    [\n        (\"example.json\", \"application/json\"),\n        (\"example.txt\", \"text/plain\"),\n        (\"no-extension\", \"application/octet-stream\"),\n    ],\n)\ndef test_multipart_encode_files_guesses_correct_content_type(\n    file_name: str, expected_content_type: str\n) -> None:\n    url = \"https://www.example.com/\"\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\"}\n    files = {\"file\": (file_name, io.BytesIO(b\"<file content>\"))}\n\n    request = httpx.Request(\"POST\", url, headers=headers, data={}, files=files)\n    request.read()\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\",\n        \"Content-Length\": str(len(request.content)),\n    }\n    assert request.content == (\n        f'--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"file\"; '\n        f'filename=\"{file_name}\"\\r\\nContent-Type: '\n        f\"{expected_content_type}\\r\\n\\r\\n<file content>\\r\\n--BOUNDARY--\\r\\n\"\n        \"\".encode(\"ascii\")\n    )\n\n\ndef test_multipart_encode_files_allows_bytes_content() -> None:\n    url = \"https://www.example.com/\"\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\"}\n    files = {\"file\": (\"test.txt\", b\"<bytes content>\", \"text/plain\")}\n\n    request = httpx.Request(\"POST\", url, headers=headers, data={}, files=files)\n    request.read()\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\",\n        \"Content-Length\": str(len(request.content)),\n    }\n    assert request.content == (\n        '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"file\"; '\n        'filename=\"test.txt\"\\r\\n'\n        \"Content-Type: text/plain\\r\\n\\r\\n<bytes content>\\r\\n\"\n        \"--BOUNDARY--\\r\\n\"\n        \"\".encode(\"ascii\")\n    )\n\n\ndef test_multipart_encode_files_allows_str_content() -> None:\n    url = \"https://www.example.com/\"\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\"}\n    files = {\"file\": (\"test.txt\", \"<str content>\", \"text/plain\")}\n\n    request = httpx.Request(\"POST\", url, headers=headers, data={}, files=files)\n    request.read()\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\",\n        \"Content-Length\": str(len(request.content)),\n    }\n    assert request.content == (\n        '--BOUNDARY\\r\\nContent-Disposition: form-data; name=\"file\"; '\n        'filename=\"test.txt\"\\r\\n'\n        \"Content-Type: text/plain\\r\\n\\r\\n<str content>\\r\\n\"\n        \"--BOUNDARY--\\r\\n\"\n        \"\".encode(\"ascii\")\n    )\n\n\ndef test_multipart_encode_files_raises_exception_with_StringIO_content() -> None:\n    url = \"https://www.example.com\"\n    files = {\"file\": (\"test.txt\", io.StringIO(\"content\"), \"text/plain\")}\n    with pytest.raises(TypeError):\n        httpx.Request(\"POST\", url, data={}, files=files)  # type: ignore\n\n\ndef test_multipart_encode_files_raises_exception_with_text_mode_file() -> None:\n    url = \"https://www.example.com\"\n    with tempfile.TemporaryFile(mode=\"w\") as upload:\n        files = {\"file\": (\"test.txt\", upload, \"text/plain\")}\n        with pytest.raises(TypeError):\n            httpx.Request(\"POST\", url, data={}, files=files)  # type: ignore\n\n\ndef test_multipart_encode_non_seekable_filelike() -> None:\n    \"\"\"\n    Test that special readable but non-seekable filelike objects are supported.\n    In this case uploads with use 'Transfer-Encoding: chunked', instead of\n    a 'Content-Length' header.\n    \"\"\"\n\n    class IteratorIO(io.IOBase):\n        def __init__(self, iterator: typing.Iterator[bytes]) -> None:\n            self._iterator = iterator\n\n        def read(self, *args: typing.Any) -> bytes:\n            return b\"\".join(self._iterator)\n\n    def data() -> typing.Iterator[bytes]:\n        yield b\"Hello\"\n        yield b\"World\"\n\n    url = \"https://www.example.com/\"\n    headers = {\"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\"}\n    fileobj: typing.Any = IteratorIO(data())\n    files = {\"file\": fileobj}\n\n    request = httpx.Request(\"POST\", url, headers=headers, files=files)\n    request.read()\n\n    assert request.headers == {\n        \"Host\": \"www.example.com\",\n        \"Content-Type\": \"multipart/form-data; boundary=BOUNDARY\",\n        \"Transfer-Encoding\": \"chunked\",\n    }\n    assert request.content == (\n        b\"--BOUNDARY\\r\\n\"\n        b'Content-Disposition: form-data; name=\"file\"; filename=\"upload\"\\r\\n'\n        b\"Content-Type: application/octet-stream\\r\\n\"\n        b\"\\r\\n\"\n        b\"HelloWorld\\r\\n\"\n        b\"--BOUNDARY--\\r\\n\"\n    )\n\n\ndef test_multipart_rewinds_files():\n    with tempfile.TemporaryFile() as upload:\n        upload.write(b\"Hello, world!\")\n\n        transport = httpx.MockTransport(echo_request_content)\n        client = httpx.Client(transport=transport)\n\n        files = {\"file\": upload}\n        response = client.post(\"http://127.0.0.1:8000/\", files=files)\n        assert response.status_code == 200\n        assert b\"\\r\\nHello, world!\\r\\n\" in response.content\n\n        # POSTing the same file instance a second time should have the same content.\n        files = {\"file\": upload}\n        response = client.post(\"http://127.0.0.1:8000/\", files=files)\n        assert response.status_code == 200\n        assert b\"\\r\\nHello, world!\\r\\n\" in response.content\n\n\nclass TestHeaderParamHTML5Formatting:\n    def test_unicode(self):\n        filename = \"n\\u00e4me\"\n        expected = b'filename=\"n\\xc3\\xa4me\"'\n        files = {\"upload\": (filename, b\"<file content>\")}\n        request = httpx.Request(\"GET\", \"https://www.example.com\", files=files)\n        assert expected in request.read()\n\n    def test_ascii(self):\n        filename = \"name\"\n        expected = b'filename=\"name\"'\n        files = {\"upload\": (filename, b\"<file content>\")}\n        request = httpx.Request(\"GET\", \"https://www.example.com\", files=files)\n        assert expected in request.read()\n\n    def test_unicode_escape(self):\n        filename = \"hello\\\\world\\u0022\"\n        expected = b'filename=\"hello\\\\\\\\world%22\"'\n        files = {\"upload\": (filename, b\"<file content>\")}\n        request = httpx.Request(\"GET\", \"https://www.example.com\", files=files)\n        assert expected in request.read()\n\n    def test_unicode_with_control_character(self):\n        filename = \"hello\\x1a\\x1b\\x1c\"\n        expected = b'filename=\"hello%1A\\x1b%1C\"'\n        files = {\"upload\": (filename, b\"<file content>\")}\n        request = httpx.Request(\"GET\", \"https://www.example.com\", files=files)\n        assert expected in request.read()\n"
  },
  {
    "path": "tests/test_status_codes.py",
    "content": "import httpx\n\n\ndef test_status_code_as_int():\n    # mypy doesn't (yet) recognize that IntEnum members are ints, so ignore it here\n    assert httpx.codes.NOT_FOUND == 404  # type: ignore[comparison-overlap]\n    assert str(httpx.codes.NOT_FOUND) == \"404\"\n\n\ndef test_status_code_value_lookup():\n    assert httpx.codes(404) == 404\n\n\ndef test_status_code_phrase_lookup():\n    assert httpx.codes[\"NOT_FOUND\"] == 404\n\n\ndef test_lowercase_status_code():\n    assert httpx.codes.not_found == 404  # type: ignore\n\n\ndef test_reason_phrase_for_status_code():\n    assert httpx.codes.get_reason_phrase(404) == \"Not Found\"\n\n\ndef test_reason_phrase_for_unknown_status_code():\n    assert httpx.codes.get_reason_phrase(499) == \"\"\n"
  },
  {
    "path": "tests/test_timeouts.py",
    "content": "import pytest\n\nimport httpx\n\n\n@pytest.mark.anyio\nasync def test_read_timeout(server):\n    timeout = httpx.Timeout(None, read=1e-6)\n\n    async with httpx.AsyncClient(timeout=timeout) as client:\n        with pytest.raises(httpx.ReadTimeout):\n            await client.get(server.url.copy_with(path=\"/slow_response\"))\n\n\n@pytest.mark.anyio\nasync def test_write_timeout(server):\n    timeout = httpx.Timeout(None, write=1e-6)\n\n    async with httpx.AsyncClient(timeout=timeout) as client:\n        with pytest.raises(httpx.WriteTimeout):\n            data = b\"*\" * 1024 * 1024 * 100\n            await client.put(server.url.copy_with(path=\"/slow_response\"), content=data)\n\n\n@pytest.mark.anyio\n@pytest.mark.network\nasync def test_connect_timeout(server):\n    timeout = httpx.Timeout(None, connect=1e-6)\n\n    async with httpx.AsyncClient(timeout=timeout) as client:\n        with pytest.raises(httpx.ConnectTimeout):\n            # See https://stackoverflow.com/questions/100841/\n            await client.get(\"http://10.255.255.1/\")\n\n\n@pytest.mark.anyio\nasync def test_pool_timeout(server):\n    limits = httpx.Limits(max_connections=1)\n    timeout = httpx.Timeout(None, pool=1e-4)\n\n    async with httpx.AsyncClient(limits=limits, timeout=timeout) as client:\n        with pytest.raises(httpx.PoolTimeout):\n            async with client.stream(\"GET\", server.url):\n                await client.get(server.url)\n\n\n@pytest.mark.anyio\nasync def test_async_client_new_request_send_timeout(server):\n    timeout = httpx.Timeout(1e-6)\n\n    async with httpx.AsyncClient(timeout=timeout) as client:\n        with pytest.raises(httpx.TimeoutException):\n            await client.send(\n                httpx.Request(\"GET\", server.url.copy_with(path=\"/slow_response\"))\n            )\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "import json\nimport logging\nimport os\nimport random\n\nimport pytest\n\nimport httpx\nfrom httpx._utils import URLPattern, get_environment_proxies\n\n\n@pytest.mark.parametrize(\n    \"encoding\",\n    (\n        \"utf-32\",\n        \"utf-8-sig\",\n        \"utf-16\",\n        \"utf-8\",\n        \"utf-16-be\",\n        \"utf-16-le\",\n        \"utf-32-be\",\n        \"utf-32-le\",\n    ),\n)\ndef test_encoded(encoding):\n    content = '{\"abc\": 123}'.encode(encoding)\n    response = httpx.Response(200, content=content)\n    assert response.json() == {\"abc\": 123}\n\n\ndef test_bad_utf_like_encoding():\n    content = b\"\\x00\\x00\\x00\\x00\"\n    response = httpx.Response(200, content=content)\n    with pytest.raises(json.decoder.JSONDecodeError):\n        response.json()\n\n\n@pytest.mark.parametrize(\n    (\"encoding\", \"expected\"),\n    (\n        (\"utf-16-be\", \"utf-16\"),\n        (\"utf-16-le\", \"utf-16\"),\n        (\"utf-32-be\", \"utf-32\"),\n        (\"utf-32-le\", \"utf-32\"),\n    ),\n)\ndef test_guess_by_bom(encoding, expected):\n    content = '\\ufeff{\"abc\": 123}'.encode(encoding)\n    response = httpx.Response(200, content=content)\n    assert response.json() == {\"abc\": 123}\n\n\ndef test_logging_request(server, caplog):\n    caplog.set_level(logging.INFO)\n    with httpx.Client() as client:\n        response = client.get(server.url)\n        assert response.status_code == 200\n\n    assert caplog.record_tuples == [\n        (\n            \"httpx\",\n            logging.INFO,\n            'HTTP Request: GET http://127.0.0.1:8000/ \"HTTP/1.1 200 OK\"',\n        )\n    ]\n\n\ndef test_logging_redirect_chain(server, caplog):\n    caplog.set_level(logging.INFO)\n    with httpx.Client(follow_redirects=True) as client:\n        response = client.get(server.url.copy_with(path=\"/redirect_301\"))\n        assert response.status_code == 200\n\n    assert caplog.record_tuples == [\n        (\n            \"httpx\",\n            logging.INFO,\n            \"HTTP Request: GET http://127.0.0.1:8000/redirect_301\"\n            ' \"HTTP/1.1 301 Moved Permanently\"',\n        ),\n        (\n            \"httpx\",\n            logging.INFO,\n            'HTTP Request: GET http://127.0.0.1:8000/ \"HTTP/1.1 200 OK\"',\n        ),\n    ]\n\n\n@pytest.mark.parametrize(\n    [\"environment\", \"proxies\"],\n    [\n        ({}, {}),\n        ({\"HTTP_PROXY\": \"http://127.0.0.1\"}, {\"http://\": \"http://127.0.0.1\"}),\n        (\n            {\"https_proxy\": \"http://127.0.0.1\", \"HTTP_PROXY\": \"https://127.0.0.1\"},\n            {\"https://\": \"http://127.0.0.1\", \"http://\": \"https://127.0.0.1\"},\n        ),\n        ({\"all_proxy\": \"http://127.0.0.1\"}, {\"all://\": \"http://127.0.0.1\"}),\n        ({\"TRAVIS_APT_PROXY\": \"http://127.0.0.1\"}, {}),\n        ({\"no_proxy\": \"127.0.0.1\"}, {\"all://127.0.0.1\": None}),\n        ({\"no_proxy\": \"192.168.0.0/16\"}, {\"all://192.168.0.0/16\": None}),\n        ({\"no_proxy\": \"::1\"}, {\"all://[::1]\": None}),\n        ({\"no_proxy\": \"localhost\"}, {\"all://localhost\": None}),\n        ({\"no_proxy\": \"github.com\"}, {\"all://*github.com\": None}),\n        ({\"no_proxy\": \".github.com\"}, {\"all://*.github.com\": None}),\n        ({\"no_proxy\": \"http://github.com\"}, {\"http://github.com\": None}),\n    ],\n)\ndef test_get_environment_proxies(environment, proxies):\n    os.environ.update(environment)\n\n    assert get_environment_proxies() == proxies\n\n\n@pytest.mark.parametrize(\n    [\"pattern\", \"url\", \"expected\"],\n    [\n        (\"http://example.com\", \"http://example.com\", True),\n        (\"http://example.com\", \"https://example.com\", False),\n        (\"http://example.com\", \"http://other.com\", False),\n        (\"http://example.com:123\", \"http://example.com:123\", True),\n        (\"http://example.com:123\", \"http://example.com:456\", False),\n        (\"http://example.com:123\", \"http://example.com\", False),\n        (\"all://example.com\", \"http://example.com\", True),\n        (\"all://example.com\", \"https://example.com\", True),\n        (\"http://\", \"http://example.com\", True),\n        (\"http://\", \"https://example.com\", False),\n        (\"all://\", \"https://example.com:123\", True),\n        (\"\", \"https://example.com:123\", True),\n    ],\n)\ndef test_url_matches(pattern, url, expected):\n    pattern = URLPattern(pattern)\n    assert pattern.matches(httpx.URL(url)) == expected\n\n\ndef test_pattern_priority():\n    matchers = [\n        URLPattern(\"all://\"),\n        URLPattern(\"http://\"),\n        URLPattern(\"http://example.com\"),\n        URLPattern(\"http://example.com:123\"),\n    ]\n    random.shuffle(matchers)\n    assert sorted(matchers) == [\n        URLPattern(\"http://example.com:123\"),\n        URLPattern(\"http://example.com\"),\n        URLPattern(\"http://\"),\n        URLPattern(\"all://\"),\n    ]\n"
  },
  {
    "path": "tests/test_wsgi.py",
    "content": "from __future__ import annotations\n\nimport sys\nimport typing\nimport wsgiref.validate\nfrom functools import partial\nfrom io import StringIO\n\nimport pytest\n\nimport httpx\n\nif typing.TYPE_CHECKING:  # pragma: no cover\n    from _typeshed.wsgi import StartResponse, WSGIApplication, WSGIEnvironment\n\n\ndef application_factory(output: typing.Iterable[bytes]) -> WSGIApplication:\n    def application(environ, start_response):\n        status = \"200 OK\"\n\n        response_headers = [\n            (\"Content-type\", \"text/plain\"),\n        ]\n\n        start_response(status, response_headers)\n\n        for item in output:\n            yield item\n\n    return wsgiref.validate.validator(application)\n\n\ndef echo_body(\n    environ: WSGIEnvironment, start_response: StartResponse\n) -> typing.Iterable[bytes]:\n    status = \"200 OK\"\n    output = environ[\"wsgi.input\"].read()\n\n    response_headers = [\n        (\"Content-type\", \"text/plain\"),\n    ]\n\n    start_response(status, response_headers)\n\n    return [output]\n\n\ndef echo_body_with_response_stream(\n    environ: WSGIEnvironment, start_response: StartResponse\n) -> typing.Iterable[bytes]:\n    status = \"200 OK\"\n\n    response_headers = [(\"Content-Type\", \"text/plain\")]\n\n    start_response(status, response_headers)\n\n    def output_generator(f: typing.IO[bytes]) -> typing.Iterator[bytes]:\n        while True:\n            output = f.read(2)\n            if not output:\n                break\n            yield output\n\n    return output_generator(f=environ[\"wsgi.input\"])\n\n\ndef raise_exc(\n    environ: WSGIEnvironment,\n    start_response: StartResponse,\n    exc: type[Exception] = ValueError,\n) -> typing.Iterable[bytes]:\n    status = \"500 Server Error\"\n    output = b\"Nope!\"\n\n    response_headers = [\n        (\"Content-type\", \"text/plain\"),\n    ]\n\n    try:\n        raise exc()\n    except exc:\n        exc_info = sys.exc_info()\n        start_response(status, response_headers, exc_info)\n\n    return [output]\n\n\ndef log_to_wsgi_log_buffer(environ, start_response):\n    print(\"test1\", file=environ[\"wsgi.errors\"])\n    environ[\"wsgi.errors\"].write(\"test2\")\n    return echo_body(environ, start_response)\n\n\ndef test_wsgi():\n    transport = httpx.WSGITransport(app=application_factory([b\"Hello, World!\"]))\n    client = httpx.Client(transport=transport)\n    response = client.get(\"http://www.example.org/\")\n    assert response.status_code == 200\n    assert response.text == \"Hello, World!\"\n\n\ndef test_wsgi_upload():\n    transport = httpx.WSGITransport(app=echo_body)\n    client = httpx.Client(transport=transport)\n    response = client.post(\"http://www.example.org/\", content=b\"example\")\n    assert response.status_code == 200\n    assert response.text == \"example\"\n\n\ndef test_wsgi_upload_with_response_stream():\n    transport = httpx.WSGITransport(app=echo_body_with_response_stream)\n    client = httpx.Client(transport=transport)\n    response = client.post(\"http://www.example.org/\", content=b\"example\")\n    assert response.status_code == 200\n    assert response.text == \"example\"\n\n\ndef test_wsgi_exc():\n    transport = httpx.WSGITransport(app=raise_exc)\n    client = httpx.Client(transport=transport)\n    with pytest.raises(ValueError):\n        client.get(\"http://www.example.org/\")\n\n\ndef test_wsgi_http_error():\n    transport = httpx.WSGITransport(app=partial(raise_exc, exc=RuntimeError))\n    client = httpx.Client(transport=transport)\n    with pytest.raises(RuntimeError):\n        client.get(\"http://www.example.org/\")\n\n\ndef test_wsgi_generator():\n    output = [b\"\", b\"\", b\"Some content\", b\" and more content\"]\n    transport = httpx.WSGITransport(app=application_factory(output))\n    client = httpx.Client(transport=transport)\n    response = client.get(\"http://www.example.org/\")\n    assert response.status_code == 200\n    assert response.text == \"Some content and more content\"\n\n\ndef test_wsgi_generator_empty():\n    output = [b\"\", b\"\", b\"\", b\"\"]\n    transport = httpx.WSGITransport(app=application_factory(output))\n    client = httpx.Client(transport=transport)\n    response = client.get(\"http://www.example.org/\")\n    assert response.status_code == 200\n    assert response.text == \"\"\n\n\ndef test_logging():\n    buffer = StringIO()\n    transport = httpx.WSGITransport(app=log_to_wsgi_log_buffer, wsgi_errors=buffer)\n    client = httpx.Client(transport=transport)\n    response = client.post(\"http://www.example.org/\", content=b\"example\")\n    assert response.status_code == 200  # no errors\n    buffer.seek(0)\n    assert buffer.read() == \"test1\\ntest2\"\n\n\n@pytest.mark.parametrize(\n    \"url, expected_server_port\",\n    [\n        pytest.param(\"http://www.example.org\", \"80\", id=\"auto-http\"),\n        pytest.param(\"https://www.example.org\", \"443\", id=\"auto-https\"),\n        pytest.param(\"http://www.example.org:8000\", \"8000\", id=\"explicit-port\"),\n    ],\n)\ndef test_wsgi_server_port(url: str, expected_server_port: str) -> None:\n    \"\"\"\n    SERVER_PORT is populated correctly from the requested URL.\n    \"\"\"\n    hello_world_app = application_factory([b\"Hello, World!\"])\n    server_port: str | None = None\n\n    def app(environ, start_response):\n        nonlocal server_port\n        server_port = environ[\"SERVER_PORT\"]\n        return hello_world_app(environ, start_response)\n\n    transport = httpx.WSGITransport(app=app)\n    client = httpx.Client(transport=transport)\n    response = client.get(url)\n    assert response.status_code == 200\n    assert response.text == \"Hello, World!\"\n    assert server_port == expected_server_port\n\n\ndef test_wsgi_server_protocol():\n    server_protocol = None\n\n    def app(environ, start_response):\n        nonlocal server_protocol\n        server_protocol = environ[\"SERVER_PROTOCOL\"]\n        start_response(\"200 OK\", [(\"Content-Type\", \"text/plain\")])\n        return [b\"success\"]\n\n    transport = httpx.WSGITransport(app=app)\n    with httpx.Client(transport=transport, base_url=\"http://testserver\") as client:\n        response = client.get(\"/\")\n\n    assert response.status_code == 200\n    assert response.text == \"success\"\n    assert server_protocol == \"HTTP/1.1\"\n"
  }
]