JFL253ZZDBEQ4SAYXRI5LIHTU5IH6Z7UUMOJTKLNMY3VU7KATH2QC
use std::io::{stdin, Read, Write};
use anyhow::{anyhow, Result};
use libtls::{config::Builder, tls::Tls};
use pico_args::Arguments;
use url::{Position, Url};
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
enum Status {
Input,
Success,
Redirect,
TemporaryFailure,
PermanentFailure,
ClientCertificateRequired,
}
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
struct Header {
status: u8,
meta: String,
}
impl Header {
pub fn status(&self) -> Status {
if self.status >= 60 {
Status::ClientCertificateRequired
} else if self.status >= 50 {
Status::PermanentFailure
} else if self.status >= 40 {
Status::TemporaryFailure
} else if self.status >= 30 {
Status::Redirect
} else if self.status >= 20 {
Status::Success
} else if self.status >= 10 {
Status::Input
} else {
panic!("invalid status: {}", self.status)
}
}
}
fn parse_address(mut address: String) -> Result<Url> {
if !address.trim_start().starts_with("gemini://") {
if address.contains("://") {
return Err(anyhow!("only gemini is supported"));
}
address = format!("gemini://{}", address);
}
let mut url = Url::parse(address.as_str())?;
if !url.username().is_empty() || url.password().is_some() {
return Err(anyhow!("no user info is allowed"));
}
if !url.has_host() {
return Err(anyhow!("must supply host"));
}
if url.port().is_none() {
url.set_port(Some(1965)).expect("port error")
}
Ok(url)
}
fn make_request(url: &Url) -> Result<(Header, String)> {
let mut client = Tls::client()?;
let config = Builder::new().noverifycert().build()?;
client.configure(&config)?;
// Open Connection
client.connect(&url[Position::BeforeHost..Position::AfterPort], None)?;
// Complete TLS handshake
let shaken = client.tls_handshake()?;
if shaken != 0 {
return Err(anyhow!("tls handshake failed"));
}
// Validate Server Certificate
// per section 4.2, we opt not to validate TLS connections beyond the handshake
// Send request
let req = format!("{}\r\n", url);
let written = client.write(req.as_bytes())?;
if written == 0 {
return Err(anyhow!("failed to write request"));
} else if written != req.len() {
eprintln!(
"warning: request was {} bytes, only wrote {}",
req.len(),
written
)
}
// Handle response
let mut res = String::new();
let read = client.read_to_string(&mut res)?;
if read == 0 {
return Err(anyhow!("failed to read response"));
}
let line_break = res
.match_indices("\r\n")
.next()
.expect("response missing CRLF")
.0;
// 2 status + 1 space + 1024 meta
if line_break >= 1028 {
return Err(anyhow!("meta was too long"));
}
let body = res.split_off(line_break);
let header = Header {
status: (&res[..2]).parse().unwrap(),
meta: (&res[2..]).to_string(),
};
Ok((header, body))
}
fn handle_response(url: Url, header: Header, body: String) -> Result<()> {
println!("status: {}", header.status);
Ok(match header.status() {
Status::Input => {
println!("server is requesting input");
println!("{}", header.meta);
print!("> ");
let mut line = String::from("?");
stdin().read_line(&mut line)?;
let with_input = url.join(line.as_str())?;
println!("connecting to {} again with new input", with_input);
let (header, body) = make_request(&with_input)?;
handle_response(with_input, header, body)?
}
Status::Success => {
// ignoring meta
println!("{}", body)
}
Status::Redirect => {
let address = header.meta;
let redirect = parse_address(address)?;
println!("redirecting {} to {}", url, redirect);
let (header, body) = make_request(&redirect)?;
handle_response(redirect, header, body)?
}
Status::TemporaryFailure | Status::PermanentFailure => {
eprintln!("error from server");
eprintln!("{}", header.meta)
}
Status::ClientCertificateRequired => {
eprintln!("server requires a client certificate");
eprintln!("{}", header.meta)
}
})
}
fn main() -> Result<()> {
let mut args = Arguments::from_env();
let address: String = args.free_from_str()?.expect("must supply address");
let url = parse_address(address)?;
println!("connecting to {}", url);
let (header, body) = make_request(&url)?;
handle_response(url, header, body)
}
[package]
authors = ["Matthew Ess <daringseal@gmail.com>"]
edition = "2018"
name = "sputnik"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.37"
libtls = "1.2.0"
pico-args = "0.3.4"
url = "2.2.0"
# This file has been generated by Niv.
let
#
# The fetchers. fetch_<type> fetches specs of type <type>.
#
fetch_file = pkgs: spec:
if spec.builtin or true then
builtins_fetchurl { inherit (spec) url sha256; }
else
pkgs.fetchurl { inherit (spec) url sha256; };
fetch_tarball = pkgs: name: spec:
let
ok = str: !builtins.isNull (builtins.match "[a-zA-Z0-9+-._?=]" str);
# sanitize the name, though nix will still fail if name starts with period
name' = stringAsChars (x: if !ok x then "-" else x) "${name}-src";
in if spec.builtin or true then
builtins_fetchTarball {
name = name';
inherit (spec) url sha256;
}
else
pkgs.fetchzip {
name = name';
inherit (spec) url sha256;
};
fetch_git = spec:
builtins.fetchGit {
url = spec.repo;
inherit (spec) rev ref;
};
fetch_local = spec: spec.path;
fetch_builtin-tarball = name:
throw ''
[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=tarball -a builtin=true'';
fetch_builtin-url = name:
throw ''
[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=file -a builtin=true'';
#
# Various helpers
#
# The set of packages used when specs are fetched using non-builtins.
mkPkgs = sources:
let
sourcesNixpkgs =
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; })
{ };
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
in if builtins.hasAttr "nixpkgs" sources then
sourcesNixpkgs
else if hasNixpkgsPath && !hasThisAsNixpkgsPath then
import <nixpkgs> { }
else
abort ''
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
add a package called "nixpkgs" to your sources.json.
'';
# The actual fetching function.
fetch = pkgs: name: spec:
if !builtins.hasAttr "type" spec then
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
else if spec.type == "file" then
fetch_file pkgs spec
else if spec.type == "tarball" then
fetch_tarball pkgs name spec
else if spec.type == "git" then
fetch_git spec
else if spec.type == "local" then
fetch_local spec
else if spec.type == "builtin-tarball" then
fetch_builtin-tarball name
else if spec.type == "builtin-url" then
fetch_builtin-url name
else
abort
"ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
# Ports of functions for older nix versions
# a Nix version of mapAttrs if the built-in doesn't exist
mapAttrs = builtins.mapAttrs or (f: set:
with builtins;
listToAttrs (map (attr: {
name = attr;
value = f attr set.${attr};
}) (attrNames set)));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
range = first: last:
if first > last then
[ ]
else
builtins.genList (n: first + n) (last - first + 1);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
stringToCharacters = s:
map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
concatStrings = builtins.concatStringsSep "";
# fetchTarball version that is compatible between all the versions of Nix
builtins_fetchTarball = { url, name, sha256 }@attrs:
let inherit (builtins) lessThan nixVersion fetchTarball;
in if lessThan nixVersion "1.12" then
fetchTarball { inherit name url; }
else
fetchTarball attrs;
# fetchurl version that is compatible between all the versions of Nix
builtins_fetchurl = { url, sha256 }@attrs:
let inherit (builtins) lessThan nixVersion fetchurl;
in if lessThan nixVersion "1.12" then
fetchurl { inherit url; }
else
fetchurl attrs;
# Create the final "sources" from the config
mkSources = config:
mapAttrs (name: spec:
if builtins.hasAttr "outPath" spec then
abort
"The values in sources.json should not have an 'outPath' attribute"
else
spec // { outPath = fetch config.pkgs name spec; }) config.sources;
# The "config" used by the fetchers
mkConfig = { sourcesFile ? ./sources.json
, sources ? builtins.fromJSON (builtins.readFile sourcesFile)
, pkgs ? mkPkgs sources }: rec {
# The sources, i.e. the attribute set of spec name to spec
inherit sources;
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
inherit pkgs;
};
in mkSources (mkConfig { }) // {
__functor = _: settings: mkSources (mkConfig settings);
}
{
"niv": {
"branch": "master",
"description": "Easy dependency management for Nix projects",
"homepage": "https://github.com/nmattia/niv",
"owner": "nmattia",
"repo": "niv",
"rev": "ba57d5a29b4e0f2085917010380ef3ddc3cf380f",
"sha256": "1kpsvc53x821cmjg1khvp1nz7906gczq8mp83664cr15h94sh8i4",
"type": "tarball",
"url": "https://github.com/nmattia/niv/archive/ba57d5a29b4e0f2085917010380ef3ddc3cf380f.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixpkgs": {
"branch": "nixos-unstable",
"description": "Nix Packages collection",
"homepage": null,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a371c1071161104d329f6a85d922fd92b7cbab63",
"sha256": "1k5wa16wyb1byk5xfjlq4m518gsw6g1kypx4xb09k3inni13p0r4",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/a371c1071161104d329f6a85d922fd92b7cbab63.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixpkgs-mozilla": {
"branch": "master",
"description": "mozilla related nixpkgs (extends nixos/nixpkgs repo)",
"homepage": null,
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"rev": "8c007b60731c07dd7a052cce508de3bb1ae849b4",
"sha256": "1zybp62zz0h077zm2zmqs2wcg3whg6jqaah9hcl1gv4x8af4zhs6",
"type": "tarball",
"url": "https://github.com/mozilla/nixpkgs-mozilla/archive/8c007b60731c07dd7a052cce508de3bb1ae849b4.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
}
}
let
sources = import ./sources.nix;
mozilla = import sources.nixpkgs-mozilla;
nixpkgs = import sources.nixpkgs;
in nixpkgs { overlays = [ mozilla ]; }