Project 3: Advanced Shell Commands Using Rust
Implement the following shell commands as Rust programs in the part1
project you created last time:
findtext
- Output every line that contains a specified pattern. The first command-line argument is the fixed-string pattern. Remaining arguments are the names of the files to inspect.
- Source file:
src/bin/findtext.rs
in the part1
project.
order
- Works like
cat
, except the output lines must be sorted before being output. All lines from all files will be mixed together and then sorted. If the “-r” command-line argument is provided, they should be sorted in reverse order.
- Source file:
src/bin/order.rs
in the part1
project.
webget
- Downloads web pages and saves them locally.
- Source file:
src/bin/webget.rs
in the part1
project.
- Its command-line interface is as follows:
Web pages are retrieved using the GET
command in the HTTP
protocol. Here is
the basic structure of a GET
:
GET [file] HTTP/1.1
Host: [hostname]
Connection: Close
For example, if we wish to retrieve this web page, we might issue the following command:
webget https://hendrix-cs.github.io/csci320/projects/rust2.html
Note that the protocol is https
, the host is hendrix-cs.github.io
, and the requested
file is csci320/projects/rust2.html
.
Given that command, webget
would send the following GET
message:
GET /csci320/projects/rust2.html HTTP/1.1
Host: hendrix-cs.github.io
Connection: Close
Note: There is a blank line after the Connection: Close
line. Without this blank line,
the message is incomplete, and you will not receive a response. The http
protocol requires that each line end with both a carriage return and a linefeed.
Each line in your message, then, should end with \r\n
, and the last four characters
in your message as a whole should be \r\n\r\n
.
Responses
When a file is successfully retrieved, you will first receive an HTTP header before the file contents.
Here is the beginning of a sample HTTP header:
HTTP/1.1 200 OK Connection: close Content-Length: 14140
Server: GitHub.com
Content-Type: text/html; charset=utf-8
Strict-Transport-Security: max-age=31556952
last-modified: Thu, 21 Jan 2021 00:44:30 GMT
You will need to extract the HTML file from the returned characters. To do so:
- Until you encounter a blank line, print out each header line to the command line.
- Once a blank line is encountered:
- All lines that follow should be saved in a file.
- The local filename should be the name of the requested file from the server.
Security
The original http
protocol had no security features. Messages could easily be inspected while in transit. The
https
protocol superimposes the http
protocol atop the
Transport Layer Security (TLS) protocol. TLS provides
end-to-end encryption to prevent messages from being inspected in transit.
Place the following lines in the dependencies
section of your Cargo.toml
to use the RusTLS crate:
rustls = "0.23.22"
webpki-roots = "0.26.8"
anyhow = "1"
Using sockets secured by TLS requires the following steps:
use std::{io::{BufReader, Read, Write}, net::TcpStream, sync::Arc};
fn send_message(host: &str, port: usize, message: &str) -> anyhow::Result<()> {
// Obtain standard set of trusted TLS certificates
let root_store = rustls::RootCertStore {
roots: webpki_roots::TLS_SERVER_ROOTS.into(),
};
// Use the trusted set above; do not offer a certificate on the
// client side, as the client is not claiming to be trusted.
let config = rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
// Set up ServerName object
let server_name = host.to_string().try_into().unwrap();
// Create TCP connection
let mut tcp = TcpStream::connect(format!("{}:{}", host, port))?;
// Create TLS connection
let mut connector = rustls::ClientConnection::new(Arc::new(config), server_name)?;
// Create I/O stream
let mut stream = rustls::Stream::new(&mut connector, &mut tcp);
write!(stream, "{message}")?;
// TODO: ****Write code here to read and process the response from the socket.****
Ok(())
}
To read from a socket, I recommend using BufReader
.
Design Hints
- Separate the processing of command-line arguments from their implementation.
- To this end, create a data structure to represent a request. It could contain:
- The host name
- The file to retrieve
- Write a function or method to create a string containing the
GET
message to be sent over the socket.
- This facilitates debugging as well, as it makes it easy to print the
GET
message to the command line.
Submissions
- Share the
part1
project as a private GitHub repository.
- Submit your GitHub URL via Teams.
Assessment
- Level 1: Correctly complete any one of the three programs.
- Level 2: Correctly complete any two of the three programs.
- Level 3: All three programs correctly completed. They also
cleanly handle any errors that arise without a panic.