remote_control/
http.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::Error;
6use fuchsia_async as fasync;
7use futures::prelude::*;
8use std::net::SocketAddr;
9
10/// Describes the possible errors to return for an HTTP request.
11#[derive(Debug, PartialEq, Eq)]
12enum HttpError {
13    NotFound,
14    BadRequest,
15}
16
17/// Verifies that an HTTP request contains a "GET /", and that's it.
18fn verify_raw_http_request(buf: &[u8], msg_size: usize) -> Result<(), HttpError> {
19    if let Some(first_line) =
20        std::str::from_utf8(&buf[..msg_size]).ok().and_then(|s| s.lines().next())
21    {
22        let mut parts = first_line.split_whitespace();
23        if parts.next() == Some("GET") && parts.next() == Some("/") {
24            Ok(())
25        } else {
26            Err(HttpError::NotFound)
27        }
28    } else {
29        Err(HttpError::BadRequest)
30    }
31}
32
33/// Runs a barebones HTTP server that serves the authorized_keys on port 9797.
34/// This is not using any third party code so as to avoid increasing binary size on devices. As
35/// such, the verification of the incoming request is also very barebones. We read maximum 1024kb of
36/// a request (so we expect something small), and only expect a `GET /` as a request.
37pub async fn run_http_server() -> Result<(), Error> {
38    log::info!("Setting up http server for authorized_keys");
39    let addr = SocketAddr::from((std::net::Ipv6Addr::UNSPECIFIED, 9797));
40    let listener = fasync::net::TcpListener::bind(&addr)?;
41    let mut task_group = fasync::TaskGroup::new();
42    let mut stream = listener.accept_stream();
43    log::info!("Trying to load file from /data/ssh/authorized_keys");
44    let response =
45        match fuchsia_fs::file::read_in_namespace_to_string("/data/ssh/authorized_keys").await {
46            Ok(c) => format!("HTTP/1.0 200 OK\r\n\r\n{c}"),
47            Err(e) => {
48                log::warn!("Unable to load file from /data/ssh/authorized_keys: {e}");
49                format!("HTTP/1.0 500 Internal Server Error\r\n\r\nUnable to read file: {e}")
50            }
51        };
52    while let Some(r) = stream.try_next().await? {
53        let (mut sock, _addr) = r;
54        let response_clone = response.clone();
55        task_group.spawn(async move {
56            let mut buf = [0u8; 1024];
57            let response = match sock.read(&mut buf).await {
58                Ok(n) => match verify_raw_http_request(&buf, n) {
59                    Ok(()) => response_clone,
60                    Err(HttpError::NotFound) => "HTTP/1.0 404 Not Found\r\n\r\n".to_owned(),
61                    Err(HttpError::BadRequest) => "HTTP/1.0 400 Bad Request\r\n\r\n".to_owned(),
62                },
63                Err(e) => {
64                    log::error!("error reading from socket: {e:?}");
65                    return;
66                }
67            };
68            if let Err(e) = sock.write_all(response.as_bytes()).await {
69                log::error!("error writing to socket: {e:?}");
70            }
71        });
72    }
73    task_group.join().await;
74    Ok(())
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_verify_raw_http_request_valid() {
83        let req = "GET / HTTP/1.1\r\n\r\n";
84        assert!(verify_raw_http_request(req.as_bytes(), req.len()).is_ok());
85    }
86
87    #[test]
88    fn test_verify_raw_http_request_not_found() {
89        let req = "GET /foo HTTP/1.1\r\n\r\n";
90        assert_eq!(verify_raw_http_request(req.as_bytes(), req.len()), Err(HttpError::NotFound));
91    }
92
93    #[test]
94    fn test_verify_raw_http_request_bad_request() {
95        let req = &[0, 1, 2, 3];
96        assert_eq!(verify_raw_http_request(req, req.len()), Err(HttpError::NotFound));
97    }
98}