From 97f05263ef490a063a33733d7ea607411a097df8 Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Fri, 3 Jul 2020 13:11:03 +0200 Subject: pkgs/profpatsch/xdg-open: Prepare decoding http urls I want to be able to open http(s) links that are e.g. images directly in the right application. Aka web urls should be transparent, instead of always opening everthing in the browser. This adds some silly ways of connecting to the server and parsing out the headers, in order to fetch the content-type. --- pkgs/profpatsch/default.nix | 9 ++- pkgs/profpatsch/xdg-open/default.nix | 139 +++++++++++++++++++++++++++++++- pkgs/profpatsch/xdg-open/read-http.rs | 56 +++++++++++++ pkgs/profpatsch/xdg-open/xdg-open.dhall | 4 + 4 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 pkgs/profpatsch/xdg-open/read-http.rs (limited to 'pkgs/profpatsch') diff --git a/pkgs/profpatsch/default.nix b/pkgs/profpatsch/default.nix index c3f0d7e2..999382c0 100644 --- a/pkgs/profpatsch/default.nix +++ b/pkgs/profpatsch/default.nix @@ -220,10 +220,17 @@ in rec { rust-deps = (import ./rust-deps.nix { inherit (pkgs) buildRustCrate; }); - inherit (import ./xdg-open { inherit pkgs getBins importDhall2 writeExecline dhall buildDhallPackage writeRustSimple el-exec; }) + inherit (import ./xdg-open { inherit pkgs getBins importDhall2 writeExecline dhall buildDhallPackage runExeclineLocal netencode-rs writeRustSimple record-get el-exec; }) xdg-open + config Prelude + httparse + get-http-content-type + read-headers-and-follow-redirect mini-url + assert-printf + as-stdin + printenv ; inherit (import ./netencode { inherit pkgs writeRustSimpleLib writeRustSimple el-semicolon el-exec; }) diff --git a/pkgs/profpatsch/xdg-open/default.nix b/pkgs/profpatsch/xdg-open/default.nix index a4aaff97..e73f8093 100644 --- a/pkgs/profpatsch/xdg-open/default.nix +++ b/pkgs/profpatsch/xdg-open/default.nix @@ -1,9 +1,25 @@ -{ pkgs, getBins, importDhall2, writeExecline, dhall, buildDhallPackage, writeRustSimple, el-exec }: +{ pkgs, getBins, +importDhall2, +writeExecline, +dhall, +buildDhallPackage, +runExeclineLocal, +writeRustSimple, +netencode-rs, +record-get, +el-exec +}: let lib = pkgs.lib; bins = getBins pkgs.libnotify [ "notify-send" ] // getBins pkgs.file [ "file" ] + // getBins pkgs.coreutils [ "printf" "ln" "echo" ] + // getBins pkgs.fdtools [ "multitee" ] + // getBins pkgs.s6 [ "s6-ioconnect" ] + // getBins pkgs.s6-portable-utils [ "s6-test" ] + // getBins pkgs.s6-networking [ "s6-tcpclient" ] + // getBins pkgs.netcat-openbsd [ "nc" ] // getBins pkgs.dmenu [ "dmenu" "dmenu_path" ] # TODO: make sure these are the ones from the environment // getBins pkgs.emacs [ "emacsclient" ] @@ -122,6 +138,119 @@ let ; }; + httparse = pkgs.buildRustCrate { + pname = "httparse"; + version = "1.3.4"; + crateName = "httparse"; + sha256 = "0dggj4s0cq69bn63q9nqzzay5acmwl33nrbhjjsh5xys8sk2x4jw"; + }; + + stderr-tee = writeExecline "stderr-tee" {} [ + "pipeline" [ bins.multitee "0-1,2" ] "$@" + ]; + + stderr-prog = writeExecline "stderr-prog" {} [ + "foreground" [ bins.echo "$@" ] + "$@" + ]; + + http-request = writeExecline "http-request-for-url" { } [ + "importas" "-i" "protocol" "protocol" + "ifelse" [ bins.s6-test "$protocol" "=" "http" ] [ (http-https-request false) ] + "ifelse" [ bins.s6-test "$protocol" "=" "https" ] [ (http-https-request true) ] + eprintf "protocol \${protocol} not supported" + ]; + + http-https-request = isHttps: writeExecline "http-https-request" { } ([ + "multisubstitute" [ + "importas" "-ui" "port" "port" + "importas" "-ui" "host" "host" + "importas" "-ui" "path" "path" + ] + "pipeline" [ + bins.printf (lib.concatStringsSep "\r\n" [ + ''HEAD %s HTTP/1.1'' + ''Host: %s'' + ''User-Agent: lol'' + ''Accept: */*'' + "" + "" + ]) "$path" "$host" + ] + bins.nc + ] ++ lib.optional isHttps "-c" ++ [ + "-N" "$host" "$port" + ]); + + printenv = writeRustSimple "printenv" {} + (pkgs.writeText "printenv.rs" '' + use std::io::Write; + use std::os::unix::ffi::OsStrExt; + fn main() -> std::io::Result<()> { + let usage = || { + eprintln!("usage: printenv VAR"); + std::process::exit(1) + }; + let mut args = std::env::args_os(); + let _ = args.next().unwrap_or_else(usage); + let var = args.next().unwrap_or_else(usage); + match std::env::var_os(&var) { + None => { + let mut err = std::io::stderr(); + err.write_all("env variable ".as_bytes())?; + err.write_all(var.as_bytes())?; + err.write_all(" does not exist\n".as_bytes())?; + }, + Some(stuff) => std::io::stdout().write_all(stuff.as_bytes())? + } + Ok(()) + } + ''); + + assert-printf = writeExecline "assert-printf" { argMode = "env"; } [ + "ifelse" [ "runblock" "2" ] + [ "runblock" "-r" "2" ] + "fdmove" "-c" "1" "2" + "runblock" "1" bins.printf + ]; + + as-stdin = writeExecline "as-stdin" { readNArgs = 1; } [ + "pipeline" [ printenv "$1" ] "$@" + ]; + + read-headers-and-follow-redirect = writeExecline "read-headers-and-follow-redirect" { readNArgs = 1; } + (let go = writeExecline "go" {} [ + "pipeline" [ http-request ] + "pipeline" [ read-http ] + record-get [ "status" "status-text" "headers" ] + "importas" "-ui" "status" "status" + # TODO: a test util for netencode values + "ifelse" [ bins.s6-test "$status" "=" "n6:301," ] + # retry the redirection location + [ as-stdin "headers" + record-get [ "Location" ] + "importas" "-ui" "Location" "Location" + "export" "host" "$Location" + "if" [ "echo" "redirected to \${Location}" ] + # save path, which would be overwritten by mini-url + "importas" "old-path" "path" + mini-url "$Location" + "export" "path" "$old-path" + "$0" + ] + printenv "headers" + ]; + in [ + mini-url "$1" + go + ]); + + + read-http = writeRustSimple "read-http" { + dependencies = [ httparse netencode-rs ]; + buildInputs = [ pkgs.skalibs ]; + } ./read-http.rs; + mini-url = writeRustSimple "mini-url" { dependencies = [ el-exec ]; buildInputs = [ pkgs.skalibs ]; @@ -130,10 +259,18 @@ let verbose = true; } ./mini-url.rs; + eprintf = writeExecline "eprintf" {} [ + "fdmove" "-c" "1" "2" bins.printf "%s" "$@" + ]; + in { inherit xdg-open Prelude + read-headers-and-follow-redirect mini-url + assert-printf + as-stdin + printenv ; } diff --git a/pkgs/profpatsch/xdg-open/read-http.rs b/pkgs/profpatsch/xdg-open/read-http.rs new file mode 100644 index 00000000..9fe8d6a9 --- /dev/null +++ b/pkgs/profpatsch/xdg-open/read-http.rs @@ -0,0 +1,56 @@ +extern crate httparse; +extern crate netencode; + +use std::os::unix::io::FromRawFd; +use std::io::Read; +use std::io::Write; + +use netencode::{encode, U}; + +fn main() -> std::io::Result<()> { + + fn die(msg: T) { + eprintln!("{}", msg); + std::process::exit(1); + } + + // max header size chosen arbitrarily + let mut headers = [httparse::EMPTY_HEADER; 128]; + let mut resp = httparse::Response::new(&mut headers); + let mut buf = vec![]; + std::io::stdin().read_to_end(&mut buf)?; + let res = resp.parse(&mut buf); + match res { + Err(err) => die(format!("http response error: {:?}", err)), + Ok(_start_of_body) => { + match resp.code { + Some(code) => write_dict(code, resp.reason, resp.headers)?, + None => die(format!("no http status")) + } + } + }; + Ok(()) +} + +fn write_dict<'buf>(code: u16, reason: Option<&'buf str>, headers: &mut [httparse::Header<'buf>]) -> std::io::Result<()> { + let mut http = vec![ + ("status", Box::new(U::N6(code as u64))), + ]; + + if let Some(t) = reason { + http.push(("status-text", Box::new(U::Text(t.as_bytes())))) + }; + + http.push(("headers", Box::new(U::Record( + headers.iter_mut().map( + |httparse::Header { name, value }| + (*name, + Box::new(U::Binary(value))) + ).collect::>() + )))); + + encode( + &mut std::io::stdout(), + U::Record(http) + ) +} diff --git a/pkgs/profpatsch/xdg-open/xdg-open.dhall b/pkgs/profpatsch/xdg-open/xdg-open.dhall index a7daebe3..03f168c3 100644 --- a/pkgs/profpatsch/xdg-open/xdg-open.dhall +++ b/pkgs/profpatsch/xdg-open/xdg-open.dhall @@ -91,6 +91,10 @@ let xdg-open = set -e file="$1" + mime= + + # match on protocols + # if you want to match files reliably, start with file:// case "$file" in ${prettyLines { indent = 2 -- cgit 1.4.1