diff options
author | Eduardo Julian | 2021-07-14 13:59:02 -0400 |
---|---|---|
committer | Eduardo Julian | 2021-07-14 13:59:02 -0400 |
commit | d6c48ae6a8b58f5974133170863a31c70f0123d1 (patch) | |
tree | 008eb88328009e2f3f07002f35c0378a8a137ed0 /stdlib/source/library/lux/world/shell.lux | |
parent | 2431e767a09894c2f685911ba7f1ba0b7de2a165 (diff) |
Normalized the hierarchy of the standard library modules.
Diffstat (limited to 'stdlib/source/library/lux/world/shell.lux')
-rw-r--r-- | stdlib/source/library/lux/world/shell.lux | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/stdlib/source/library/lux/world/shell.lux b/stdlib/source/library/lux/world/shell.lux new file mode 100644 index 000000000..52cd3efd4 --- /dev/null +++ b/stdlib/source/library/lux/world/shell.lux @@ -0,0 +1,374 @@ +(.module: + [library + [lux #* + ["@" target] + ["jvm" ffi (#+ import:)] + [abstract + [monad (#+ do)]] + [control + ["." function] + ["." try (#+ Try)] + ["." exception (#+ exception:)] + ["." io (#+ IO)] + [security + ["?" policy (#+ Context Safety Safe)]] + [concurrency + ["." atom (#+ Atom)] + ["." promise (#+ Promise)]] + [parser + [environment (#+ Environment)]]] + [data + ["." product] + ["." text + ["%" format (#+ format)] + [encoding + ["." utf8]]] + [collection + ["." array (#+ Array)] + ["." list ("#\." fold functor)] + ["." dictionary]]] + [math + [number (#+ hex) + ["n" nat]]]]] + [// + [file (#+ Path)]]) + +(type: #export Exit + Int) + +(template [<code> <name>] + [(def: #export <name> + Exit + <code>)] + + [+0 normal] + [+1 error] + ) + +(interface: #export (Process !) + (: (-> [] (! (Try Text))) + read) + (: (-> [] (! (Try Text))) + error) + (: (-> Text (! (Try Any))) + write) + (: (-> [] (! (Try Any))) + destroy) + (: (-> [] (! (Try Exit))) + await)) + +(def: (async_process process) + (-> (Process IO) (Process Promise)) + (`` (implementation + (~~ (template [<method>] + [(def: <method> + (|>> (\ process <method>) + promise.future))] + + [read] + [error] + [write] + [destroy] + [await] + ))))) + +(type: #export Command + Text) + +(type: #export Argument + Text) + +(interface: #export (Shell !) + (: (-> [Environment Path Command (List Argument)] (! (Try (Process !)))) + execute)) + +(def: #export (async shell) + (-> (Shell IO) (Shell Promise)) + (implementation + (def: (execute input) + (promise.future + (do (try.with io.monad) + [process (\ shell execute input)] + (wrap (..async_process process))))))) + +## https://en.wikipedia.org/wiki/Code_injection#Shell_injection +(interface: (Policy ?) + (: (-> Command (Safe Command ?)) + command) + (: (-> Argument (Safe Argument ?)) + argument) + (: (All [a] (-> (Safe a ?) a)) + value)) + +(type: (Sanitizer a) + (-> a a)) + +(type: Replacer + (-> Text Text)) + +(def: (replace bad replacer) + (-> Text Replacer (-> Text Text)) + (text.replace_all bad (replacer bad))) + +(def: sanitize_common_command + (-> Replacer (Sanitizer Command)) + (let [x0A (text.from_code (hex "0A")) + xFF (text.from_code (hex "FF"))] + (function (_ replacer) + (|>> (..replace x0A replacer) + (..replace xFF replacer) + (..replace "\" replacer) + (..replace "&" replacer) + (..replace "#" replacer) + (..replace ";" replacer) + (..replace "`" replacer) + (..replace "|" replacer) + (..replace "*" replacer) + (..replace "?" replacer) + (..replace "~" replacer) + (..replace "^" replacer) + (..replace "$" replacer) + (..replace "<" replacer) (..replace ">" replacer) + (..replace "(" replacer) (..replace ")" replacer) + (..replace "[" replacer) (..replace "]" replacer) + (..replace "{" replacer) (..replace "}" replacer))))) + +(def: (policy sanitize_command sanitize_argument) + (Ex [?] (-> (Sanitizer Command) (Sanitizer Argument) (Policy ?))) + (?.with_policy + (: (Context Safety Policy) + (function (_ (^open "?\.")) + (implementation + (def: command (|>> sanitize_command ?\can_upgrade)) + (def: argument (|>> sanitize_argument ?\can_upgrade)) + (def: value ?\can_downgrade)))))) + +(def: unix_policy + (let [replacer (: Replacer + (|>> (format "\"))) + sanitize_command (: (Sanitizer Command) + (..sanitize_common_command replacer)) + sanitize_argument (: (Sanitizer Argument) + (|>> (..replace "'" replacer) + (text.enclose' "'")))] + (..policy sanitize_command sanitize_argument))) + +(def: windows_policy + (let [replacer (: Replacer + (function.constant " ")) + sanitize_command (: (Sanitizer Command) + (|>> (..sanitize_common_command replacer) + (..replace "%" replacer) + (..replace "!" replacer))) + sanitize_argument (: (Sanitizer Argument) + (|>> (..replace "%" replacer) + (..replace "!" replacer) + (..replace text.double_quote replacer) + (text.enclose' text.double_quote)))] + (..policy sanitize_command sanitize_argument))) + +(with_expansions [<jvm> (as_is (import: java/lang/String + ["#::." + (toLowerCase [] java/lang/String)]) + + (def: (jvm::arguments_array arguments) + (-> (List Argument) (Array java/lang/String)) + (product.right + (list\fold (function (_ argument [idx output]) + [(inc idx) (jvm.array_write idx + (:as java/lang/String argument) + output)]) + [0 (jvm.array java/lang/String (list.size arguments))] + arguments))) + + (import: (java/util/Map k v) + ["#::." + (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\fold (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 + ["#::." + (read [] #io #try int)]) + + (import: java/io/BufferedReader + ["#::." + (new [java/io/Reader]) + (readLine [] #io #try #? java/lang/String)]) + + (import: java/io/InputStream) + + (import: java/io/InputStreamReader + ["#::." + (new [java/io/InputStream])]) + + (import: java/io/OutputStream + ["#::." + (write [[byte]] #io #try void)]) + + (import: java/lang/Process + ["#::." + (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: #export 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)]] + (wrap (: (Process IO) + (`` (implementation + (~~ (template [<name> <stream>] + [(def: (<name> _) + (do ! + [output (java/io/BufferedReader::readLine <stream>)] + (case output + (#.Some output) + (wrap output) + + #.None + (\ io.monad wrap (exception.throw ..no_more_output [])))))] + + [read jvm_input] + [error jvm_error] + )) + (def: (write message) + (java/io/OutputStream::write (\ utf8.codec encode message) jvm_output)) + (~~ (template [<name> <method>] + [(def: (<name> _) + (<method> process))] + + [destroy java/lang/Process::destroy] + [await java/lang/Process::waitFor] + )))))))) + + (import: java/io/File + ["#::." + (new [java/lang/String])]) + + (import: java/lang/ProcessBuilder + ["#::." + (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 + ["#::." + (#static getProperty [java/lang/String] #io #try java/lang/String)]) + + ## https://en.wikipedia.org/wiki/Code_injection#Shell_injection + (def: windows? + (IO (Try Bit)) + (\ (try.with io.monad) map + (|>> java/lang/String::toLowerCase (text.starts_with? "windows")) + (java/lang/System::getProperty "os.name"))) + + (implementation: #export default + (Shell IO) + + (def: (execute [environment working_directory command arguments]) + (do {! (try.with io.monad)} + [#let [builder (|> (list& command arguments) + ..jvm::arguments_array + java/lang/ProcessBuilder::new + (java/lang/ProcessBuilder::directory (java/io/File::new working_directory)))] + _ (|> builder + java/lang/ProcessBuilder::environment + (\ try.functor map (..jvm::load_environment environment)) + (\ io.monad wrap)) + process (java/lang/ProcessBuilder::start builder)] + (..default_process process)))) + )] + (for {@.old (as_is <jvm>) + @.jvm (as_is <jvm>)} + (as_is))) + +(interface: #export (Mock s) + (: (-> s (Try [s Text])) + on_read) + (: (-> s (Try [s Text])) + on_error) + (: (-> Text s (Try s)) + on_write) + (: (-> s (Try s)) + on_destroy) + (: (-> s (Try [s Exit])) + on_await)) + +(`` (implementation: (mock_process mock state) + (All [s] (-> (Mock s) (Atom s) (Process IO))) + + (~~ (template [<name> <mock>] + [(def: (<name> _) + (do {! io.monad} + [|state| (atom.read state)] + (case (\ mock <mock> |state|) + (#try.Success [|state| output]) + (do ! + [_ (atom.write |state| state)] + (wrap (#try.Success output))) + + (#try.Failure error) + (wrap (#try.Failure error)))))] + + [read on_read] + [error on_error] + [await on_await] + )) + (def: (write message) + (do {! io.monad} + [|state| (atom.read state)] + (case (\ mock on_write message |state|) + (#try.Success |state|) + (do ! + [_ (atom.write |state| state)] + (wrap (#try.Success []))) + + (#try.Failure error) + (wrap (#try.Failure error))))) + (def: (destroy _) + (do {! io.monad} + [|state| (atom.read state)] + (case (\ mock on_destroy |state|) + (#try.Success |state|) + (do ! + [_ (atom.write |state| state)] + (wrap (#try.Success []))) + + (#try.Failure error) + (wrap (#try.Failure error))))))) + +(implementation: #export (mock mock init) + (All [s] + (-> (-> [Environment Path Command (List Argument)] + (Try (Mock s))) + s + (Shell IO))) + + (def: (execute input) + (io.io (do try.monad + [mock (mock input)] + (wrap (..mock_process mock (atom.atom init))))))) |