(.require [library [lux (.except) ["[0]" ffi (.only import)] [abstract [monad (.only do)]] [control ["[0]" function] ["[0]" try (.only Try)] ["[0]" exception (.only exception)] ["[0]" io (.only IO)] [security ["?" policy (.only Context Safety Safe)]] [concurrency ["[0]" atom (.only Atom)] ["[0]" async (.only Async)]]] [data ["[0]" product] ["[0]" text (.only) ["%" \\format (.only format)] [encoding ["[0]" utf8]]] [collection ["[0]" array (.only Array)] ["[0]" list (.use "[1]#[0]" mix functor)] ["[0]" dictionary]]] [math [number (.only hex) ["n" nat]]] [meta ["@" target]]]] [// [file (.only Path)] [environment [\\parser (.only Environment)]]]) (type .public Exit Int) (with_template [ ] [(def .public Exit )] [+0 normal] [+1 error] ) (type .public (Process !) (Interface (is (-> [] (! (Try Text))) read) (is (-> [] (! (Try Text))) fail) (is (-> Text (! (Try Any))) write) (is (-> [] (! (Try Any))) destroy) (is (-> [] (! (Try Exit))) await))) (def (async_process process) (-> (Process IO) (Process Async)) (`` (implementation (,, (with_template [] [(def (|>> (at process ) async.future))] [read] [fail] [write] [destroy] [await] ))))) (type .public Command Text) (type .public Argument Text) (type .public (Shell !) (Interface (is (-> [Environment Path Command (List Argument)] (! (Try (Process !)))) execute))) (def .public (async shell) (-> (Shell IO) (Shell Async)) (implementation (def (execute input) (async.future (do (try.with io.monad) [process (at shell execute input)] (in (..async_process process))))))) ... https://en.wikipedia.org/wiki/Code_injection#Shell_injection (type (Policy ?) (Interface (is (-> Command (Safe Command ?)) command) (is (-> Argument (Safe Argument ?)) argument) (is (All (_ a) (-> (Safe a ?) a)) value))) (type (Sanitizer a) (-> a a)) (type Replacer (-> Text Text)) (def (replaced bad replacer) (-> Text Replacer (-> Text Text)) (text.replaced bad (replacer bad))) (def safe_common_command (-> Replacer (Sanitizer Command)) (let [x0A (text.of_char (hex "0A")) xFF (text.of_char (hex "FF"))] (function (_ replacer) (|>> (..replaced x0A replacer) (..replaced xFF replacer) (..replaced "\" replacer) (..replaced "&" replacer) (..replaced "#" replacer) (..replaced ";" replacer) (..replaced "`" replacer) (..replaced "|" replacer) (..replaced "*" replacer) (..replaced "?" replacer) (..replaced "~" replacer) (..replaced "^" replacer) (..replaced "$" replacer) (..replaced "<" replacer) (..replaced ">" replacer) (..replaced "(" replacer) (..replaced ")" replacer) (..replaced "[" replacer) (..replaced "]" replacer) (..replaced "{" replacer) (..replaced "}" replacer))))) (def (policy safe_command safe_argument) (Ex (_ ?) (-> (Sanitizer Command) (Sanitizer Argument) (Policy ?))) (?.with_policy (is (Context Safety Policy) (function (_ (open "?[0]")) (implementation (def command (|>> safe_command ?#can_upgrade)) (def argument (|>> safe_argument ?#can_upgrade)) (def value ?#can_downgrade)))))) (def unix_policy (let [replacer (is Replacer (|>> (format "\"))) safe_command (is (Sanitizer Command) (..safe_common_command replacer)) safe_argument (is (Sanitizer Argument) (|>> (..replaced "'" replacer) (text.enclosed' "'")))] (..policy safe_command safe_argument))) (def windows_policy (let [replacer (is Replacer (function.constant " ")) safe_command (is (Sanitizer Command) (|>> (..safe_common_command replacer) (..replaced "%" replacer) (..replaced "!" replacer))) safe_argument (is (Sanitizer Argument) (|>> (..replaced "%" replacer) (..replaced "!" replacer) (..replaced text.double_quote replacer) (text.enclosed' text.double_quote)))] (..policy safe_command safe_argument))) (with_expansions [ (these (import java/lang/String "[1]::[0]" (toLowerCase [] java/lang/String)) (def (jvm::arguments_array arguments) (-> (List Argument) (Array java/lang/String)) (product.right (list#mix (function (_ argument [idx output]) [(++ idx) (ffi.write! idx (ffi.as_string argument) output)]) [0 (ffi.array java/lang/String (list.size arguments))] arguments))) (import (java/util/Map k v) "[1]::[0]" (put [k v] v)) (def (jvm::load_environment input target) (-> Environment (java/util/Map java/lang/String java/lang/String) (java/util/Map java/lang/String java/lang/String)) (list#mix (function (_ [key value] target') (exec (java/util/Map::put (as java/lang/String key) (as java/lang/String value) target') target')) target (dictionary.entries input))) (import java/io/Reader "[1]::[0]" (read [] "io" "try" int)) (import java/io/BufferedReader "[1]::[0]" (new [java/io/Reader]) (readLine [] "io" "try" "?" java/lang/String)) (import java/io/InputStream "[1]::[0]") (import java/io/InputStreamReader "[1]::[0]" (new [java/io/InputStream])) (import java/io/OutputStream "[1]::[0]" (write [[byte]] "io" "try" void)) (import java/lang/Process "[1]::[0]" (getInputStream [] "io" "try" java/io/InputStream) (getErrorStream [] "io" "try" java/io/InputStream) (getOutputStream [] "io" "try" java/io/OutputStream) (destroy [] "io" "try" void) (waitFor [] "io" "try" int)) (exception .public no_more_output) (def (default_process process) (-> java/lang/Process (IO (Try (Process IO)))) (do [! (try.with io.monad)] [jvm_input (java/lang/Process::getInputStream process) jvm_error (java/lang/Process::getErrorStream process) jvm_output (java/lang/Process::getOutputStream process) .let [jvm_input (|> jvm_input java/io/InputStreamReader::new java/io/BufferedReader::new) jvm_error (|> jvm_error java/io/InputStreamReader::new java/io/BufferedReader::new)]] (in (is (Process IO) (`` (implementation (,, (with_template [ ] [(def ( _) (do ! [output (java/io/BufferedReader::readLine )] (case output {.#Some output} (in (ffi.of_string output)) {.#None} (at io.monad in (exception.except ..no_more_output [])))))] [read jvm_input] [fail jvm_error] )) (def (write message) (java/io/OutputStream::write (at utf8.codec encoded message) jvm_output)) (,, (with_template [ ] [(def ( _) (|> process ))] [destroy java/lang/Process::destroy] [await (<| (at ! each (|>> ffi.of_int)) java/lang/Process::waitFor)] )))))))) (import java/io/File "[1]::[0]" (new [java/lang/String])) (import java/lang/ProcessBuilder "[1]::[0]" (new [[java/lang/String]]) (environment [] "try" (java/util/Map java/lang/String java/lang/String)) (directory [java/io/File] java/lang/ProcessBuilder) (start [] "io" "try" java/lang/Process)) (import java/lang/System "[1]::[0]" ("static" getProperty [java/lang/String] "io" "try" java/lang/String)) ... https://en.wikipedia.org/wiki/Code_injection#Shell_injection (def windows? (IO (Try Bit)) (at (try.with io.monad) each (|>> java/lang/String::toLowerCase ffi.of_string (text.starts_with? "windows")) (java/lang/System::getProperty (ffi.as_string "os.name")))) (def .public default (Shell IO) (implementation (def (execute [environment working_directory the_command arguments]) (do [! (try.with io.monad)] [.let [builder (|> (list.partial the_command arguments) ..jvm::arguments_array java/lang/ProcessBuilder::new (java/lang/ProcessBuilder::directory (java/io/File::new (ffi.as_string working_directory))))] _ (|> builder java/lang/ProcessBuilder::environment (at try.functor each (..jvm::load_environment environment)) (at io.monad in)) process (java/lang/ProcessBuilder::start builder)] (..default_process process))))) )] (for @.old (these ) @.jvm (these ) (these))) (type .public (Mock s) (Interface (is (-> s (Try [s Text])) on_read) (is (-> s (Try [s Text])) on_fail) (is (-> Text s (Try s)) on_write) (is (-> s (Try s)) on_destroy) (is (-> s (Try [s Exit])) on_await))) (`` (def (mock_process state mock) (All (_ s) (-> (Atom s) (Mock s) (Process IO))) (implementation (,, (with_template [ ] [(def ( _) (do [! io.monad] [|state| (atom.read! state)] (case (at mock |state|) {try.#Success [|state| output]} (do ! [_ (atom.write! |state| state)] (in {try.#Success output})) {try.#Failure error} (in {try.#Failure error}))))] [read on_read] [fail on_fail] [await on_await] )) (def (write message) (do [! io.monad] [|state| (atom.read! state)] (case (at mock on_write message |state|) {try.#Success |state|} (do ! [_ (atom.write! |state| state)] (in {try.#Success []})) {try.#Failure error} (in {try.#Failure error})))) (def (destroy _) (do [! io.monad] [|state| (atom.read! state)] (case (at mock on_destroy |state|) {try.#Success |state|} (do ! [_ (atom.write! |state| state)] (in {try.#Success []})) {try.#Failure error} (in {try.#Failure error}))))))) (def .public (mock mock init) (All (_ s) (-> (-> [Environment Path Command (List Argument)] (Try (Mock s))) s (Shell IO))) (implementation (def execute (|>> mock (at try.monad each (..mock_process (atom.atom init))) io.io))))