pull/1/head
dvkt 4 years ago
commit 8b825dbd5e

2
Cargo.lock generated

@ -42,7 +42,7 @@ dependencies = [
[[package]] [[package]]
name = "phd" name = "phd"
version = "0.1.5-dev" version = "0.1.6-dev"
dependencies = [ dependencies = [
"content_inspector 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "content_inspector 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"gophermap 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "gophermap 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",

@ -1,6 +1,6 @@
[package] [package]
name = "phd" name = "phd"
version = "0.1.5-dev" version = "0.1.6-dev"
authors = ["dvkt <c@dvkt.io>"] authors = ["dvkt <c@dvkt.io>"]
license = "MIT" license = "MIT"
edition = "2018" edition = "2018"
@ -25,8 +25,8 @@ gophermap = "0.1.2"
[package.metadata.release] [package.metadata.release]
pre-release-replacements = [ pre-release-replacements = [
{file="README.md", search="phd-v\\d\\.\\d\\.\\d-", replace="{{crate_name}}-v{{version}}-"}, {file="README.md", search="phd-v\\d+\\.\\d+\\.\\d+-", replace="{{crate_name}}-v{{version}}-"},
{file="README.md", search="/v\\d\\.\\d\\.\\d/", replace="/v{{version}}/"}, {file="README.md", search="/v\\d+\\.\\d+\\.\\d+/", replace="/v{{version}}/"},
] ]
dev-version-ext = "dev" dev-version-ext = "dev"

@ -4,7 +4,11 @@
| )| )| ) | )| )| )
|__/ | / |__/ |__/ | / |__/
| |
--> <p align="center"><img src="./img/logo.png"></p> --> <p align="center"> <img src="./img/logo.png"> <br>
<a href="https://github.com/dvkt/phd/releases">
<img src="https://img.shields.io/github/v/release/dvkt/phd?include_prereleases">
</a>
</p>
`phd` is an esoteric gopher server for small gopherholes. `phd` is an esoteric gopher server for small gopherholes.
@ -13,7 +17,7 @@ sub-directories, and binary files over gopher. any `.gph` files will
be served up as [gopermaps][map] and executable `.gph` files will be be served up as [gopermaps][map] and executable `.gph` files will be
run as a script with their output served to the client, like cgi! run as a script with their output served to the client, like cgi!
special files: ### special files:
- **header.gph**: if it exists in a directory, its content will be - **header.gph**: if it exists in a directory, its content will be
shown above the directory's content. put ascii art in it. shown above the directory's content. put ascii art in it.
@ -29,6 +33,8 @@ any line in a `.gph` file that doesn't contain tabs (`\t`) and doesn't
start with an `i` will get an `i` automatically prefixed, turning it start with an `i` will get an `i` automatically prefixed, turning it
into a gopher information item. into a gopher information item.
### dynamic content:
any `.gph` file that is marked **executable** with be run as if it any `.gph` file that is marked **executable** with be run as if it
were a shell script and its output will be sent to the client. it will were a shell script and its output will be sent to the client. it will
be passed three arguments: the query string (if any, the host, and the be passed three arguments: the query string (if any, the host, and the
@ -36,10 +42,12 @@ port. do with them what you will.
for example: for example:
$ cat echo.gph ```sh
#!/bin/sh $ cat echo.gph
echo "Hi, world! You said:" $1 #!/bin/sh
echo "1Visit Gopherpedia / gopherpedia.com 70" echo "Hi, world! You said:" $1
echo "1Visit Gopherpedia / gopherpedia.com 70"
```
then: then:
@ -49,9 +57,11 @@ then:
or more seriously: or more seriously:
$ cat figlet.gph ```sh
#!/bin/sh $ cat figlet.gph
figlet $1 #!/bin/sh
figlet $1
```
then: then:
@ -63,6 +73,42 @@ then:
[INFO] |_| |_|_| \__, |\___/| .__/|_| |_|\___|_| [INFO] |_| |_|_| \__, |\___/| .__/|_| |_|\___|_|
[INFO] |___/ |_| [INFO] |___/ |_|
### ruby on rails:
`sh` is fun, but for serious work you need a serious scripting
language like Ruby or PHP or Node.JS:
```ruby
$ cat sizes.gph
#!/usr/bin/env ruby
def filesize(file)
(size=File.size file) > (k=1024) ? "#{size/k}K" : "#{size}B"
end
puts "~ file sizes ~"
spaces = 20
Dir[__dir__ + "/*"].each do |entry|
name = File.basename entry
puts "#{name}#{' ' * (spaces - name.length)}#{filesize entry}"
end
```
now you can finally share the file sizes of a directory with the world
of Gopher!
$ phetch -r 0.0.0.0:7070/1/sizes
i~ file sizes ~ (null) 127.0.0.1 7070
iCargo.toml 731B (null) 127.0.0.1 7070
iLICENSE 1K (null) 127.0.0.1 7070
iMakefile 724B (null) 127.0.0.1 7070
itarget 288B (null) 127.0.0.1 7070
iphd 248K (null) 127.0.0.1 7070
iCargo.lock 2K (null) 127.0.0.1 7070
iREADME.md 4K (null) 127.0.0.1 7070
img 96B (null) 127.0.0.1 7070
isizes.gph 276B (null) 127.0.0.1 7070
isrc 224B (null) 127.0.0.1 7070
## usage ## usage

@ -0,0 +1,34 @@
use std::fmt;
macro_rules! color {
($t:ident, $code:expr) => {
pub struct $t;
impl fmt::Display for $t {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\x1b[{}m", $code)
}
}
};
}
color!(Black, 90);
color!(Red, 91);
color!(Green, 92);
color!(Yellow, 93);
color!(Blue, 94);
color!(Magenta, 95);
color!(Cyan, 96);
color!(White, 97);
color!(DarkBlack, 30);
color!(DarkRed, 31);
color!(DarkGreen, 32);
color!(DarkYellow, 33);
color!(DarkBlue, 34);
color!(DarkMagenta, 35);
color!(DarkCyan, 36);
color!(DarkWhite, 37);
color!(Reset, 0);
color!(Bold, 1);
color!(Underline, 4);

@ -1,3 +1,4 @@
pub mod color;
pub mod request; pub mod request;
pub mod server; pub mod server;

@ -50,5 +50,12 @@ impl Request {
} }
} }
self.selector.push_str(line); self.selector.push_str(line);
// strip trailing /
if let Some(last) = self.selector.chars().last() {
if last == '/' {
self.selector.pop();
}
}
} }
} }

@ -1,4 +1,4 @@
use crate::{Request, Result}; use crate::{color, Request, Result};
use gophermap::{GopherMenu, ItemType}; use gophermap::{GopherMenu, ItemType};
use std::{ use std::{
fs, fs,
@ -28,14 +28,30 @@ pub fn start(host: &str, port: u16, root: &str) -> Result<()> {
let full_root_path = fs::canonicalize(&root)?.to_string_lossy().to_string(); let full_root_path = fs::canonicalize(&root)?.to_string_lossy().to_string();
let pool = ThreadPool::new(MAX_WORKERS); let pool = ThreadPool::new(MAX_WORKERS);
println!("-> Listening on {} at {}", addr, full_root_path); println!(
"{}┬ Listening {}on {}{}{} at {}{}{}",
color::Yellow,
color::Reset,
color::Yellow,
addr,
color::Reset,
color::Blue,
full_root_path,
color::Reset
);
for stream in listener.incoming() { for stream in listener.incoming() {
let stream = stream?; let stream = stream?;
println!("-> Connection from: {}", stream.peer_addr()?); println!(
"{}┌ Connection{} from {}{}",
color::Green,
color::Reset,
color::Magenta,
stream.peer_addr()?
);
let req = Request::from(host, port, root)?; let req = Request::from(host, port, root)?;
pool.execute(move || { pool.execute(move || {
if let Err(e) = accept(stream, req) { if let Err(e) = accept(stream, req) {
eprintln!("-! {}", e); eprintln!("{}└ {}{}", color::Red, e, color::Reset);
} }
}); });
} }
@ -47,7 +63,14 @@ fn accept(stream: TcpStream, mut req: Request) -> Result<()> {
let reader = BufReader::new(&stream); let reader = BufReader::new(&stream);
let mut lines = reader.lines(); let mut lines = reader.lines();
if let Some(Ok(line)) = lines.next() { if let Some(Ok(line)) = lines.next() {
println!("-> Client sent: {:?}", line); println!(
"{}│{} Client sent:\t{}{:?}{}",
color::Green,
color::Reset,
color::Cyan,
line,
color::Reset
);
req.parse_request(&line); req.parse_request(&line);
write_response(&stream, req)?; write_response(&stream, req)?;
} }
@ -168,6 +191,15 @@ where
} }
menu.end()?; menu.end()?;
println!(
"{}│{} Server reply:\t{}DIR {}{}{}",
color::Green,
color::Reset,
color::Yellow,
color::Bold,
req.relative_file_path(),
color::Reset,
);
Ok(()) Ok(())
} }
@ -176,8 +208,18 @@ fn write_file<'a, W>(mut w: &'a W, req: Request) -> Result<()>
where where
&'a W: Write, &'a W: Write,
{ {
let mut f = fs::File::open(&req.file_path())?; let path = req.file_path();
let mut f = fs::File::open(&path)?;
io::copy(&mut f, &mut w)?; io::copy(&mut f, &mut w)?;
println!(
"{}│{} Server reply:\t{}FILE {}{}{}",
color::Green,
color::Reset,
color::Yellow,
color::Bold,
req.relative_file_path(),
color::Reset,
);
Ok(()) Ok(())
} }
@ -192,7 +234,7 @@ where
let reader = if is_executable(&path) { let reader = if is_executable(&path) {
shell(&path, &[&req.query, &req.host, &req.port.to_string()])? shell(&path, &[&req.query, &req.host, &req.port.to_string()])?
} else { } else {
fs::read_to_string(path)? fs::read_to_string(&path)?
}; };
for line in reader.lines() { for line in reader.lines() {
@ -213,6 +255,15 @@ where
line.push_str("\r\n"); line.push_str("\r\n");
w.write_all(line.as_bytes())?; w.write_all(line.as_bytes())?;
} }
println!(
"{}│{} Server reply:\t{}MAP {}{}{}",
color::Green,
color::Reset,
color::Yellow,
color::Bold,
req.relative_file_path(),
color::Reset,
);
Ok(()) Ok(())
} }
@ -221,6 +272,13 @@ where
&'a W: Write, &'a W: Write,
{ {
let line = format!("3Not Found: {}\t/\tnone\t70\r\n", req.selector); let line = format!("3Not Found: {}\t/\tnone\t70\r\n", req.selector);
println!(
"{}│ Not found: {}{}{}",
color::Red,
color::Cyan,
req.relative_file_path(),
color::Reset,
);
w.write_all(line.as_bytes())?; w.write_all(line.as_bytes())?;
Ok(()) Ok(())
} }

Loading…
Cancel
Save