1 // Copyright © 2020 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 6 use std::io::{Read, Write}; 7 use std::os::unix::io::RawFd; 8 9 use thiserror::Error; 10 use vmm_sys_util::sock_ctrl_msg::ScmSocket; 11 12 #[derive(Debug, Error)] 13 pub enum Error { 14 #[error("Error writing to or reading from HTTP socket")] 15 Socket(#[source] std::io::Error), 16 #[error("Error sending file descriptors")] 17 SocketSendFds(#[source] vmm_sys_util::errno::Error), 18 #[error("Error parsing HTTP status code")] 19 StatusCodeParsing(#[source] std::num::ParseIntError), 20 #[error("HTTP output is missing protocol statement")] 21 MissingProtocol, 22 #[error("Error parsing HTTP Content-Length field")] 23 ContentLengthParsing(#[source] std::num::ParseIntError), 24 #[error("Server responded with error {0:?}: {1:?}")] 25 ServerResponse( 26 StatusCode, 27 // TODO: Move `api` module from `vmm` to dedicated crate and use a common type definition 28 Option< 29 String, /* Untyped: Currently Vec<String> of error messages from top to root cause */ 30 >, 31 ), 32 } 33 34 #[derive(Clone, Copy, Debug)] 35 pub enum StatusCode { 36 Continue, 37 Ok, 38 NoContent, 39 BadRequest, 40 NotFound, 41 TooManyRequests, 42 InternalServerError, 43 NotImplemented, 44 Unknown, 45 } 46 47 impl StatusCode { 48 fn from_raw(code: usize) -> StatusCode { 49 match code { 50 100 => StatusCode::Continue, 51 200 => StatusCode::Ok, 52 204 => StatusCode::NoContent, 53 400 => StatusCode::BadRequest, 54 404 => StatusCode::NotFound, 55 429 => StatusCode::TooManyRequests, 56 500 => StatusCode::InternalServerError, 57 501 => StatusCode::NotImplemented, 58 _ => StatusCode::Unknown, 59 } 60 } 61 62 fn parse(code: &str) -> Result<StatusCode, Error> { 63 Ok(StatusCode::from_raw( 64 code.trim().parse().map_err(Error::StatusCodeParsing)?, 65 )) 66 } 67 68 fn is_server_error(self) -> bool { 69 !matches!( 70 self, 71 StatusCode::Ok | StatusCode::Continue | StatusCode::NoContent 72 ) 73 } 74 } 75 76 fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> { 77 let header_str = format!("{header}: "); 78 res.find(&header_str) 79 .map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()]) 80 } 81 82 fn get_status_code(res: &str) -> Result<StatusCode, Error> { 83 if let Some(o) = res.find("HTTP/1.1") { 84 Ok(StatusCode::parse( 85 &res[o + "HTTP/1.1 ".len()..res[o..].find('\r').unwrap()], 86 )?) 87 } else { 88 Err(Error::MissingProtocol) 89 } 90 } 91 92 fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> { 93 let mut res = String::new(); 94 let mut body_offset = None; 95 let mut content_length: Option<usize> = None; 96 loop { 97 let mut bytes = vec![0; 256]; 98 let count = socket.read(&mut bytes).map_err(Error::Socket)?; 99 // If the return value is 0, the peer has performed an orderly shutdown. 100 if count == 0 { 101 break; 102 } 103 res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap()); 104 105 // End of headers 106 if let Some(o) = res.find("\r\n\r\n") { 107 body_offset = Some(o + "\r\n\r\n".len()); 108 109 // With all headers available we can see if there is any body 110 content_length = if let Some(length) = get_header(&res, "Content-Length") { 111 Some(length.trim().parse().map_err(Error::ContentLengthParsing)?) 112 } else { 113 None 114 }; 115 116 if content_length.is_none() { 117 break; 118 } 119 } 120 121 if let Some(body_offset) = body_offset { 122 if let Some(content_length) = content_length { 123 if res.len() >= content_length + body_offset { 124 break; 125 } 126 } 127 } 128 } 129 let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..]))); 130 let status_code = get_status_code(&res)?; 131 132 if status_code.is_server_error() { 133 Err(Error::ServerResponse(status_code, body_string)) 134 } else { 135 Ok(body_string) 136 } 137 } 138 139 /// Make an API request using the fully qualified command name. 140 /// For example, full_command could be "vm.create" or "vmm.ping". 141 pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket>( 142 socket: &mut T, 143 method: &str, 144 full_command: &str, 145 request_body: Option<&str>, 146 request_fds: Vec<RawFd>, 147 ) -> Result<Option<String>, Error> { 148 socket 149 .send_with_fds( 150 &[format!( 151 "{method} /api/v1/{full_command} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n" 152 ) 153 .as_bytes()], 154 &request_fds, 155 ) 156 .map_err(Error::SocketSendFds)?; 157 158 if let Some(request_body) = request_body { 159 socket 160 .write_all(format!("Content-Length: {}\r\n", request_body.len()).as_bytes()) 161 .map_err(Error::Socket)?; 162 } 163 164 socket.write_all(b"\r\n").map_err(Error::Socket)?; 165 166 if let Some(request_body) = request_body { 167 socket 168 .write_all(request_body.as_bytes()) 169 .map_err(Error::Socket)?; 170 } 171 172 socket.flush().map_err(Error::Socket)?; 173 174 parse_http_response(socket) 175 } 176 177 pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>( 178 socket: &mut T, 179 method: &str, 180 full_command: &str, 181 request_body: Option<&str>, 182 request_fds: Vec<RawFd>, 183 ) -> Result<(), Error> { 184 let response = simple_api_full_command_with_fds_and_response( 185 socket, 186 method, 187 full_command, 188 request_body, 189 request_fds, 190 )?; 191 192 if response.is_some() { 193 println!("{}", response.unwrap()); 194 } 195 196 Ok(()) 197 } 198 199 pub fn simple_api_full_command<T: Read + Write + ScmSocket>( 200 socket: &mut T, 201 method: &str, 202 full_command: &str, 203 request_body: Option<&str>, 204 ) -> Result<(), Error> { 205 simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new()) 206 } 207 208 pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>( 209 socket: &mut T, 210 method: &str, 211 full_command: &str, 212 request_body: Option<&str>, 213 ) -> Result<Option<String>, Error> { 214 simple_api_full_command_with_fds_and_response( 215 socket, 216 method, 217 full_command, 218 request_body, 219 Vec::new(), 220 ) 221 } 222 223 pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>( 224 socket: &mut T, 225 method: &str, 226 c: &str, 227 request_body: Option<&str>, 228 request_fds: Vec<RawFd>, 229 ) -> Result<(), Error> { 230 // Create the full VM command. For VMM commands, use 231 // simple_api_full_command(). 232 let full_command = format!("vm.{c}"); 233 234 simple_api_full_command_with_fds(socket, method, &full_command, request_body, request_fds) 235 } 236 237 pub fn simple_api_command<T: Read + Write + ScmSocket>( 238 socket: &mut T, 239 method: &str, 240 c: &str, 241 request_body: Option<&str>, 242 ) -> Result<(), Error> { 243 simple_api_command_with_fds(socket, method, c, request_body, Vec::new()) 244 } 245