You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
phetch/src/gopher.rs

132 lines
3.7 KiB
Rust

use std::io;
use std::io::{Read, Write};
use std::net::TcpStream;
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Type {
5 years ago
Text = '0' as isize, // 0 | 96 | cyan
Menu, // 1 | 94 | blue
CSOEntity, // 2
5 years ago
Error, // 3 | 91 | red
Binhex, // 4 | 4 | white underline
DOSFile, // 5 | 4 | white underline
UUEncoded, // 6 | 4 | white underline
Search, // 7 | 0 | white
Telnet, // 8
5 years ago
Binary, // 9 | 4 | white underline
Mirror = '+' as isize, // +
5 years ago
GIF = 'g' as isize, // g | 4 | white underline
Telnet3270 = 'T' as isize, // T
5 years ago
HTML = 'h' as isize, // h | 92 | green
Info = 'i' as isize, // i | 93 | yellow
Sound = 's' as isize, // s | 4 | white underline
Document = 'd' as isize, // d | 4 | white underline
}
impl Type {
pub fn is_download(&self) -> bool {
match self {
Type::Binhex
| Type::DOSFile
| Type::UUEncoded
| Type::Binary
| Type::GIF
| Type::Sound
| Type::Document => true,
_ => false,
}
}
}
// Fetches a URL and returns a raw Gopher response.
5 years ago
pub fn fetch_url(url: &str) -> io::Result<String> {
let (_, host, port, sel) = parse_url(url);
fetch(host, port, sel)
}
// Fetches a URL by its component parts and returns a raw Gopher response.
pub fn fetch(host: &str, port: &str, selector: &str) -> io::Result<String> {
let mut body = String::new();
let stream = TcpStream::connect(format!("{}:{}", host, port))
.and_then(|mut stream| {
stream.write(format!("{}\r\n", selector).as_ref());
Ok(stream)
})
.and_then(|mut stream| {
stream.read_to_string(&mut body);
Ok(())
});
match stream {
Ok(_) => Ok(body),
Err(e) => Err(e),
}
}
5 years ago
enum Parsing {
Host,
Port,
Selector,
}
// Parses gopher URL into parts.
pub fn parse_url<'a>(url: &'a str) -> (Type, &'a str, &'a str, &'a str) {
let url = url.trim_start_matches("gopher://");
let mut host = "";
let mut port = "70";
let mut sel = "/";
5 years ago
let mut typ = Type::Menu;
5 years ago
let mut state = Parsing::Host;
let mut start = 0;
for (i, c) in url.char_indices() {
match state {
Parsing::Host => {
match c {
':' => state = Parsing::Port,
'/' => state = Parsing::Selector,
_ => continue,
}
host = &url[start..i];
5 years ago
start = if c == '/' { i } else { i + 1 };
5 years ago
}
Parsing::Port => {
if c == '/' {
state = Parsing::Selector;
port = &url[start..i];
5 years ago
start = i;
5 years ago
}
}
Parsing::Selector => {}
}
}
match state {
Parsing::Selector => sel = &url[start..],
Parsing::Port => port = &url[start..],
Parsing::Host => host = &url[start..],
};
let mut chars = sel.chars();
5 years ago
if let (Some('/'), Some(t), Some('/')) = (chars.nth(0), chars.nth(0), chars.nth(0)) {
typ = match t {
'0' => {
sel = &sel[2..];
Type::Text
}
'1' => {
sel = &sel[2..];
Type::Menu
}
'h' => {
sel = &sel[2..];
Type::HTML
}
_ => typ,
5 years ago
}
}
(typ, host, port, sel)
}