xref: /cloud-hypervisor/api_client/src/lib.rs (revision 87c0791d535fd9a1a248dd1b146b65ccac106dd2)
1 // Copyright © 2020 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 
6 use std::fmt;
7 use std::io::{Read, Write};
8 use std::os::unix::io::RawFd;
9 use vmm_sys_util::sock_ctrl_msg::ScmSocket;
10 
11 #[derive(Debug)]
12 pub enum Error {
13     Socket(std::io::Error),
14     SocketSendFds(vmm_sys_util::errno::Error),
15     StatusCodeParsing(std::num::ParseIntError),
16     MissingProtocol,
17     ContentLengthParsing(std::num::ParseIntError),
18     ServerResponse(StatusCode, Option<String>),
19 }
20 
21 impl fmt::Display for Error {
22     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23         use Error::*;
24         match self {
25             Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {}", e),
26             SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {}", e),
27             StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {}", e),
28             MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
29             ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {}", e),
30             ServerResponse(s, o) => {
31                 if let Some(o) = o {
32                     write!(f, "Server responded with an error: {:?}: {}", s, o)
33                 } else {
34                     write!(f, "Server responded with an error: {:?}", s)
35                 }
36             }
37         }
38     }
39 }
40 
41 #[derive(Clone, Copy, Debug)]
42 pub enum StatusCode {
43     Continue,
44     Ok,
45     NoContent,
46     BadRequest,
47     NotFound,
48     InternalServerError,
49     NotImplemented,
50     Unknown,
51 }
52 
53 impl StatusCode {
54     fn from_raw(code: usize) -> StatusCode {
55         match code {
56             100 => StatusCode::Continue,
57             200 => StatusCode::Ok,
58             204 => StatusCode::NoContent,
59             400 => StatusCode::BadRequest,
60             404 => StatusCode::NotFound,
61             500 => StatusCode::InternalServerError,
62             501 => StatusCode::NotImplemented,
63             _ => StatusCode::Unknown,
64         }
65     }
66 
67     fn parse(code: &str) -> Result<StatusCode, Error> {
68         Ok(StatusCode::from_raw(
69             code.trim().parse().map_err(Error::StatusCodeParsing)?,
70         ))
71     }
72 
73     fn is_server_error(self) -> bool {
74         !matches!(
75             self,
76             StatusCode::Ok | StatusCode::Continue | StatusCode::NoContent
77         )
78     }
79 }
80 
81 fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
82     let header_str = format!("{}: ", header);
83     res.find(&header_str)
84         .map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
85 }
86 
87 fn get_status_code(res: &str) -> Result<StatusCode, Error> {
88     if let Some(o) = res.find("HTTP/1.1") {
89         Ok(StatusCode::parse(
90             &res[o + "HTTP/1.1 ".len()..res[o..].find('\r').unwrap()],
91         )?)
92     } else {
93         Err(Error::MissingProtocol)
94     }
95 }
96 
97 fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
98     let mut res = String::new();
99     let mut body_offset = None;
100     let mut content_length: Option<usize> = None;
101     loop {
102         let mut bytes = vec![0; 256];
103         let count = socket.read(&mut bytes).map_err(Error::Socket)?;
104         // If the return value is 0, the peer has performed an orderly shutdown.
105         if count == 0 {
106             break;
107         }
108         res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
109 
110         // End of headers
111         if let Some(o) = res.find("\r\n\r\n") {
112             body_offset = Some(o + "\r\n\r\n".len());
113 
114             // With all headers available we can see if there is any body
115             content_length = if let Some(length) = get_header(&res, "Content-Length") {
116                 Some(length.trim().parse().map_err(Error::ContentLengthParsing)?)
117             } else {
118                 None
119             };
120 
121             if content_length.is_none() {
122                 break;
123             }
124         }
125 
126         if let Some(body_offset) = body_offset {
127             if let Some(content_length) = content_length {
128                 if res.len() >= content_length + body_offset {
129                     break;
130                 }
131             }
132         }
133     }
134     let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
135     let status_code = get_status_code(&res)?;
136 
137     if status_code.is_server_error() {
138         Err(Error::ServerResponse(status_code, body_string))
139     } else {
140         Ok(body_string)
141     }
142 }
143 
144 pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
145     socket: &mut T,
146     method: &str,
147     c: &str,
148     request_body: Option<&str>,
149     request_fds: Vec<RawFd>,
150 ) -> Result<(), Error> {
151     socket
152         .send_with_fds(
153             &[format!(
154                 "{} /api/v1/vm.{} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n",
155                 method, c
156             )
157             .as_bytes()],
158             &request_fds,
159         )
160         .map_err(Error::SocketSendFds)?;
161 
162     if let Some(request_body) = request_body {
163         socket
164             .write_all(format!("Content-Length: {}\r\n", request_body.len()).as_bytes())
165             .map_err(Error::Socket)?;
166     }
167 
168     socket.write_all(b"\r\n").map_err(Error::Socket)?;
169 
170     if let Some(request_body) = request_body {
171         socket
172             .write_all(request_body.as_bytes())
173             .map_err(Error::Socket)?;
174     }
175 
176     socket.flush().map_err(Error::Socket)?;
177 
178     if let Some(body) = parse_http_response(socket)? {
179         println!("{}", body);
180     }
181     Ok(())
182 }
183 
184 pub fn simple_api_command<T: Read + Write + ScmSocket>(
185     socket: &mut T,
186     method: &str,
187     c: &str,
188     request_body: Option<&str>,
189 ) -> Result<(), Error> {
190     simple_api_command_with_fds(socket, method, c, request_body, Vec::new())
191 }
192