Conversation
`QueryPair` and `HeaderPair` leave cmd_api.py for a module of their own, because a second command needs the same two shapes and the details are easy to get subtly different: where the split happens, what is trimmed, which names are refused. The refusal list is the one thing that differs. `api` writes the body itself and always as JSON, so it owns `content-type` as well; a command that sends a body as it was given has to leave the type to whoever wrote it. `HeaderPair` therefore takes the set it refuses.
`audible api` speaks to the Audible API and expects JSON. The rest of what audible-cli reaches had nowhere to go from the command line: the website a companion file comes from, the delivery host that hands out the audio, Amazon's CDE service for annotations, and every answer that is not JSON. `request` takes a whole url. Its host has to be one of the 45 this tool has business with -- `api`, `www`, `cds` and `api.amazon` of a marketplace, plus `cde-ta-g7g.amazon.com` -- derived from the library's marketplace list so it grows with it. The request carries the credentials of the profile, so the list is the list of who may be handed them, and a url naming anything else is refused before they are loaded, as are plain http, a port of its own and a name and password written into the url. Body and answer pass through untouched. The url is sent as it was written rather than parsed and rebuilt: a delivery url is signed over those exact bytes, and reading its query into pairs would turn `%FF` into the replacement character and `%20` into `+`. `--query` appends to the raw query instead. The answer is streamed, so a file of any size can go through `--output` without being held whole in memory first. `--include` writes the status line and the response headers in front of the body, wherever the body goes; `--dump-header` writes them to a file of their own. A redirect is shown, not followed. An error status ends the command after the answer has been written, because the body of a refusal is what explains it.
The host allowlist is the security boundary, so it is tested from both sides: every family of allowed host reaches the client, and a foreign host, plain http, an ip literal, a punycode name, a trailing dot, a port and a name in the url are all refused without a client ever being built. The rest pins what passes through unchanged: a query with `%FF`, `%20` and a key without a value, a body from an option, a file and a pipe, repeated request headers, repeated response headers, a compressed answer and any method the host might understand. Two cases go through the real `AsyncClient` behind a mock transport, where a stand-in cannot help: the url that actually leaves, and that a redirect is not followed -- the second needs two hops to say anything.
The README section says which hosts are allowed and why the list exists, what passes through unchanged, and how `--include`, `--dump-header` and an error status behave.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
audible apispeaks to the API and expects JSON back. The rest of whataudible-cli reaches had nowhere to go from the command line: the website a
companion file comes from, the delivery host that hands out the audio, Amazon's
CDE service for annotations, and every answer that is not JSON.
Which hosts, and why there is a list at all
The request carries the credentials of the profile, so the list of allowed
hosts is the list of who may be handed them:
api,www,cdsandapi.amazonof any of the 11 marketplaces, pluscde-ta-g7g.amazon.com. Thatis 45 hosts, derived in
constants.pyfrom the library's marketplace list, soit grows with it. A url naming anything else is refused before any credentials
are loaded, as are plain http, a port of its own, and a name and password
written into the url.
What passes through untouched
The url is sent as it was written, not parsed and rebuilt. A delivery url is
signed over those exact bytes, and reading its query into pairs turns
%FFinto the replacement character,
%20into+andflagintoflag=.--queryappends to the raw query instead of merging into a parsed one.The body goes out as it was given — bytes, not JSON — and the answer is
streamed, so a file of any size can go through
--outputwithout being heldwhole in memory. The method is whatever the host understands,
PATCHandOPTIONSincluded.--include/-iwrites the status line and the response headers in front of thebody, wherever the body goes;
--dump-header/-Dwrites them to a file of theirown. A header that came twice is written twice. A redirect is shown, not
followed. An error status ends the command after the answer has been written,
because the body of a refusal is what explains it.
Shared with
apiQueryPairandHeaderPairmove to_params.py, so both commands split aquery and a header the same way and refuse the same headers. One name differs:
apiwrites the body itself and always as JSON, so it ownscontent-typeaswell, while here the body is raw and its type is the caller's to declare.
Tests
59, including two that go through the real
AsyncClientbehind a mocktransport, where a stand-in cannot help: the url that actually leaves, and that
a redirect is not followed — the second needs two hops to say anything.