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 Connectionclient.connect(&url[Position::BeforeHost..Position::AfterPort], None)?;// Complete TLS handshakelet 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 requestlet 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 responselet 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 metaif 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 metaprintln!("{}", 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 thenbuiltins_fetchurl { inherit (spec) url sha256; }elsepkgs.fetchurl { inherit (spec) url sha256; };fetch_tarball = pkgs: name: spec:letok = str: !builtins.isNull (builtins.match "[a-zA-Z0-9+-._?=]" str);# sanitize the name, though nix will still fail if name starts with periodname' = stringAsChars (x: if !ok x then "-" else x) "${name}-src";in if spec.builtin or true thenbuiltins_fetchTarball {name = name';inherit (spec) url sha256;}elsepkgs.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:letsourcesNixpkgs =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 thensourcesNixpkgselse if hasNixpkgsPath && !hasThisAsNixpkgsPath thenimport <nixpkgs> { }elseabort ''Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) oradd a package called "nixpkgs" to your sources.json.'';# The actual fetching function.fetch = pkgs: name: spec:if !builtins.hasAttr "type" spec thenabort "ERROR: niv spec ${name} does not have a 'type' attribute"else if spec.type == "file" thenfetch_file pkgs specelse if spec.type == "tarball" thenfetch_tarball pkgs name specelse if spec.type == "git" thenfetch_git specelse if spec.type == "local" thenfetch_local specelse if spec.type == "builtin-tarball" thenfetch_builtin-tarball nameelse if spec.type == "builtin-url" thenfetch_builtin-url nameelseabort"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 existmapAttrs = 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#L295range = first: last:if first > last then[ ]elsebuiltins.genList (n: first + n) (last - first + 1);# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257stringToCharacters = s:map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269stringAsChars = f: s: concatStrings (map f (stringToCharacters s));concatStrings = builtins.concatStringsSep "";# fetchTarball version that is compatible between all the versions of Nixbuiltins_fetchTarball = { url, name, sha256 }@attrs:let inherit (builtins) lessThan nixVersion fetchTarball;in if lessThan nixVersion "1.12" thenfetchTarball { inherit name url; }elsefetchTarball attrs;# fetchurl version that is compatible between all the versions of Nixbuiltins_fetchurl = { url, sha256 }@attrs:let inherit (builtins) lessThan nixVersion fetchurl;in if lessThan nixVersion "1.12" thenfetchurl { inherit url; }elsefetchurl attrs;# Create the final "sources" from the configmkSources = config:mapAttrs (name: spec:if builtins.hasAttr "outPath" spec thenabort"The values in sources.json should not have an 'outPath' attribute"elsespec // { outPath = fetch config.pkgs name spec; }) config.sources;# The "config" used by the fetchersmkConfig = { sourcesFile ? ./sources.json, sources ? builtins.fromJSON (builtins.readFile sourcesFile), pkgs ? mkPkgs sources }: rec {# The sources, i.e. the attribute set of spec name to specinherit sources;# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchersinherit 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"}}
letsources = import ./sources.nix;mozilla = import sources.nixpkgs-mozilla;nixpkgs = import sources.nixpkgs;in nixpkgs { overlays = [ mozilla ]; }