Compare commits

...

4 Commits

Author SHA1 Message Date
Vincent Ollivier 3795c5ccfb
Merge 899c4f511b into 348b2b6d63 2024-04-07 20:14:41 -07:00
Vincent Ollivier 348b2b6d63
Allow copying file to dir (#607)
* Allow copying file to dir

* Add error when trying to copy dir

* Fix root case

* Using --help is not an error

* Add check for empty destination

* Rename test
2024-04-05 09:42:05 +02:00
Vincent Ollivier d04d1a0539
Fix HTTP server (#609)
* Refactor index code

* Fix chunked receive buffer

* Fix join_path

* Fix relative path
2024-04-05 09:40:26 +02:00
Vincent Ollivier 6ab9ad713b
Fix HTTP server content type (#608) 2024-04-02 20:45:53 +02:00
4 changed files with 137 additions and 52 deletions

View File

@ -224,7 +224,14 @@ pub fn read_dir(path: &str) -> Result<Vec<FileInfo>, ()> {
}
#[test_case]
fn test_file() {
fn test_filename() {
assert_eq!(filename("/path/to/file.txt"), "file.txt");
assert_eq!(filename("/file.txt"), "file.txt");
assert_eq!(filename("file.txt"), "file.txt");
}
#[test_case]
fn test_fs() {
use crate::sys::fs::{dismount, format_mem, mount_mem};
mount_mem();
format_mem();

View File

@ -2,12 +2,11 @@ use crate::api::console::Style;
use crate::api::fs;
use crate::api::process::ExitCode;
use alloc::format;
use alloc::string::{String, ToString};
pub fn main(args: &[&str]) -> Result<(), ExitCode> {
let n = args.len();
if n != 3 {
help();
return Err(ExitCode::UsageError);
}
for i in 1..n {
match args[i] {
"-h" | "--help" => {
@ -17,12 +16,26 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
_ => continue,
}
}
if n != 3 {
help();
return Err(ExitCode::UsageError);
}
if args[2].is_empty() {
error!("Could not write to ''");
return Err(ExitCode::Failure);
}
let source = args[1];
let dest = args[2];
let dest = destination(args[1], args[2]);
if fs::is_dir(source) {
error!("Could not copy directory '{}'", source);
return Err(ExitCode::Failure);
}
if let Ok(contents) = fs::read_to_bytes(source) {
if fs::write(dest, &contents).is_ok() {
if fs::write(&dest, &contents).is_ok() {
Ok(())
} else {
error!("Could not write to '{}'", dest);
@ -34,6 +47,16 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
}
}
fn destination(source: &str, dest: &str) -> String {
debug_assert!(!dest.is_empty());
let mut dest = dest.trim_end_matches('/').to_string();
if dest.is_empty() || fs::is_dir(&dest) {
let file = fs::filename(source);
dest = format!("{}/{}", dest, file);
}
dest
}
fn help() {
let csi_option = Style::color("LightCyan");
let csi_title = Style::color("Yellow");
@ -43,3 +66,24 @@ fn help() {
csi_title, csi_reset, csi_option, csi_reset
);
}
#[test_case]
fn test_destination() {
use crate::{usr, sys};
sys::fs::mount_mem();
sys::fs::format_mem();
usr::install::copy_files(false);
assert_eq!(destination("foo.txt", "bar.txt"), "bar.txt");
assert_eq!(destination("foo.txt", "/"), "/foo.txt");
assert_eq!(destination("foo.txt", "/tmp"), "/tmp/foo.txt");
assert_eq!(destination("foo.txt", "/tmp/"), "/tmp/foo.txt");
assert_eq!(destination("/usr/vinc/foo.txt", "/"), "/foo.txt");
assert_eq!(destination("/usr/vinc/foo.txt", "/tmp"), "/tmp/foo.txt");
assert_eq!(destination("/usr/vinc/foo.txt", "/tmp/"), "/tmp/foo.txt");
sys::fs::dismount();
}

View File

@ -23,6 +23,7 @@ use smoltcp::wire::IpAddress;
const MAX_CONNECTIONS: usize = 32;
const POLL_DELAY_DIV: usize = 128;
const INDEX: [&str; 4] = ["", "/index.html", "/index.htm", "/index.txt"];
#[derive(Clone)]
struct Request {
@ -212,16 +213,14 @@ fn get(req: &Request, res: &mut Response) {
res.body.extend_from_slice(b"<h1>Moved Permanently</h1>\r\n");
} else {
let mut not_found = true;
for autocomplete in
&["", "/index.html", "/index.htm", "/index.txt"]
{
let real_path = format!("{}{}", res.real_path, autocomplete);
for index in INDEX {
let real_path = format!("{}{}", res.real_path, index);
if fs::is_dir(&real_path) {
continue;
}
if let Ok(buf) = fs::read_to_bytes(&real_path) {
res.code = 200;
res.mime = content_type(&res.real_path);
res.mime = content_type(&real_path);
let tmp;
res.body.extend_from_slice(
if res.mime.starts_with("text/") {
@ -338,6 +337,9 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
i += 1;
}
// NOTE: This specific format is needed by `join_path`
let dir = format!("/{}", fs::realpath(&dir).trim_matches('/'));
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SocketSet::new(vec![]);
@ -381,45 +383,57 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
None => continue,
};
if socket.may_recv() {
let res = socket.recv(|buf| {
if let Some(req) = Request::from(endpoint.addr, buf) {
let mut res = Response::new(req.clone());
let sep = if req.path == "/" { "" } else { "/" };
res.real_path = format!(
"{}{}{}",
dir,
sep,
req.path.strip_suffix('/').unwrap_or(&req.path)
).replace("//", "/");
match req.verb.as_str() {
"GET" => {
get(&req, &mut res)
}
"PUT" if !read_only => {
put(&req, &mut res)
}
"DELETE" if !read_only => {
delete(&req, &mut res)
}
_ => {
let s = b"<h1>Bad Request</h1>\r\n";
res.body.extend_from_slice(s);
res.code = 400;
res.mime = "text/html".to_string();
}
// The amount of octets queued in the receive buffer may be
// larger than the contiguous slice returned by `recv` so
// we need to loop over chunks of it until it is empty.
let recv_queue = socket.recv_queue();
let mut receiving = true;
let mut buf = vec![];
while receiving {
let res = socket.recv(|chunk| {
buf.extend_from_slice(chunk);
if buf.len() < recv_queue {
return (chunk.len(), None);
}
res.end();
println!("{}", res);
(buf.len(), Some(res))
} else {
(0, None)
receiving = false;
let addr = endpoint.addr;
if let Some(req) = Request::from(addr, &buf) {
let mut res = Response::new(req.clone());
res.real_path = join_path(&dir, &req.path);
match req.verb.as_str() {
"GET" => {
get(&req, &mut res)
}
"PUT" if !read_only => {
put(&req, &mut res)
}
"DELETE" if !read_only => {
delete(&req, &mut res)
}
_ => {
let s = b"<h1>Bad Request</h1>\r\n";
res.body.extend_from_slice(s);
res.code = 400;
res.mime = "text/html".to_string();
}
}
res.end();
println!("{}", res);
(chunk.len(), Some(res))
} else {
(0, None)
}
});
if receiving {
continue;
}
});
if let Ok(Some(res)) = res {
*keep_alive = res.is_persistent();
for chunk in res.buf.chunks(buf_len) {
send_queue.push_back(chunk.to_vec());
if let Ok(Some(res)) = res {
*keep_alive = res.is_persistent();
for chunk in res.buf.chunks(buf_len) {
send_queue.push_back(chunk.to_vec());
}
}
}
if socket.can_send() {
@ -468,6 +482,15 @@ fn content_type(path: &str) -> String {
}.to_string()
}
// Join the requested file path to the root dir of the server
fn join_path(dir: &str, path: &str) -> String {
debug_assert!(dir.starts_with('/'));
debug_assert!(path.starts_with('/'));
let path = path.trim_matches('/');
let sep = if dir == "/" || path == "" { "" } else { "/" };
format!("{}{}{}", dir, sep, path)
}
fn usage() {
let csi_option = Style::color("LightCyan");
let csi_title = Style::color("Yellow");
@ -491,3 +514,13 @@ fn usage() {
csi_option, csi_reset
);
}
#[test_case]
fn test_join_path() {
assert_eq!(join_path("/foo", "/bar/"), "/foo/bar");
assert_eq!(join_path("/foo", "/bar"), "/foo/bar");
assert_eq!(join_path("/foo", "/"), "/foo");
assert_eq!(join_path("/", "/bar/"), "/bar");
assert_eq!(join_path("/", "/bar"), "/bar");
assert_eq!(join_path("/", "/"), "/");
}

View File

@ -19,9 +19,10 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
}
// TODO: Avoid doing copy+delete
match usr::copy::main(args) {
Ok(()) => usr::delete::main(&args[0..2]),
_ => Err(ExitCode::Failure),
if usr::copy::main(args).is_ok() {
usr::delete::main(&args[0..2])
} else {
Err(ExitCode::Failure)
}
}