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