aboutsummaryrefslogtreecommitdiff
path: root/stdlib/source/library/lux/world/shell.lux
diff options
context:
space:
mode:
authorEduardo Julian2021-07-14 13:59:02 -0400
committerEduardo Julian2021-07-14 13:59:02 -0400
commitd6c48ae6a8b58f5974133170863a31c70f0123d1 (patch)
tree008eb88328009e2f3f07002f35c0378a8a137ed0 /stdlib/source/library/lux/world/shell.lux
parent2431e767a09894c2f685911ba7f1ba0b7de2a165 (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.lux374
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)))))))