/ firmware / src / services / http / files.rs
files.rs
  1  use alloc::string::String as AllocString;
  2  
  3  use picoserve::response::{IntoResponse, StatusCode};
  4  
  5  use crate::filesystems::sd::is_supported_flat_file_name;
  6  
  7  const CHUNK_SIZE: usize = 4096;
  8  
  9  fn content_type_for(name: &str) -> &'static str {
 10      if name.ends_with(".html") || name.ends_with(".htm") {
 11          "text/html; charset=utf-8"
 12      } else if name.ends_with(".js") {
 13          "application/javascript; charset=utf-8"
 14      } else if name.ends_with(".wasm") || name.ends_with(".was") {
 15          "application/wasm"
 16      } else if name.ends_with(".css") {
 17          "text/css"
 18      } else {
 19          "application/octet-stream"
 20      }
 21  }
 22  
 23  pub struct IndexService;
 24  
 25  impl picoserve::routing::RequestHandlerService<()> for IndexService {
 26      async fn call_request_handler_service<
 27          R: picoserve::io::Read,
 28          W: picoserve::response::ResponseWriter<Error = R::Error>,
 29      >(
 30          &self,
 31          _state: &(),
 32          (): (),
 33          request: picoserve::request::Request<'_, R>,
 34          response_writer: W,
 35      ) -> Result<picoserve::ResponseSent, W::Error> {
 36          let size = match crate::filesystems::sd::file_size_at("", "index.htm") {
 37              Ok(size) => size,
 38              Err(_) => {
 39                  return (StatusCode::NOT_FOUND, "index.htm not found on SD card\n")
 40                      .write_to(request.body_connection.finalize().await?, response_writer)
 41                      .await;
 42              }
 43          };
 44  
 45          picoserve::response::chunked::ChunkedResponse::new(SdCardChunks {
 46              file_name: AllocString::from("index.htm"),
 47              size,
 48              content_type: "text/html; charset=utf-8",
 49          })
 50          .write_to(request.body_connection.finalize().await?, response_writer)
 51          .await
 52      }
 53  }
 54  
 55  pub struct StaticFileService;
 56  
 57  impl picoserve::routing::RequestHandlerService<(), (AllocString,)> for StaticFileService {
 58      async fn call_request_handler_service<
 59          R: picoserve::io::Read,
 60          W: picoserve::response::ResponseWriter<Error = R::Error>,
 61      >(
 62          &self,
 63          _state: &(),
 64          (file_name,): (AllocString,),
 65          request: picoserve::request::Request<'_, R>,
 66          response_writer: W,
 67      ) -> Result<picoserve::ResponseSent, W::Error> {
 68          if !is_supported_flat_file_name(&file_name) {
 69              return (StatusCode::BAD_REQUEST, "invalid path\n")
 70                  .write_to(request.body_connection.finalize().await?, response_writer)
 71                  .await;
 72          }
 73  
 74          let size = match crate::filesystems::sd::file_size_at("", &file_name) {
 75              Ok(size) => size,
 76              Err(_) => {
 77                  return (StatusCode::NOT_FOUND, "not found\n")
 78                      .write_to(request.body_connection.finalize().await?, response_writer)
 79                      .await;
 80              }
 81          };
 82  
 83          let content_type = content_type_for(&file_name);
 84  
 85          picoserve::response::chunked::ChunkedResponse::new(SdCardChunks {
 86              file_name,
 87              size,
 88              content_type,
 89          })
 90          .write_to(request.body_connection.finalize().await?, response_writer)
 91          .await
 92      }
 93  }
 94  
 95  struct SdCardChunks {
 96      file_name: AllocString,
 97      size: u32,
 98      content_type: &'static str,
 99  }
100  
101  impl picoserve::response::chunked::Chunks for SdCardChunks {
102      fn content_type(&self) -> &'static str {
103          self.content_type
104      }
105  
106      async fn write_chunks<W: picoserve::io::Write>(
107          self,
108          mut writer: picoserve::response::chunked::ChunkWriter<W>,
109      ) -> Result<picoserve::response::chunked::ChunksWritten, W::Error> {
110          let mut offset = 0u32;
111          let mut buffer = [0u8; CHUNK_SIZE];
112  
113          while offset < self.size {
114              let bytes_read = match crate::filesystems::sd::read_file_chunk(
115                  &self.file_name,
116                  offset,
117                  &mut buffer,
118              ) {
119                  Ok(count) if count > 0 => count,
120                  _ => break,
121              };
122              writer.write_chunk(&buffer[..bytes_read]).await?;
123              offset += bytes_read as u32;
124          }
125  
126          writer.finalize().await
127      }
128  }