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