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