1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Accepting a request body the client compressed.
//!
//! ```text
//! cargo run -p kynos --example decompression --features compression
//! ```
//!
//! Then send the same payload twice, once plain and once gzipped, and watch the
//! handler see no difference:
//!
//! ```text
//! curl -s localhost:3000/measurements -H 'content-type: application/json' \
//! --data '{"readings":[1,2,3]}'
//!
//! printf '{"readings":[1,2,3]}' | gzip | curl -s localhost:3000/measurements \
//! -H 'content-type: application/json' -H 'content-encoding: gzip' --data-binary @-
//! ```
//!
//! Five things are worth noticing:
//!
//! * **This is not `Accept-Encoding` in reverse.** RFC 9110 section 12.5.3's
//! `Accept-Encoding` is a client saying what it will *receive*, and it says
//! nothing about what it may send. What a client sends is announced in
//! `Content-Encoding` (section 8.4) and is not negotiated at all: it arrives,
//! and the server either understands it or refuses it with 415. The two
//! directions are separate decisions, which is why they are separate
//! interceptors — mounting [`Compression`] does not make a service accept
//! compressed uploads, and this does not make it send compressed responses.
//! * **`BodySize` is not the guard here, and mounting it is a compile error.**
//! Two kilobytes of zeroes are a gigabyte of gzip output, so a limit measured
//! before decoding measures the one number an attacker sets freely. The limit
//! `Decompression` takes is the route's body limit applied to what the
//! handler will actually see. Both answer 413, so `statuses_disjoint` refuses
//! the pair — correctly, since it would be ambiguous as well as redundant.
//! * **The refusals are declared, and appear in the description.** 415, 413 and
//! 400 reach every covered operation because [`Undecodable`] is the
//! interceptor's `Short` type. A client generator therefore knows to expect
//! them without anyone remembering to write them down.
//! * **Each refusal names its own RFC 9457 `type`, and there are three.** A
//! client branches on `type`, and `about:blank` says the status code is the
//! whole story — true of neither of these. So each is named with a marker of
//! its own, and the declared response narrows `type` to that URI rather than
//! merely exemplifying it: a body disagreeing with its own declaration fails
//! the conformance harness. One marker for all three would say a malformed
//! body and an unsupported coding are the same problem.
//! * **What is stripped is stripped because it stopped being true.** Section
//! 8.4 says the representation *is* the coded form, and that all other
//! metadata about it describes that form. Once the coded form is gone,
//! `Content-Length` names the wrong number and `Content-Digest` names octets
//! nothing holds — so they go, rather than being left to be checked against a
//! body they were never computed over.
//!
//! [`Compression`]: kynos::middleware::compression::Compression
//! [`Undecodable`]: kynos::middleware::decompression::Undecodable
use Ipv4Addr;
use ;
/// What this service calls a coding it cannot decode.
///
/// A marker rather than a value: what an interceptor declares is read from its
/// associated types, so a URI chosen per request would reach the wire and leave
/// the description saying `about:blank` about it.
;
/// What it calls a body that is not the coding it claimed.
;
/// What it calls a payload that expands past the limit.
///
/// The one an operator most wants told apart: a client whose batch is simply
/// too big retries smaller, and a client sending a decompression bomb does not.
;
/// A batch of sensor readings.
///
/// The shape this example exists for: a payload big enough and repetitive
/// enough that a client sending thousands of them a minute will compress, and
/// small enough on the wire afterwards that nothing between here and there
/// notices.
/// Records a batch of readings.
///
/// Nothing here knows whether the body arrived compressed. That is the whole
/// point: `Json<Measurements>` is handed the decoded octets, so a handler never
/// grows a branch for a transport decision.
async
async