about summary refs log tree commit diff
path: root/pkgs/profpatsch/xdg-open
diff options
context:
space:
mode:
authorProfpatsch <mail@profpatsch.de>2020-07-03 13:11:03 +0200
committerProfpatsch <mail@profpatsch.de>2020-07-06 20:13:42 +0200
commit97f05263ef490a063a33733d7ea607411a097df8 (patch)
treef1617705e22437c908459e1de059993bcab09ed3 /pkgs/profpatsch/xdg-open
parente409df3861f48de44d0e37277ce007e348a7a0dc (diff)
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.
Diffstat (limited to 'pkgs/profpatsch/xdg-open')
-rw-r--r--pkgs/profpatsch/xdg-open/default.nix139
-rw-r--r--pkgs/profpatsch/xdg-open/read-http.rs56
-rw-r--r--pkgs/profpatsch/xdg-open/xdg-open.dhall4
3 files changed, 198 insertions, 1 deletions
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<T: std::fmt::Display>(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::<Vec<_>>()
+    ))));
+
+    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