Coverage Report

Created: 2024-05-21 06:19

/rust/git/checkouts/micro-http-22be4cdcbef12607/ef43cef/src/response.rs
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
// SPDX-License-Identifier: Apache-2.0
3
4
use std::io::{Error as WriteError, Write};
5
6
use crate::ascii::{COLON, CR, LF, SP};
7
use crate::common::{Body, Version};
8
use crate::headers::{Header, MediaType};
9
use crate::Method;
10
11
/// Wrapper over a response status code.
12
///
13
/// The status code is defined as specified in the
14
/// [RFC](https://tools.ietf.org/html/rfc7231#section-6).
15
0
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16
pub enum StatusCode {
17
    /// 100, Continue
18
    Continue,
19
    /// 200, OK
20
    OK,
21
    /// 204, No Content
22
    NoContent,
23
    /// 400, Bad Request
24
    BadRequest,
25
    /// 401, Unauthorized
26
    Unauthorized,
27
    /// 404, Not Found
28
    NotFound,
29
    /// 405, Method Not Allowed
30
    MethodNotAllowed,
31
    /// 413, Payload Too Large
32
    PayloadTooLarge,
33
    /// 500, Internal Server Error
34
    InternalServerError,
35
    /// 501, Not Implemented
36
    NotImplemented,
37
    /// 503, Service Unavailable
38
    ServiceUnavailable,
39
}
40
41
impl StatusCode {
42
    /// Returns the status code as bytes.
43
0
    pub fn raw(self) -> &'static [u8; 3] {
44
0
        match self {
45
0
            Self::Continue => b"100",
46
0
            Self::OK => b"200",
47
0
            Self::NoContent => b"204",
48
0
            Self::BadRequest => b"400",
49
0
            Self::Unauthorized => b"401",
50
0
            Self::NotFound => b"404",
51
0
            Self::MethodNotAllowed => b"405",
52
0
            Self::PayloadTooLarge => b"413",
53
0
            Self::InternalServerError => b"500",
54
0
            Self::NotImplemented => b"501",
55
0
            Self::ServiceUnavailable => b"503",
56
        }
57
0
    }
58
}
59
60
0
#[derive(Debug, PartialEq)]
61
struct StatusLine {
62
    http_version: Version,
63
    status_code: StatusCode,
64
}
65
66
impl StatusLine {
67
15.9k
    fn new(http_version: Version, status_code: StatusCode) -> Self {
68
15.9k
        Self {
69
15.9k
            http_version,
70
15.9k
            status_code,
71
15.9k
        }
72
15.9k
    }
73
74
0
    fn write_all<T: Write>(&self, mut buf: T) -> Result<(), WriteError> {
75
0
        buf.write_all(self.http_version.raw())?;
76
0
        buf.write_all(&[SP])?;
77
0
        buf.write_all(self.status_code.raw())?;
78
0
        buf.write_all(&[SP, CR, LF])?;
79
80
0
        Ok(())
81
0
    }
82
}
83
84
/// Wrapper over the list of headers associated with a HTTP Response.
85
/// When creating a ResponseHeaders object, the content type is initialized to `text/plain`.
86
/// The content type can be updated with a call to `set_content_type`.
87
0
#[derive(Debug, PartialEq, Eq)]
88
pub struct ResponseHeaders {
89
    content_length: Option<i32>,
90
    content_type: MediaType,
91
    deprecation: bool,
92
    server: String,
93
    allow: Vec<Method>,
94
    accept_encoding: bool,
95
}
96
97
impl Default for ResponseHeaders {
98
15.9k
    fn default() -> Self {
99
15.9k
        Self {
100
15.9k
            content_length: Default::default(),
101
15.9k
            content_type: Default::default(),
102
15.9k
            deprecation: false,
103
15.9k
            server: String::from("Firecracker API"),
104
15.9k
            allow: Vec::new(),
105
15.9k
            accept_encoding: false,
106
15.9k
        }
107
15.9k
    }
108
}
109
110
impl ResponseHeaders {
111
    // The logic pertaining to `Allow` header writing.
112
0
    fn write_allow_header<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> {
113
0
        if self.allow.is_empty() {
114
0
            return Ok(());
115
0
        }
116
0
117
0
        buf.write_all(b"Allow: ")?;
118
119
0
        let delimitator = b", ";
120
0
        for (idx, method) in self.allow.iter().enumerate() {
121
0
            buf.write_all(method.raw())?;
122
            // We check above that `self.allow` is not empty.
123
0
            if idx < self.allow.len() - 1 {
124
0
                buf.write_all(delimitator)?;
125
0
            }
126
        }
127
128
0
        buf.write_all(&[CR, LF])
129
0
    }
130
131
    // The logic pertaining to `Deprecation` header writing.
132
0
    fn write_deprecation_header<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> {
133
0
        if !self.deprecation {
134
0
            return Ok(());
135
0
        }
136
0
137
0
        buf.write_all(b"Deprecation: true")?;
138
0
        buf.write_all(&[CR, LF])
139
0
    }
140
141
    /// Writes the headers to `buf` using the HTTP specification.
142
0
    pub fn write_all<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> {
143
0
        buf.write_all(Header::Server.raw())?;
144
0
        buf.write_all(&[COLON, SP])?;
145
0
        buf.write_all(self.server.as_bytes())?;
146
0
        buf.write_all(&[CR, LF])?;
147
148
0
        buf.write_all(b"Connection: keep-alive")?;
149
0
        buf.write_all(&[CR, LF])?;
150
151
0
        self.write_allow_header(buf)?;
152
0
        self.write_deprecation_header(buf)?;
153
154
0
        if let Some(content_length) = self.content_length {
155
0
            buf.write_all(Header::ContentType.raw())?;
156
0
            buf.write_all(&[COLON, SP])?;
157
0
            buf.write_all(self.content_type.as_str().as_bytes())?;
158
0
            buf.write_all(&[CR, LF])?;
159
160
0
            buf.write_all(Header::ContentLength.raw())?;
161
0
            buf.write_all(&[COLON, SP])?;
162
0
            buf.write_all(content_length.to_string().as_bytes())?;
163
0
            buf.write_all(&[CR, LF])?;
164
165
0
            if self.accept_encoding {
166
0
                buf.write_all(Header::AcceptEncoding.raw())?;
167
0
                buf.write_all(&[COLON, SP])?;
168
0
                buf.write_all(b"identity")?;
169
0
                buf.write_all(&[CR, LF])?;
170
0
            }
171
0
        }
172
173
0
        buf.write_all(&[CR, LF])
174
0
    }
175
176
    /// Sets the content length to be written in the HTTP response.
177
15.5k
    pub fn set_content_length(&mut self, content_length: Option<i32>) {
178
15.5k
        self.content_length = content_length;
179
15.5k
    }
180
181
    /// Sets the HTTP response header server.
182
0
    pub fn set_server(&mut self, server: &str) {
183
0
        self.server = String::from(server);
184
0
    }
185
186
    /// Sets the content type to be written in the HTTP response.
187
0
    pub fn set_content_type(&mut self, content_type: MediaType) {
188
0
        self.content_type = content_type;
189
0
    }
190
191
    /// Sets the `Deprecation` header to be written in the HTTP response.
192
    /// https://tools.ietf.org/id/draft-dalal-deprecation-header-03.html
193
    #[allow(unused)]
194
0
    pub fn set_deprecation(&mut self) {
195
0
        self.deprecation = true;
196
0
    }
197
198
    /// Sets the encoding type to be written in the HTTP response.
199
    #[allow(unused)]
200
0
    pub fn set_encoding(&mut self) {
201
0
        self.accept_encoding = true;
202
0
    }
203
}
204
205
/// Wrapper over an HTTP Response.
206
///
207
/// The Response is created using a `Version` and a `StatusCode`. When creating a Response object,
208
/// the body is initialized to `None` and the header is initialized with the `default` value. The body
209
/// can be updated with a call to `set_body`. The header can be updated with `set_content_type` and
210
/// `set_server`.
211
0
#[derive(Debug, PartialEq)]
212
pub struct Response {
213
    status_line: StatusLine,
214
    headers: ResponseHeaders,
215
    body: Option<Body>,
216
}
217
218
impl Response {
219
    /// Creates a new HTTP `Response` with an empty body.
220
    ///
221
    /// Although there are several cases where Content-Length field must not
222
    /// be sent, micro-http omits Content-Length field when the response
223
    /// status code is 1XX or 204. If needed, users can remove it by calling
224
    /// `set_content_length(None)`.
225
    ///
226
    /// https://datatracker.ietf.org/doc/html/rfc9110#name-content-length
227
    /// > A server MAY send a Content-Length header field in a response to a
228
    /// > HEAD request (Section 9.3.2); a server MUST NOT send Content-Length
229
    /// > in such a response unless its field value equals the decimal number
230
    /// > of octets that would have been sent in the content of a response if
231
    /// > the same request had used the GET method.
232
    /// >
233
    /// > A server MAY send a Content-Length header field in a 304 (Not
234
    /// > Modified) response to a conditional GET request (Section 15.4.5); a
235
    /// > server MUST NOT send Content-Length in such a response unless its
236
    /// > field value equals the decimal number of octets that would have been
237
    /// > sent in the content of a 200 (OK) response to the same request.
238
    /// >
239
    /// > A server MUST NOT send a Content-Length header field in any response
240
    /// > with a status code of 1xx (Informational) or 204 (No Content). A
241
    /// > server MUST NOT send a Content-Length header field in any 2xx
242
    /// > (Successful) response to a CONNECT request (Section 9.3.6).
243
15.9k
    pub fn new(http_version: Version, status_code: StatusCode) -> Self {
244
15.9k
        Self {
245
15.9k
            status_line: StatusLine::new(http_version, status_code),
246
15.9k
            headers: ResponseHeaders {
247
15.9k
                content_length: match status_code {
248
377
                    StatusCode::Continue | StatusCode::NoContent => None,
249
15.5k
                    _ => Some(0),
250
                },
251
15.9k
                ..Default::default()
252
15.9k
            },
253
15.9k
            body: Default::default(),
254
15.9k
        }
255
15.9k
    }
256
257
    /// Updates the body of the `Response`.
258
    ///
259
    /// This function has side effects because it also updates the headers:
260
    /// - `ContentLength`: this is set to the length of the specified body.
261
15.5k
    pub fn set_body(&mut self, body: Body) {
262
15.5k
        self.headers.set_content_length(Some(body.len() as i32));
263
15.5k
        self.body = Some(body);
264
15.5k
    }
265
266
    /// Updates the content length of the `Response`.
267
    ///
268
    /// It is recommended to use this method only when removing Content-Length
269
    /// field if the response status is not 1XX or 204.
270
0
    pub fn set_content_length(&mut self, content_length: Option<i32>) {
271
0
        self.headers.set_content_length(content_length);
272
0
    }
273
274
    /// Updates the content type of the `Response`.
275
0
    pub fn set_content_type(&mut self, content_type: MediaType) {
276
0
        self.headers.set_content_type(content_type);
277
0
    }
278
279
    /// Marks the `Response` as deprecated.
280
0
    pub fn set_deprecation(&mut self) {
281
0
        self.headers.set_deprecation();
282
0
    }
283
284
    /// Updates the encoding type of `Response`.
285
0
    pub fn set_encoding(&mut self) {
286
0
        self.headers.set_encoding();
287
0
    }
288
289
    /// Sets the HTTP response server.
290
0
    pub fn set_server(&mut self, server: &str) {
291
0
        self.headers.set_server(server);
292
0
    }
293
294
    /// Sets the HTTP allowed methods.
295
0
    pub fn set_allow(&mut self, methods: Vec<Method>) {
296
0
        self.headers.allow = methods;
297
0
    }
298
299
    /// Allows a specific HTTP method.
300
0
    pub fn allow_method(&mut self, method: Method) {
301
0
        self.headers.allow.push(method);
302
0
    }
303
304
0
    fn write_body<T: Write>(&self, mut buf: T) -> Result<(), WriteError> {
305
0
        if let Some(ref body) = self.body {
306
0
            buf.write_all(body.raw())?;
307
0
        }
308
0
        Ok(())
309
0
    }
310
311
    /// Writes the content of the `Response` to the specified `buf`.
312
    ///
313
    /// # Errors
314
    /// Returns an error when the buffer is not large enough.
315
0
    pub fn write_all<T: Write>(&self, mut buf: &mut T) -> Result<(), WriteError> {
316
0
        self.status_line.write_all(&mut buf)?;
317
0
        self.headers.write_all(&mut buf)?;
318
0
        self.write_body(&mut buf)?;
319
320
0
        Ok(())
321
0
    }
322
323
    /// Returns the Status Code of the Response.
324
0
    pub fn status(&self) -> StatusCode {
325
0
        self.status_line.status_code
326
0
    }
327
328
    /// Returns the Body of the response. If the response does not have a body,
329
    /// it returns None.
330
0
    pub fn body(&self) -> Option<Body> {
331
0
        self.body.clone()
332
0
    }
333
334
    /// Returns the Content Length of the response.
335
0
    pub fn content_length(&self) -> i32 {
336
0
        self.headers.content_length.unwrap_or(0)
337
0
    }
338
339
    /// Returns the Content Type of the response.
340
0
    pub fn content_type(&self) -> MediaType {
341
0
        self.headers.content_type
342
0
    }
343
344
    /// Returns the deprecation status of the response.
345
0
    pub fn deprecation(&self) -> bool {
346
0
        self.headers.deprecation
347
0
    }
348
349
    /// Returns the HTTP Version of the response.
350
0
    pub fn http_version(&self) -> Version {
351
0
        self.status_line.http_version
352
0
    }
353
354
    /// Returns the allowed HTTP methods.
355
0
    pub fn allow(&self) -> Vec<Method> {
356
0
        self.headers.allow.clone()
357
0
    }
358
}
359
360
#[cfg(test)]
361
mod tests {
362
    use super::*;
363
364
    #[test]
365
    fn test_write_response() {
366
        let mut response = Response::new(Version::Http10, StatusCode::OK);
367
        let body = "This is a test";
368
        response.set_body(Body::new(body));
369
        response.set_content_type(MediaType::PlainText);
370
        response.set_encoding();
371
372
        assert_eq!(response.status(), StatusCode::OK);
373
        assert_eq!(response.body().unwrap(), Body::new(body));
374
        assert_eq!(response.http_version(), Version::Http10);
375
        assert_eq!(response.content_length(), 14);
376
        assert_eq!(response.content_type(), MediaType::PlainText);
377
378
        let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\
379
            Server: Firecracker API\r\n\
380
            Connection: keep-alive\r\n\
381
            Content-Type: text/plain\r\n\
382
            Content-Length: 14\r\n\
383
            Accept-Encoding: identity\r\n\r\n\
384
            This is a test";
385
386
        let mut response_buf: [u8; 153] = [0; 153];
387
        assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
388
        assert_eq!(response_buf.as_ref(), expected_response);
389
390
        // Test response `Allow` header.
391
        let mut response = Response::new(Version::Http10, StatusCode::OK);
392
        let allowed_methods = vec![Method::Get, Method::Patch, Method::Put];
393
        response.set_allow(allowed_methods.clone());
394
        assert_eq!(response.allow(), allowed_methods);
395
396
        let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\
397
            Server: Firecracker API\r\n\
398
            Connection: keep-alive\r\n\
399
            Allow: GET, PATCH, PUT\r\n\
400
            Content-Type: application/json\r\n\
401
            Content-Length: 0\r\n\r\n";
402
        let mut response_buf: [u8; 141] = [0; 141];
403
        assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
404
        assert_eq!(response_buf.as_ref(), expected_response);
405
406
        // Test write failed.
407
        let mut response_buf: [u8; 1] = [0; 1];
408
        assert!(response.write_all(&mut response_buf.as_mut()).is_err());
409
    }
410
411
    #[test]
412
    fn test_set_server() {
413
        let mut response = Response::new(Version::Http10, StatusCode::OK);
414
        let body = "This is a test";
415
        let server = "rust-vmm API";
416
        response.set_body(Body::new(body));
417
        response.set_content_type(MediaType::PlainText);
418
        response.set_server(server);
419
420
        assert_eq!(response.status(), StatusCode::OK);
421
        assert_eq!(response.body().unwrap(), Body::new(body));
422
        assert_eq!(response.http_version(), Version::Http10);
423
        assert_eq!(response.content_length(), 14);
424
        assert_eq!(response.content_type(), MediaType::PlainText);
425
426
        let expected_response = format!(
427
            "HTTP/1.0 200 \r\n\
428
             Server: {}\r\n\
429
             Connection: keep-alive\r\n\
430
             Content-Type: text/plain\r\n\
431
             Content-Length: 14\r\n\r\n\
432
             This is a test",
433
            server
434
        );
435
436
        let mut response_buf: [u8; 123] = [0; 123];
437
        assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
438
        assert!(response_buf.as_ref() == expected_response.as_bytes());
439
    }
440
441
    #[test]
442
    fn test_status_code() {
443
        assert_eq!(StatusCode::Continue.raw(), b"100");
444
        assert_eq!(StatusCode::OK.raw(), b"200");
445
        assert_eq!(StatusCode::NoContent.raw(), b"204");
446
        assert_eq!(StatusCode::BadRequest.raw(), b"400");
447
        assert_eq!(StatusCode::Unauthorized.raw(), b"401");
448
        assert_eq!(StatusCode::NotFound.raw(), b"404");
449
        assert_eq!(StatusCode::MethodNotAllowed.raw(), b"405");
450
        assert_eq!(StatusCode::PayloadTooLarge.raw(), b"413");
451
        assert_eq!(StatusCode::InternalServerError.raw(), b"500");
452
        assert_eq!(StatusCode::NotImplemented.raw(), b"501");
453
        assert_eq!(StatusCode::ServiceUnavailable.raw(), b"503");
454
    }
455
456
    #[test]
457
    fn test_allow_method() {
458
        let mut response = Response::new(Version::Http10, StatusCode::MethodNotAllowed);
459
        response.allow_method(Method::Get);
460
        response.allow_method(Method::Put);
461
        assert_eq!(response.allow(), vec![Method::Get, Method::Put]);
462
    }
463
464
    #[test]
465
    fn test_deprecation() {
466
        // Test a deprecated response with body.
467
        let mut response = Response::new(Version::Http10, StatusCode::OK);
468
        let body = "This is a test";
469
        response.set_body(Body::new(body));
470
        response.set_content_type(MediaType::PlainText);
471
        response.set_encoding();
472
        response.set_deprecation();
473
474
        assert_eq!(response.status(), StatusCode::OK);
475
        assert_eq!(response.body().unwrap(), Body::new(body));
476
        assert_eq!(response.http_version(), Version::Http10);
477
        assert_eq!(response.content_length(), 14);
478
        assert_eq!(response.content_type(), MediaType::PlainText);
479
        assert!(response.deprecation());
480
481
        let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\
482
            Server: Firecracker API\r\n\
483
            Connection: keep-alive\r\n\
484
            Deprecation: true\r\n\
485
            Content-Type: text/plain\r\n\
486
            Content-Length: 14\r\n\
487
            Accept-Encoding: identity\r\n\r\n\
488
            This is a test";
489
490
        let mut response_buf: [u8; 172] = [0; 172];
491
        assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
492
        assert_eq!(response_buf.as_ref(), expected_response);
493
494
        // Test a deprecated response without a body.
495
        let mut response = Response::new(Version::Http10, StatusCode::NoContent);
496
        response.set_deprecation();
497
498
        assert_eq!(response.status(), StatusCode::NoContent);
499
        assert_eq!(response.http_version(), Version::Http10);
500
        assert!(response.deprecation());
501
502
        let expected_response: &'static [u8] = b"HTTP/1.0 204 \r\n\
503
            Server: Firecracker API\r\n\
504
            Connection: keep-alive\r\n\
505
            Deprecation: true\r\n\r\n";
506
507
        let mut response_buf: [u8; 85] = [0; 85];
508
        assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
509
        assert_eq!(response_buf.as_ref(), expected_response);
510
    }
511
512
    #[test]
513
    fn test_equal() {
514
        let response = Response::new(Version::Http10, StatusCode::MethodNotAllowed);
515
        let another_response = Response::new(Version::Http10, StatusCode::MethodNotAllowed);
516
        assert_eq!(response, another_response);
517
518
        let response = Response::new(Version::Http10, StatusCode::OK);
519
        let another_response = Response::new(Version::Http10, StatusCode::BadRequest);
520
        assert_ne!(response, another_response);
521
    }
522
523
    #[test]
524
    fn test_content_length() {
525
        // If the status code is 1XX or 204, Content-Length field must not exist.
526
        let response = Response::new(Version::Http10, StatusCode::Continue);
527
        let expected_response: &'static [u8] = b"HTTP/1.0 100 \r\n\
528
            Server: Firecracker API\r\n\
529
            Connection: keep-alive\r\n\r\n";
530
        let mut response_buf: [u8; 66] = [0; 66];
531
        assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
532
        assert_eq!(response_buf.as_ref(), expected_response);
533
534
        let response = Response::new(Version::Http10, StatusCode::NoContent);
535
        let expected_response: &'static [u8] = b"HTTP/1.0 204 \r\n\
536
            Server: Firecracker API\r\n\
537
            Connection: keep-alive\r\n\r\n";
538
        let mut response_buf: [u8; 66] = [0; 66];
539
        assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
540
        assert_eq!(response_buf.as_ref(), expected_response);
541
542
        // If not 1XX or 204, Content-Length field must exist even if the body isn't set.
543
        let response = Response::new(Version::Http10, StatusCode::OK);
544
        let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\
545
            Server: Firecracker API\r\n\
546
            Connection: keep-alive\r\n\
547
            Content-Type: application/json\r\n\
548
            Content-Length: 0\r\n\r\n";
549
        let mut response_buf: [u8; 117] = [0; 117];
550
        assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
551
        assert_eq!(response_buf.as_ref(), expected_response);
552
    }
553
}